mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 23:08:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
523
drivers/mtd/nand/Kconfig
Normal file
523
drivers/mtd/nand/Kconfig
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
config MTD_NAND_IDS
|
||||
tristate "Include chip ids for known NAND devices."
|
||||
depends on MTD
|
||||
help
|
||||
Useful for NAND drivers that do not use the NAND subsystem but
|
||||
still like to take advantage of the known chip information.
|
||||
|
||||
config MTD_NAND_ECC
|
||||
tristate
|
||||
|
||||
config MTD_NAND_ECC_SMC
|
||||
bool "NAND ECC Smart Media byte order"
|
||||
depends on MTD_NAND_ECC
|
||||
default n
|
||||
help
|
||||
Software ECC according to the Smart Media Specification.
|
||||
The original Linux implementation had byte 0 and 1 swapped.
|
||||
|
||||
|
||||
menuconfig MTD_NAND
|
||||
tristate "NAND Device Support"
|
||||
depends on MTD
|
||||
select MTD_NAND_IDS
|
||||
select MTD_NAND_ECC
|
||||
help
|
||||
This enables support for accessing all type of NAND flash
|
||||
devices. For further information see
|
||||
<http://www.linux-mtd.infradead.org/doc/nand.html>.
|
||||
|
||||
if MTD_NAND
|
||||
|
||||
config MTD_NAND_BCH
|
||||
tristate
|
||||
select BCH
|
||||
depends on MTD_NAND_ECC_BCH
|
||||
default MTD_NAND
|
||||
|
||||
config MTD_NAND_ECC_BCH
|
||||
bool "Support software BCH ECC"
|
||||
default n
|
||||
help
|
||||
This enables support for software BCH error correction. Binary BCH
|
||||
codes are more powerful and cpu intensive than traditional Hamming
|
||||
ECC codes. They are used with NAND devices requiring more than 1 bit
|
||||
of error correction.
|
||||
|
||||
config MTD_SM_COMMON
|
||||
tristate
|
||||
default n
|
||||
|
||||
config MTD_NAND_DENALI
|
||||
tristate "Support Denali NAND controller"
|
||||
depends on HAS_DMA
|
||||
help
|
||||
Enable support for the Denali NAND controller. This should be
|
||||
combined with either the PCI or platform drivers to provide device
|
||||
registration.
|
||||
|
||||
config MTD_NAND_DENALI_PCI
|
||||
tristate "Support Denali NAND controller on Intel Moorestown"
|
||||
depends on PCI && MTD_NAND_DENALI
|
||||
help
|
||||
Enable the driver for NAND flash on Intel Moorestown, using the
|
||||
Denali NAND controller core.
|
||||
|
||||
config MTD_NAND_DENALI_DT
|
||||
tristate "Support Denali NAND controller as a DT device"
|
||||
depends on HAVE_CLK && MTD_NAND_DENALI
|
||||
help
|
||||
Enable the driver for NAND flash on platforms using a Denali NAND
|
||||
controller as a DT device.
|
||||
|
||||
config MTD_NAND_DENALI_SCRATCH_REG_ADDR
|
||||
hex "Denali NAND size scratch register address"
|
||||
default "0xFF108018"
|
||||
depends on MTD_NAND_DENALI_PCI
|
||||
help
|
||||
Some platforms place the NAND chip size in a scratch register
|
||||
because (some versions of) the driver aren't able to automatically
|
||||
determine the size of certain chips. Set the address of the
|
||||
scratch register here to enable this feature. On Intel Moorestown
|
||||
boards, the scratch register is at 0xFF108018.
|
||||
|
||||
config MTD_NAND_GPIO
|
||||
tristate "GPIO NAND Flash driver"
|
||||
depends on GPIOLIB
|
||||
help
|
||||
This enables a GPIO based NAND flash driver.
|
||||
|
||||
config MTD_NAND_AMS_DELTA
|
||||
tristate "NAND Flash device on Amstrad E3"
|
||||
depends on MACH_AMS_DELTA
|
||||
default y
|
||||
help
|
||||
Support for NAND flash on Amstrad E3 (Delta).
|
||||
|
||||
config MTD_NAND_OMAP2
|
||||
tristate "NAND Flash device on OMAP2, OMAP3 and OMAP4"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
help
|
||||
Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4
|
||||
platforms.
|
||||
|
||||
config MTD_NAND_OMAP_BCH
|
||||
depends on MTD_NAND_OMAP2
|
||||
bool "Support hardware based BCH error correction"
|
||||
default n
|
||||
select BCH
|
||||
help
|
||||
This config enables the ELM hardware engine, which can be used to
|
||||
locate and correct errors when using BCH ECC scheme. This offloads
|
||||
the cpu from doing ECC error searching and correction. However some
|
||||
legacy OMAP families like OMAP2xxx, OMAP3xxx do not have ELM engine
|
||||
so this is optional for them.
|
||||
|
||||
config MTD_NAND_OMAP_BCH_BUILD
|
||||
def_tristate MTD_NAND_OMAP2 && MTD_NAND_OMAP_BCH
|
||||
|
||||
config MTD_NAND_RICOH
|
||||
tristate "Ricoh xD card reader"
|
||||
default n
|
||||
depends on PCI
|
||||
select MTD_SM_COMMON
|
||||
help
|
||||
Enable support for Ricoh R5C852 xD card reader
|
||||
You also need to enable ether
|
||||
NAND SSFDC (SmartMedia) read only translation layer' or new
|
||||
expermental, readwrite
|
||||
'SmartMedia/xD new translation layer'
|
||||
|
||||
config MTD_NAND_AU1550
|
||||
tristate "Au1550/1200 NAND support"
|
||||
depends on MIPS_ALCHEMY
|
||||
help
|
||||
This enables the driver for the NAND flash controller on the
|
||||
AMD/Alchemy 1550 SOC.
|
||||
|
||||
config MTD_NAND_BF5XX
|
||||
tristate "Blackfin on-chip NAND Flash Controller driver"
|
||||
depends on BF54x || BF52x
|
||||
help
|
||||
This enables the Blackfin on-chip NAND flash controller
|
||||
|
||||
No board specific support is done by this driver, each board
|
||||
must advertise a platform_device for the driver to attach.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called bf5xx-nand.
|
||||
|
||||
config MTD_NAND_BF5XX_HWECC
|
||||
bool "BF5XX NAND Hardware ECC"
|
||||
default y
|
||||
depends on MTD_NAND_BF5XX
|
||||
help
|
||||
Enable the use of the BF5XX's internal ECC generator when
|
||||
using NAND.
|
||||
|
||||
config MTD_NAND_BF5XX_BOOTROM_ECC
|
||||
bool "Use Blackfin BootROM ECC Layout"
|
||||
default n
|
||||
depends on MTD_NAND_BF5XX_HWECC
|
||||
help
|
||||
If you wish to modify NAND pages and allow the Blackfin on-chip
|
||||
BootROM to boot from them, say Y here. This is only necessary
|
||||
if you are booting U-Boot out of NAND and you wish to update
|
||||
U-Boot from Linux' userspace. Otherwise, you should say N here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MTD_NAND_S3C2410
|
||||
tristate "NAND Flash support for Samsung S3C SoCs"
|
||||
depends on ARCH_S3C24XX || ARCH_S3C64XX
|
||||
help
|
||||
This enables the NAND flash controller on the S3C24xx and S3C64xx
|
||||
SoCs
|
||||
|
||||
No board specific support is done by this driver, each board
|
||||
must advertise a platform_device for the driver to attach.
|
||||
|
||||
config MTD_NAND_S3C2410_DEBUG
|
||||
bool "Samsung S3C NAND driver debug"
|
||||
depends on MTD_NAND_S3C2410
|
||||
help
|
||||
Enable debugging of the S3C NAND driver
|
||||
|
||||
config MTD_NAND_S3C2410_HWECC
|
||||
bool "Samsung S3C NAND Hardware ECC"
|
||||
depends on MTD_NAND_S3C2410
|
||||
help
|
||||
Enable the use of the controller's internal ECC generator when
|
||||
using NAND. Early versions of the chips have had problems with
|
||||
incorrect ECC generation, and if using these, the default of
|
||||
software ECC is preferable.
|
||||
|
||||
config MTD_NAND_NDFC
|
||||
tristate "NDFC NanD Flash Controller"
|
||||
depends on 4xx
|
||||
select MTD_NAND_ECC_SMC
|
||||
help
|
||||
NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs
|
||||
|
||||
config MTD_NAND_S3C2410_CLKSTOP
|
||||
bool "Samsung S3C NAND IDLE clock stop"
|
||||
depends on MTD_NAND_S3C2410
|
||||
default n
|
||||
help
|
||||
Stop the clock to the NAND controller when there is no chip
|
||||
selected to save power. This will mean there is a small delay
|
||||
when the is NAND chip selected or released, but will save
|
||||
approximately 5mA of power when there is nothing happening.
|
||||
|
||||
config MTD_NAND_DISKONCHIP
|
||||
tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation)"
|
||||
depends on HAS_IOMEM
|
||||
select REED_SOLOMON
|
||||
select REED_SOLOMON_DEC16
|
||||
help
|
||||
This is a reimplementation of M-Systems DiskOnChip 2000,
|
||||
Millennium and Millennium Plus as a standard NAND device driver,
|
||||
as opposed to the earlier self-contained MTD device drivers.
|
||||
This should enable, among other things, proper JFFS2 operation on
|
||||
these devices.
|
||||
|
||||
config MTD_NAND_DISKONCHIP_PROBE_ADVANCED
|
||||
bool "Advanced detection options for DiskOnChip"
|
||||
depends on MTD_NAND_DISKONCHIP
|
||||
help
|
||||
This option allows you to specify nonstandard address at which to
|
||||
probe for a DiskOnChip, or to change the detection options. You
|
||||
are unlikely to need any of this unless you are using LinuxBIOS.
|
||||
Say 'N'.
|
||||
|
||||
config MTD_NAND_DISKONCHIP_PROBE_ADDRESS
|
||||
hex "Physical address of DiskOnChip" if MTD_NAND_DISKONCHIP_PROBE_ADVANCED
|
||||
depends on MTD_NAND_DISKONCHIP
|
||||
default "0"
|
||||
---help---
|
||||
By default, the probe for DiskOnChip devices will look for a
|
||||
DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
|
||||
This option allows you to specify a single address at which to probe
|
||||
for the device, which is useful if you have other devices in that
|
||||
range which get upset when they are probed.
|
||||
|
||||
(Note that on PowerPC, the normal probe will only check at
|
||||
0xE4000000.)
|
||||
|
||||
Normally, you should leave this set to zero, to allow the probe at
|
||||
the normal addresses.
|
||||
|
||||
config MTD_NAND_DISKONCHIP_PROBE_HIGH
|
||||
bool "Probe high addresses"
|
||||
depends on MTD_NAND_DISKONCHIP_PROBE_ADVANCED
|
||||
help
|
||||
By default, the probe for DiskOnChip devices will look for a
|
||||
DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
|
||||
This option changes to make it probe between 0xFFFC8000 and
|
||||
0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be
|
||||
useful to you. Say 'N'.
|
||||
|
||||
config MTD_NAND_DISKONCHIP_BBTWRITE
|
||||
bool "Allow BBT writes on DiskOnChip Millennium and 2000TSOP"
|
||||
depends on MTD_NAND_DISKONCHIP
|
||||
help
|
||||
On DiskOnChip devices shipped with the INFTL filesystem (Millennium
|
||||
and 2000 TSOP/Alon), Linux reserves some space at the end of the
|
||||
device for the Bad Block Table (BBT). If you have existing INFTL
|
||||
data on your device (created by non-Linux tools such as M-Systems'
|
||||
DOS drivers), your data might overlap the area Linux wants to use for
|
||||
the BBT. If this is a concern for you, leave this option disabled and
|
||||
Linux will not write BBT data into this area.
|
||||
The downside of leaving this option disabled is that if bad blocks
|
||||
are detected by Linux, they will not be recorded in the BBT, which
|
||||
could cause future problems.
|
||||
Once you enable this option, new filesystems (INFTL or others, created
|
||||
in Linux or other operating systems) will not use the reserved area.
|
||||
The only reason not to enable this option is to prevent damage to
|
||||
preexisting filesystems.
|
||||
Even if you leave this disabled, you can enable BBT writes at module
|
||||
load time (assuming you build diskonchip as a module) with the module
|
||||
parameter "inftl_bbt_write=1".
|
||||
|
||||
config MTD_NAND_DOCG4
|
||||
tristate "Support for DiskOnChip G4"
|
||||
depends on HAS_IOMEM
|
||||
select BCH
|
||||
select BITREVERSE
|
||||
help
|
||||
Support for diskonchip G4 nand flash, found in various smartphones and
|
||||
PDAs, among them the Palm Treo680, HTC Prophet and Wizard, Toshiba
|
||||
Portege G900, Asus P526, and O2 XDA Zinc.
|
||||
|
||||
With this driver you will be able to use UBI and create a ubifs on the
|
||||
device, so you may wish to consider enabling UBI and UBIFS as well.
|
||||
|
||||
These devices ship with the Mys/Sandisk SAFTL formatting, for which
|
||||
there is currently no mtd parser, so you may want to use command line
|
||||
partitioning to segregate write-protected blocks. On the Treo680, the
|
||||
first five erase blocks (256KiB each) are write-protected, followed
|
||||
by the block containing the saftl partition table. This is probably
|
||||
typical.
|
||||
|
||||
config MTD_NAND_SHARPSL
|
||||
tristate "Support for NAND Flash on Sharp SL Series (C7xx + others)"
|
||||
depends on ARCH_PXA
|
||||
|
||||
config MTD_NAND_CAFE
|
||||
tristate "NAND support for OLPC CAFÉ chip"
|
||||
depends on PCI
|
||||
select REED_SOLOMON
|
||||
select REED_SOLOMON_DEC16
|
||||
help
|
||||
Use NAND flash attached to the CAFÉ chip designed for the OLPC
|
||||
laptop.
|
||||
|
||||
config MTD_NAND_CS553X
|
||||
tristate "NAND support for CS5535/CS5536 (AMD Geode companion chip)"
|
||||
depends on X86_32
|
||||
help
|
||||
The CS553x companion chips for the AMD Geode processor
|
||||
include NAND flash controllers with built-in hardware ECC
|
||||
capabilities; enabling this option will allow you to use
|
||||
these. The driver will check the MSRs to verify that the
|
||||
controller is enabled for NAND, and currently requires that
|
||||
the controller be in MMIO mode.
|
||||
|
||||
If you say "m", the module will be called cs553x_nand.
|
||||
|
||||
config MTD_NAND_ATMEL
|
||||
tristate "Support for NAND Flash / SmartMedia on AT91 and AVR32"
|
||||
depends on ARCH_AT91 || AVR32
|
||||
help
|
||||
Enables support for NAND Flash / Smart Media Card interface
|
||||
on Atmel AT91 and AVR32 processors.
|
||||
|
||||
config MTD_NAND_PXA3xx
|
||||
tristate "NAND support on PXA3xx and Armada 370/XP"
|
||||
depends on PXA3xx || ARCH_MMP || PLAT_ORION
|
||||
help
|
||||
This enables the driver for the NAND flash device found on
|
||||
PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2).
|
||||
|
||||
config MTD_NAND_SLC_LPC32XX
|
||||
tristate "NXP LPC32xx SLC Controller"
|
||||
depends on ARCH_LPC32XX
|
||||
help
|
||||
Enables support for NXP's LPC32XX SLC (i.e. for Single Level Cell
|
||||
chips) NAND controller. This is the default for the PHYTEC 3250
|
||||
reference board which contains a NAND256R3A2CZA6 chip.
|
||||
|
||||
Please check the actual NAND chip connected and its support
|
||||
by the SLC NAND controller.
|
||||
|
||||
config MTD_NAND_MLC_LPC32XX
|
||||
tristate "NXP LPC32xx MLC Controller"
|
||||
depends on ARCH_LPC32XX
|
||||
help
|
||||
Uses the LPC32XX MLC (i.e. for Multi Level Cell chips) NAND
|
||||
controller. This is the default for the WORK92105 controller
|
||||
board.
|
||||
|
||||
Please check the actual NAND chip connected and its support
|
||||
by the MLC NAND controller.
|
||||
|
||||
config MTD_NAND_CM_X270
|
||||
tristate "Support for NAND Flash on CM-X270 modules"
|
||||
depends on MACH_ARMCORE
|
||||
|
||||
config MTD_NAND_PASEMI
|
||||
tristate "NAND support for PA Semi PWRficient"
|
||||
depends on PPC_PASEMI
|
||||
help
|
||||
Enables support for NAND Flash interface on PA Semi PWRficient
|
||||
based boards
|
||||
|
||||
config MTD_NAND_TMIO
|
||||
tristate "NAND Flash device on Toshiba Mobile IO Controller"
|
||||
depends on MFD_TMIO
|
||||
help
|
||||
Support for NAND flash connected to a Toshiba Mobile IO
|
||||
Controller in some PDAs, including the Sharp SL6000x.
|
||||
|
||||
config MTD_NAND_NANDSIM
|
||||
tristate "Support for NAND Flash Simulator"
|
||||
help
|
||||
The simulator may simulate various NAND flash chips for the
|
||||
MTD nand layer.
|
||||
|
||||
config MTD_NAND_GPMI_NAND
|
||||
tristate "GPMI NAND Flash Controller driver"
|
||||
depends on MTD_NAND && MXS_DMA
|
||||
help
|
||||
Enables NAND Flash support for IMX23, IMX28 or IMX6.
|
||||
The GPMI controller is very powerful, with the help of BCH
|
||||
module, it can do the hardware ECC. The GPMI supports several
|
||||
NAND flashs at the same time. The GPMI may conflicts with other
|
||||
block, such as SD card. So pay attention to it when you enable
|
||||
the GPMI.
|
||||
|
||||
config MTD_NAND_BCM47XXNFLASH
|
||||
tristate "Support for NAND flash on BCM4706 BCMA bus"
|
||||
depends on BCMA_NFLASH
|
||||
help
|
||||
BCMA bus can have various flash memories attached, they are
|
||||
registered by bcma as platform devices. This enables driver for
|
||||
NAND flash memories. For now only BCM4706 is supported.
|
||||
|
||||
config MTD_NAND_PLATFORM
|
||||
tristate "Support for generic platform NAND driver"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This implements a generic NAND driver for on-SOC platform
|
||||
devices. You will need to provide platform-specific functions
|
||||
via platform_data.
|
||||
|
||||
config MTD_NAND_ORION
|
||||
tristate "NAND Flash support for Marvell Orion SoC"
|
||||
depends on PLAT_ORION
|
||||
help
|
||||
This enables the NAND flash controller on Orion machines.
|
||||
|
||||
No board specific support is done by this driver, each board
|
||||
must advertise a platform_device for the driver to attach.
|
||||
|
||||
config MTD_NAND_FSL_ELBC
|
||||
tristate "NAND support for Freescale eLBC controllers"
|
||||
depends on PPC_OF
|
||||
select FSL_LBC
|
||||
help
|
||||
Various Freescale chips, including the 8313, include a NAND Flash
|
||||
Controller Module with built-in hardware ECC capabilities.
|
||||
Enabling this option will enable you to use this to control
|
||||
external NAND devices.
|
||||
|
||||
config MTD_NAND_FSL_IFC
|
||||
tristate "NAND support for Freescale IFC controller"
|
||||
depends on MTD_NAND && FSL_SOC
|
||||
select FSL_IFC
|
||||
select MEMORY
|
||||
help
|
||||
Various Freescale chips e.g P1010, include a NAND Flash machine
|
||||
with built-in hardware ECC capabilities.
|
||||
Enabling this option will enable you to use this to control
|
||||
external NAND devices.
|
||||
|
||||
config MTD_NAND_FSL_UPM
|
||||
tristate "Support for NAND on Freescale UPM"
|
||||
depends on PPC_83xx || PPC_85xx
|
||||
select FSL_LBC
|
||||
help
|
||||
Enables support for NAND Flash chips wired onto Freescale PowerPC
|
||||
processor localbus with User-Programmable Machine support.
|
||||
|
||||
config MTD_NAND_MPC5121_NFC
|
||||
tristate "MPC5121 built-in NAND Flash Controller support"
|
||||
depends on PPC_MPC512x
|
||||
help
|
||||
This enables the driver for the NAND flash controller on the
|
||||
MPC5121 SoC.
|
||||
|
||||
config MTD_NAND_MXC
|
||||
tristate "MXC NAND support"
|
||||
depends on ARCH_MXC
|
||||
help
|
||||
This enables the driver for the NAND flash controller on the
|
||||
MXC processors.
|
||||
|
||||
config MTD_NAND_SH_FLCTL
|
||||
tristate "Support for NAND on Renesas SuperH FLCTL"
|
||||
depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
depends on HAS_DMA
|
||||
help
|
||||
Several Renesas SuperH CPU has FLCTL. This option enables support
|
||||
for NAND Flash using FLCTL.
|
||||
|
||||
config MTD_NAND_DAVINCI
|
||||
tristate "Support NAND on DaVinci/Keystone SoC"
|
||||
depends on ARCH_DAVINCI || (ARCH_KEYSTONE && TI_AEMIF)
|
||||
help
|
||||
Enable the driver for NAND flash chips on Texas Instruments
|
||||
DaVinci/Keystone processors.
|
||||
|
||||
config MTD_NAND_TXX9NDFMC
|
||||
tristate "NAND Flash support for TXx9 SoC"
|
||||
depends on SOC_TX4938 || SOC_TX4939
|
||||
help
|
||||
This enables the NAND flash controller on the TXx9 SoCs.
|
||||
|
||||
config MTD_NAND_SOCRATES
|
||||
tristate "Support for NAND on Socrates board"
|
||||
depends on SOCRATES
|
||||
help
|
||||
Enables support for NAND Flash chips wired onto Socrates board.
|
||||
|
||||
config MTD_NAND_NUC900
|
||||
tristate "Support for NAND on Nuvoton NUC9xx/w90p910 evaluation boards."
|
||||
depends on ARCH_W90X900
|
||||
help
|
||||
This enables the driver for the NAND Flash on evaluation board based
|
||||
on w90p910 / NUC9xx.
|
||||
|
||||
config MTD_NAND_JZ4740
|
||||
tristate "Support for JZ4740 SoC NAND controller"
|
||||
depends on MACH_JZ4740
|
||||
help
|
||||
Enables support for NAND Flash on JZ4740 SoC based boards.
|
||||
|
||||
config MTD_NAND_FSMC
|
||||
tristate "Support for NAND on ST Micros FSMC"
|
||||
depends on PLAT_SPEAR || ARCH_NOMADIK || ARCH_U8500 || MACH_U300
|
||||
help
|
||||
Enables support for NAND Flash chips on the ST Microelectronics
|
||||
Flexible Static Memory Controller (FSMC)
|
||||
|
||||
config MTD_NAND_XWAY
|
||||
tristate "Support for NAND on Lantiq XWAY SoC"
|
||||
depends on LANTIQ && SOC_TYPE_XWAY
|
||||
select MTD_NAND_PLATFORM
|
||||
help
|
||||
Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
|
||||
to the External Bus Unit (EBU).
|
||||
|
||||
endif # MTD_NAND
|
||||
54
drivers/mtd/nand/Makefile
Normal file
54
drivers/mtd/nand/Makefile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#
|
||||
# linux/drivers/nand/Makefile
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MTD_NAND) += nand.o
|
||||
obj-$(CONFIG_MTD_NAND_ECC) += nand_ecc.o
|
||||
obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
|
||||
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
|
||||
obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
|
||||
|
||||
obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o
|
||||
obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o
|
||||
obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
|
||||
obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
|
||||
obj-$(CONFIG_MTD_NAND_DAVINCI) += davinci_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
|
||||
obj-$(CONFIG_MTD_NAND_DOCG4) += docg4.o
|
||||
obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o
|
||||
obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o
|
||||
obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o
|
||||
obj-$(CONFIG_MTD_NAND_ATMEL) += atmel_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_GPIO) += gpio.o
|
||||
obj-$(CONFIG_MTD_NAND_OMAP2) += omap2.o
|
||||
obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD) += omap_elm.o
|
||||
obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_TMIO) += tmio_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_IFC) += fsl_ifc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o
|
||||
obj-$(CONFIG_MTD_NAND_SLC_LPC32XX) += lpc32xx_slc.o
|
||||
obj-$(CONFIG_MTD_NAND_MLC_LPC32XX) += lpc32xx_mlc.o
|
||||
obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o
|
||||
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
||||
obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
|
||||
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
|
||||
obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
|
||||
obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
|
||||
|
||||
nand-objs := nand_base.o nand_bbt.o nand_timings.o
|
||||
301
drivers/mtd/nand/ams-delta.c
Normal file
301
drivers/mtd/nand/ams-delta.c
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* drivers/mtd/nand/ams-delta.c
|
||||
*
|
||||
* Copyright (C) 2006 Jonathan McDowell <noodles@earth.li>
|
||||
*
|
||||
* Derived from drivers/mtd/toto.c
|
||||
* Converted to platform driver by Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
|
||||
* Partially stolen from drivers/mtd/nand/plat_nand.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Overview:
|
||||
* This is a device driver for the NAND flash device found on the
|
||||
* Amstrad E3 (Delta).
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_data/gpio-omap.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
|
||||
#include <mach/board-ams-delta.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
|
||||
/*
|
||||
* MTD structure for E3 (Delta)
|
||||
*/
|
||||
static struct mtd_info *ams_delta_mtd = NULL;
|
||||
|
||||
/*
|
||||
* Define partitions for flash devices
|
||||
*/
|
||||
|
||||
static struct mtd_partition partition_info[] = {
|
||||
{ .name = "Kernel",
|
||||
.offset = 0,
|
||||
.size = 3 * SZ_1M + SZ_512K },
|
||||
{ .name = "u-boot",
|
||||
.offset = 3 * SZ_1M + SZ_512K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "u-boot params",
|
||||
.offset = 3 * SZ_1M + SZ_512K + SZ_256K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "Amstrad LDR",
|
||||
.offset = 4 * SZ_1M,
|
||||
.size = SZ_256K },
|
||||
{ .name = "File system",
|
||||
.offset = 4 * SZ_1M + 1 * SZ_256K,
|
||||
.size = 27 * SZ_1M },
|
||||
{ .name = "PBL reserved",
|
||||
.offset = 32 * SZ_1M - 3 * SZ_256K,
|
||||
.size = 3 * SZ_256K },
|
||||
};
|
||||
|
||||
static void ams_delta_write_byte(struct mtd_info *mtd, u_char byte)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *io_base = this->priv;
|
||||
|
||||
writew(0, io_base + OMAP_MPUIO_IO_CNTL);
|
||||
writew(byte, this->IO_ADDR_W);
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 0);
|
||||
ndelay(40);
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 1);
|
||||
}
|
||||
|
||||
static u_char ams_delta_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
u_char res;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *io_base = this->priv;
|
||||
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 0);
|
||||
ndelay(40);
|
||||
writew(~0, io_base + OMAP_MPUIO_IO_CNTL);
|
||||
res = readw(this->IO_ADDR_R);
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void ams_delta_write_buf(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
ams_delta_write_byte(mtd, buf[i]);
|
||||
}
|
||||
|
||||
static void ams_delta_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
buf[i] = ams_delta_read_byte(mtd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Command control function
|
||||
*
|
||||
* ctrl:
|
||||
* NAND_NCE: bit 0 -> bit 2
|
||||
* NAND_CLE: bit 1 -> bit 7
|
||||
* NAND_ALE: bit 2 -> bit 6
|
||||
*/
|
||||
static void ams_delta_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NCE,
|
||||
(ctrl & NAND_NCE) == 0);
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_CLE,
|
||||
(ctrl & NAND_CLE) != 0);
|
||||
gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_ALE,
|
||||
(ctrl & NAND_ALE) != 0);
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
ams_delta_write_byte(mtd, cmd);
|
||||
}
|
||||
|
||||
static int ams_delta_nand_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return gpio_get_value(AMS_DELTA_GPIO_PIN_NAND_RB);
|
||||
}
|
||||
|
||||
static const struct gpio _mandatory_gpio[] = {
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_NCE,
|
||||
.flags = GPIOF_OUT_INIT_HIGH,
|
||||
.label = "nand_nce",
|
||||
},
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_NRE,
|
||||
.flags = GPIOF_OUT_INIT_HIGH,
|
||||
.label = "nand_nre",
|
||||
},
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_NWP,
|
||||
.flags = GPIOF_OUT_INIT_HIGH,
|
||||
.label = "nand_nwp",
|
||||
},
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_NWE,
|
||||
.flags = GPIOF_OUT_INIT_HIGH,
|
||||
.label = "nand_nwe",
|
||||
},
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_ALE,
|
||||
.flags = GPIOF_OUT_INIT_LOW,
|
||||
.label = "nand_ale",
|
||||
},
|
||||
{
|
||||
.gpio = AMS_DELTA_GPIO_PIN_NAND_CLE,
|
||||
.flags = GPIOF_OUT_INIT_LOW,
|
||||
.label = "nand_cle",
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Main initialization routine
|
||||
*/
|
||||
static int ams_delta_init(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
void __iomem *io_base;
|
||||
int err = 0;
|
||||
|
||||
if (!res)
|
||||
return -ENXIO;
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
ams_delta_mtd = kmalloc(sizeof(struct mtd_info) +
|
||||
sizeof(struct nand_chip), GFP_KERNEL);
|
||||
if (!ams_delta_mtd) {
|
||||
printk (KERN_WARNING "Unable to allocate E3 NAND MTD device structure.\n");
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ams_delta_mtd->owner = THIS_MODULE;
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *) (&ams_delta_mtd[1]);
|
||||
|
||||
/* Initialize structures */
|
||||
memset(ams_delta_mtd, 0, sizeof(struct mtd_info));
|
||||
memset(this, 0, sizeof(struct nand_chip));
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
ams_delta_mtd->priv = this;
|
||||
|
||||
/*
|
||||
* Don't try to request the memory region from here,
|
||||
* it should have been already requested from the
|
||||
* gpio-omap driver and requesting it again would fail.
|
||||
*/
|
||||
|
||||
io_base = ioremap(res->start, resource_size(res));
|
||||
if (io_base == NULL) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
err = -EIO;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
this->priv = io_base;
|
||||
|
||||
/* Set address of NAND IO lines */
|
||||
this->IO_ADDR_R = io_base + OMAP_MPUIO_INPUT_LATCH;
|
||||
this->IO_ADDR_W = io_base + OMAP_MPUIO_OUTPUT;
|
||||
this->read_byte = ams_delta_read_byte;
|
||||
this->write_buf = ams_delta_write_buf;
|
||||
this->read_buf = ams_delta_read_buf;
|
||||
this->cmd_ctrl = ams_delta_hwcontrol;
|
||||
if (gpio_request(AMS_DELTA_GPIO_PIN_NAND_RB, "nand_rdy") == 0) {
|
||||
this->dev_ready = ams_delta_nand_ready;
|
||||
} else {
|
||||
this->dev_ready = NULL;
|
||||
printk(KERN_NOTICE "Couldn't request gpio for Delta NAND ready.\n");
|
||||
}
|
||||
/* 25 us command delay time */
|
||||
this->chip_delay = 30;
|
||||
this->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
platform_set_drvdata(pdev, io_base);
|
||||
|
||||
/* Set chip enabled, but */
|
||||
err = gpio_request_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio));
|
||||
if (err)
|
||||
goto out_gpio;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan(ams_delta_mtd, 1)) {
|
||||
err = -ENXIO;
|
||||
goto out_mtd;
|
||||
}
|
||||
|
||||
/* Register the partitions */
|
||||
mtd_device_register(ams_delta_mtd, partition_info,
|
||||
ARRAY_SIZE(partition_info));
|
||||
|
||||
goto out;
|
||||
|
||||
out_mtd:
|
||||
gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio));
|
||||
out_gpio:
|
||||
gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB);
|
||||
iounmap(io_base);
|
||||
out_free:
|
||||
kfree(ams_delta_mtd);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up routine
|
||||
*/
|
||||
static int ams_delta_cleanup(struct platform_device *pdev)
|
||||
{
|
||||
void __iomem *io_base = platform_get_drvdata(pdev);
|
||||
|
||||
/* Release resources, unregister device */
|
||||
nand_release(ams_delta_mtd);
|
||||
|
||||
gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio));
|
||||
gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB);
|
||||
iounmap(io_base);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(ams_delta_mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ams_delta_nand_driver = {
|
||||
.probe = ams_delta_init,
|
||||
.remove = ams_delta_cleanup,
|
||||
.driver = {
|
||||
.name = "ams-delta-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ams_delta_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>");
|
||||
MODULE_DESCRIPTION("Glue layer for NAND flash on Amstrad E3 (Delta)");
|
||||
2337
drivers/mtd/nand/atmel_nand.c
Normal file
2337
drivers/mtd/nand/atmel_nand.c
Normal file
File diff suppressed because it is too large
Load diff
151
drivers/mtd/nand/atmel_nand_ecc.h
Normal file
151
drivers/mtd/nand/atmel_nand_ecc.h
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Error Corrected Code Controller (ECC) - System peripherals regsters.
|
||||
* Based on AT91SAM9260 datasheet revision B.
|
||||
*
|
||||
* Copyright (C) 2007 Andrew Victor
|
||||
* Copyright (C) 2007 - 2012 Atmel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef ATMEL_NAND_ECC_H
|
||||
#define ATMEL_NAND_ECC_H
|
||||
|
||||
#define ATMEL_ECC_CR 0x00 /* Control register */
|
||||
#define ATMEL_ECC_RST (1 << 0) /* Reset parity */
|
||||
|
||||
#define ATMEL_ECC_MR 0x04 /* Mode register */
|
||||
#define ATMEL_ECC_PAGESIZE (3 << 0) /* Page Size */
|
||||
#define ATMEL_ECC_PAGESIZE_528 (0)
|
||||
#define ATMEL_ECC_PAGESIZE_1056 (1)
|
||||
#define ATMEL_ECC_PAGESIZE_2112 (2)
|
||||
#define ATMEL_ECC_PAGESIZE_4224 (3)
|
||||
|
||||
#define ATMEL_ECC_SR 0x08 /* Status register */
|
||||
#define ATMEL_ECC_RECERR (1 << 0) /* Recoverable Error */
|
||||
#define ATMEL_ECC_ECCERR (1 << 1) /* ECC Single Bit Error */
|
||||
#define ATMEL_ECC_MULERR (1 << 2) /* Multiple Errors */
|
||||
|
||||
#define ATMEL_ECC_PR 0x0c /* Parity register */
|
||||
#define ATMEL_ECC_BITADDR (0xf << 0) /* Bit Error Address */
|
||||
#define ATMEL_ECC_WORDADDR (0xfff << 4) /* Word Error Address */
|
||||
|
||||
#define ATMEL_ECC_NPR 0x10 /* NParity register */
|
||||
#define ATMEL_ECC_NPARITY (0xffff << 0) /* NParity */
|
||||
|
||||
/* PMECC Register Definitions */
|
||||
#define ATMEL_PMECC_CFG 0x000 /* Configuration Register */
|
||||
#define PMECC_CFG_BCH_ERR2 (0 << 0)
|
||||
#define PMECC_CFG_BCH_ERR4 (1 << 0)
|
||||
#define PMECC_CFG_BCH_ERR8 (2 << 0)
|
||||
#define PMECC_CFG_BCH_ERR12 (3 << 0)
|
||||
#define PMECC_CFG_BCH_ERR24 (4 << 0)
|
||||
|
||||
#define PMECC_CFG_SECTOR512 (0 << 4)
|
||||
#define PMECC_CFG_SECTOR1024 (1 << 4)
|
||||
|
||||
#define PMECC_CFG_PAGE_1SECTOR (0 << 8)
|
||||
#define PMECC_CFG_PAGE_2SECTORS (1 << 8)
|
||||
#define PMECC_CFG_PAGE_4SECTORS (2 << 8)
|
||||
#define PMECC_CFG_PAGE_8SECTORS (3 << 8)
|
||||
|
||||
#define PMECC_CFG_READ_OP (0 << 12)
|
||||
#define PMECC_CFG_WRITE_OP (1 << 12)
|
||||
|
||||
#define PMECC_CFG_SPARE_ENABLE (1 << 16)
|
||||
#define PMECC_CFG_SPARE_DISABLE (0 << 16)
|
||||
|
||||
#define PMECC_CFG_AUTO_ENABLE (1 << 20)
|
||||
#define PMECC_CFG_AUTO_DISABLE (0 << 20)
|
||||
|
||||
#define ATMEL_PMECC_SAREA 0x004 /* Spare area size */
|
||||
#define ATMEL_PMECC_SADDR 0x008 /* PMECC starting address */
|
||||
#define ATMEL_PMECC_EADDR 0x00c /* PMECC ending address */
|
||||
#define ATMEL_PMECC_CLK 0x010 /* PMECC clock control */
|
||||
#define PMECC_CLK_133MHZ (2 << 0)
|
||||
|
||||
#define ATMEL_PMECC_CTRL 0x014 /* PMECC control register */
|
||||
#define PMECC_CTRL_RST (1 << 0)
|
||||
#define PMECC_CTRL_DATA (1 << 1)
|
||||
#define PMECC_CTRL_USER (1 << 2)
|
||||
#define PMECC_CTRL_ENABLE (1 << 4)
|
||||
#define PMECC_CTRL_DISABLE (1 << 5)
|
||||
|
||||
#define ATMEL_PMECC_SR 0x018 /* PMECC status register */
|
||||
#define PMECC_SR_BUSY (1 << 0)
|
||||
#define PMECC_SR_ENABLE (1 << 4)
|
||||
|
||||
#define ATMEL_PMECC_IER 0x01c /* PMECC interrupt enable */
|
||||
#define PMECC_IER_ENABLE (1 << 0)
|
||||
#define ATMEL_PMECC_IDR 0x020 /* PMECC interrupt disable */
|
||||
#define PMECC_IER_DISABLE (1 << 0)
|
||||
#define ATMEL_PMECC_IMR 0x024 /* PMECC interrupt mask */
|
||||
#define PMECC_IER_MASK (1 << 0)
|
||||
#define ATMEL_PMECC_ISR 0x028 /* PMECC interrupt status */
|
||||
#define ATMEL_PMECC_ECCx 0x040 /* PMECC ECC x */
|
||||
#define ATMEL_PMECC_REMx 0x240 /* PMECC REM x */
|
||||
|
||||
/* PMERRLOC Register Definitions */
|
||||
#define ATMEL_PMERRLOC_ELCFG 0x000 /* Error location config */
|
||||
#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0)
|
||||
#define PMERRLOC_ELCFG_SECTOR_1024 (1 << 0)
|
||||
#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16)
|
||||
|
||||
#define ATMEL_PMERRLOC_ELPRIM 0x004 /* Error location primitive */
|
||||
#define ATMEL_PMERRLOC_ELEN 0x008 /* Error location enable */
|
||||
#define ATMEL_PMERRLOC_ELDIS 0x00c /* Error location disable */
|
||||
#define PMERRLOC_DISABLE (1 << 0)
|
||||
|
||||
#define ATMEL_PMERRLOC_ELSR 0x010 /* Error location status */
|
||||
#define PMERRLOC_ELSR_BUSY (1 << 0)
|
||||
#define ATMEL_PMERRLOC_ELIER 0x014 /* Error location int enable */
|
||||
#define ATMEL_PMERRLOC_ELIDR 0x018 /* Error location int disable */
|
||||
#define ATMEL_PMERRLOC_ELIMR 0x01c /* Error location int mask */
|
||||
#define ATMEL_PMERRLOC_ELISR 0x020 /* Error location int status */
|
||||
#define PMERRLOC_ERR_NUM_MASK (0x1f << 8)
|
||||
#define PMERRLOC_CALC_DONE (1 << 0)
|
||||
#define ATMEL_PMERRLOC_SIGMAx 0x028 /* Error location SIGMA x */
|
||||
#define ATMEL_PMERRLOC_ELx 0x08c /* Error location x */
|
||||
|
||||
/* Register access macros for PMECC */
|
||||
#define pmecc_readl_relaxed(addr, reg) \
|
||||
readl_relaxed((addr) + ATMEL_PMECC_##reg)
|
||||
|
||||
#define pmecc_writel(addr, reg, value) \
|
||||
writel((value), (addr) + ATMEL_PMECC_##reg)
|
||||
|
||||
#define pmecc_readb_ecc_relaxed(addr, sector, n) \
|
||||
readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
|
||||
|
||||
#define pmecc_readl_rem_relaxed(addr, sector, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_relaxed(addr, reg) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
|
||||
|
||||
#define pmerrloc_writel(addr, reg, value) \
|
||||
writel((value), (addr) + ATMEL_PMERRLOC_##reg)
|
||||
|
||||
#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
|
||||
writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_sigma_relaxed(addr, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_el_relaxed(addr, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
|
||||
|
||||
/* Galois field dimension */
|
||||
#define PMECC_GF_DIMENSION_13 13
|
||||
#define PMECC_GF_DIMENSION_14 14
|
||||
|
||||
#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000
|
||||
#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000
|
||||
|
||||
/* Time out value for reading PMECC status register */
|
||||
#define PMECC_MAX_TIMEOUT_MS 100
|
||||
|
||||
#endif
|
||||
102
drivers/mtd/nand/atmel_nand_nfc.h
Normal file
102
drivers/mtd/nand/atmel_nand_nfc.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Atmel Nand Flash Controller (NFC) - System peripherals regsters.
|
||||
* Based on SAMA5D3 datasheet.
|
||||
*
|
||||
* © Copyright 2013 Atmel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef ATMEL_NAND_NFC_H
|
||||
#define ATMEL_NAND_NFC_H
|
||||
|
||||
/*
|
||||
* HSMC NFC registers
|
||||
*/
|
||||
#define ATMEL_HSMC_NFC_CFG 0x00 /* NFC Configuration Register */
|
||||
#define NFC_CFG_PAGESIZE (7 << 0)
|
||||
#define NFC_CFG_PAGESIZE_512 (0 << 0)
|
||||
#define NFC_CFG_PAGESIZE_1024 (1 << 0)
|
||||
#define NFC_CFG_PAGESIZE_2048 (2 << 0)
|
||||
#define NFC_CFG_PAGESIZE_4096 (3 << 0)
|
||||
#define NFC_CFG_PAGESIZE_8192 (4 << 0)
|
||||
#define NFC_CFG_WSPARE (1 << 8)
|
||||
#define NFC_CFG_RSPARE (1 << 9)
|
||||
#define NFC_CFG_NFC_DTOCYC (0xf << 16)
|
||||
#define NFC_CFG_NFC_DTOMUL (0x7 << 20)
|
||||
#define NFC_CFG_NFC_SPARESIZE (0x7f << 24)
|
||||
#define NFC_CFG_NFC_SPARESIZE_BIT_POS 24
|
||||
|
||||
#define ATMEL_HSMC_NFC_CTRL 0x04 /* NFC Control Register */
|
||||
#define NFC_CTRL_ENABLE (1 << 0)
|
||||
#define NFC_CTRL_DISABLE (1 << 1)
|
||||
|
||||
#define ATMEL_HSMC_NFC_SR 0x08 /* NFC Status Register */
|
||||
#define NFC_SR_XFR_DONE (1 << 16)
|
||||
#define NFC_SR_CMD_DONE (1 << 17)
|
||||
#define NFC_SR_DTOE (1 << 20)
|
||||
#define NFC_SR_UNDEF (1 << 21)
|
||||
#define NFC_SR_AWB (1 << 22)
|
||||
#define NFC_SR_ASE (1 << 23)
|
||||
#define NFC_SR_RB_EDGE (1 << 24)
|
||||
|
||||
#define ATMEL_HSMC_NFC_IER 0x0c
|
||||
#define ATMEL_HSMC_NFC_IDR 0x10
|
||||
#define ATMEL_HSMC_NFC_IMR 0x14
|
||||
#define ATMEL_HSMC_NFC_CYCLE0 0x18 /* NFC Address Cycle Zero */
|
||||
#define ATMEL_HSMC_NFC_ADDR_CYCLE0 (0xff)
|
||||
|
||||
#define ATMEL_HSMC_NFC_BANK 0x1c /* NFC Bank Register */
|
||||
#define ATMEL_HSMC_NFC_BANK0 (0 << 0)
|
||||
#define ATMEL_HSMC_NFC_BANK1 (1 << 0)
|
||||
|
||||
#define nfc_writel(addr, reg, value) \
|
||||
writel((value), (addr) + ATMEL_HSMC_NFC_##reg)
|
||||
|
||||
#define nfc_readl(addr, reg) \
|
||||
readl_relaxed((addr) + ATMEL_HSMC_NFC_##reg)
|
||||
|
||||
/*
|
||||
* NFC Address Command definitions
|
||||
*/
|
||||
#define NFCADDR_CMD_CMD1 (0xff << 2) /* Command for Cycle 1 */
|
||||
#define NFCADDR_CMD_CMD1_BIT_POS 2
|
||||
#define NFCADDR_CMD_CMD2 (0xff << 10) /* Command for Cycle 2 */
|
||||
#define NFCADDR_CMD_CMD2_BIT_POS 10
|
||||
#define NFCADDR_CMD_VCMD2 (0x1 << 18) /* Valid Cycle 2 Command */
|
||||
#define NFCADDR_CMD_ACYCLE (0x7 << 19) /* Number of Address required */
|
||||
#define NFCADDR_CMD_ACYCLE_NONE (0x0 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_1 (0x1 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_2 (0x2 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_3 (0x3 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_4 (0x4 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_5 (0x5 << 19)
|
||||
#define NFCADDR_CMD_ACYCLE_BIT_POS 19
|
||||
#define NFCADDR_CMD_CSID (0x7 << 22) /* Chip Select Identifier */
|
||||
#define NFCADDR_CMD_CSID_0 (0x0 << 22)
|
||||
#define NFCADDR_CMD_CSID_1 (0x1 << 22)
|
||||
#define NFCADDR_CMD_CSID_2 (0x2 << 22)
|
||||
#define NFCADDR_CMD_CSID_3 (0x3 << 22)
|
||||
#define NFCADDR_CMD_CSID_4 (0x4 << 22)
|
||||
#define NFCADDR_CMD_CSID_5 (0x5 << 22)
|
||||
#define NFCADDR_CMD_CSID_6 (0x6 << 22)
|
||||
#define NFCADDR_CMD_CSID_7 (0x7 << 22)
|
||||
#define NFCADDR_CMD_DATAEN (0x1 << 25) /* Data Transfer Enable */
|
||||
#define NFCADDR_CMD_DATADIS (0x0 << 25) /* Data Transfer Disable */
|
||||
#define NFCADDR_CMD_NFCRD (0x0 << 26) /* NFC Read Enable */
|
||||
#define NFCADDR_CMD_NFCWR (0x1 << 26) /* NFC Write Enable */
|
||||
#define NFCADDR_CMD_NFCBUSY (0x1 << 27) /* NFC Busy */
|
||||
|
||||
#define nfc_cmd_addr1234_writel(cmd, addr1234, nfc_base) \
|
||||
writel((addr1234), (cmd) + nfc_base)
|
||||
|
||||
#define nfc_cmd_readl(bitstatus, nfc_base) \
|
||||
readl_relaxed((bitstatus) + nfc_base)
|
||||
|
||||
#define NFC_TIME_OUT_MS 100
|
||||
#define NFC_SRAM_BANK1_OFFSET 0x1200
|
||||
|
||||
#endif
|
||||
516
drivers/mtd/nand/au1550nd.c
Normal file
516
drivers/mtd/nand/au1550nd.c
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
/*
|
||||
* drivers/mtd/nand/au1550nd.c
|
||||
*
|
||||
* Copyright (C) 2004 Embedded Edge, LLC
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/mach-au1x00/au1000.h>
|
||||
#include <asm/mach-au1x00/au1550nd.h>
|
||||
|
||||
|
||||
struct au1550nd_ctx {
|
||||
struct mtd_info info;
|
||||
struct nand_chip chip;
|
||||
|
||||
int cs;
|
||||
void __iomem *base;
|
||||
void (*write_byte)(struct mtd_info *, u_char);
|
||||
};
|
||||
|
||||
/**
|
||||
* au_read_byte - read one byte from the chip
|
||||
* @mtd: MTD device structure
|
||||
*
|
||||
* read function for 8bit buswidth
|
||||
*/
|
||||
static u_char au_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u_char ret = readb(this->IO_ADDR_R);
|
||||
wmb(); /* drain writebuffer */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* au_write_byte - write one byte to the chip
|
||||
* @mtd: MTD device structure
|
||||
* @byte: pointer to data byte to write
|
||||
*
|
||||
* write function for 8it buswidth
|
||||
*/
|
||||
static void au_write_byte(struct mtd_info *mtd, u_char byte)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
writeb(byte, this->IO_ADDR_W);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
|
||||
/**
|
||||
* au_read_byte16 - read one byte endianness aware from the chip
|
||||
* @mtd: MTD device structure
|
||||
*
|
||||
* read function for 16bit buswidth with endianness conversion
|
||||
*/
|
||||
static u_char au_read_byte16(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u_char ret = (u_char) cpu_to_le16(readw(this->IO_ADDR_R));
|
||||
wmb(); /* drain writebuffer */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* au_write_byte16 - write one byte endianness aware to the chip
|
||||
* @mtd: MTD device structure
|
||||
* @byte: pointer to data byte to write
|
||||
*
|
||||
* write function for 16bit buswidth with endianness conversion
|
||||
*/
|
||||
static void au_write_byte16(struct mtd_info *mtd, u_char byte)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
writew(le16_to_cpu((u16) byte), this->IO_ADDR_W);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
|
||||
/**
|
||||
* au_read_word - read one word from the chip
|
||||
* @mtd: MTD device structure
|
||||
*
|
||||
* read function for 16bit buswidth without endianness conversion
|
||||
*/
|
||||
static u16 au_read_word(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u16 ret = readw(this->IO_ADDR_R);
|
||||
wmb(); /* drain writebuffer */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* au_write_buf - write buffer to chip
|
||||
* @mtd: MTD device structure
|
||||
* @buf: data buffer
|
||||
* @len: number of bytes to write
|
||||
*
|
||||
* write function for 8bit buswidth
|
||||
*/
|
||||
static void au_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
writeb(buf[i], this->IO_ADDR_W);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* au_read_buf - read chip data into buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer to store date
|
||||
* @len: number of bytes to read
|
||||
*
|
||||
* read function for 8bit buswidth
|
||||
*/
|
||||
static void au_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[i] = readb(this->IO_ADDR_R);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* au_write_buf16 - write buffer to chip
|
||||
* @mtd: MTD device structure
|
||||
* @buf: data buffer
|
||||
* @len: number of bytes to write
|
||||
*
|
||||
* write function for 16bit buswidth
|
||||
*/
|
||||
static void au_write_buf16(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
writew(p[i], this->IO_ADDR_W);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* au_read_buf16 - read chip data into buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer to store date
|
||||
* @len: number of bytes to read
|
||||
*
|
||||
* read function for 16bit buswidth
|
||||
*/
|
||||
static void au_read_buf16(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
p[i] = readw(this->IO_ADDR_R);
|
||||
wmb(); /* drain writebuffer */
|
||||
}
|
||||
}
|
||||
|
||||
/* Select the chip by setting nCE to low */
|
||||
#define NAND_CTL_SETNCE 1
|
||||
/* Deselect the chip by setting nCE to high */
|
||||
#define NAND_CTL_CLRNCE 2
|
||||
/* Select the command latch by setting CLE to high */
|
||||
#define NAND_CTL_SETCLE 3
|
||||
/* Deselect the command latch by setting CLE to low */
|
||||
#define NAND_CTL_CLRCLE 4
|
||||
/* Select the address latch by setting ALE to high */
|
||||
#define NAND_CTL_SETALE 5
|
||||
/* Deselect the address latch by setting ALE to low */
|
||||
#define NAND_CTL_CLRALE 6
|
||||
|
||||
static void au1550_hwcontrol(struct mtd_info *mtd, int cmd)
|
||||
{
|
||||
struct au1550nd_ctx *ctx = container_of(mtd, struct au1550nd_ctx, info);
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
switch (cmd) {
|
||||
|
||||
case NAND_CTL_SETCLE:
|
||||
this->IO_ADDR_W = ctx->base + MEM_STNAND_CMD;
|
||||
break;
|
||||
|
||||
case NAND_CTL_CLRCLE:
|
||||
this->IO_ADDR_W = ctx->base + MEM_STNAND_DATA;
|
||||
break;
|
||||
|
||||
case NAND_CTL_SETALE:
|
||||
this->IO_ADDR_W = ctx->base + MEM_STNAND_ADDR;
|
||||
break;
|
||||
|
||||
case NAND_CTL_CLRALE:
|
||||
this->IO_ADDR_W = ctx->base + MEM_STNAND_DATA;
|
||||
/* FIXME: Nobody knows why this is necessary,
|
||||
* but it works only that way */
|
||||
udelay(1);
|
||||
break;
|
||||
|
||||
case NAND_CTL_SETNCE:
|
||||
/* assert (force assert) chip enable */
|
||||
alchemy_wrsmem((1 << (4 + ctx->cs)), AU1000_MEM_STNDCTL);
|
||||
break;
|
||||
|
||||
case NAND_CTL_CLRNCE:
|
||||
/* deassert chip enable */
|
||||
alchemy_wrsmem(0, AU1000_MEM_STNDCTL);
|
||||
break;
|
||||
}
|
||||
|
||||
this->IO_ADDR_R = this->IO_ADDR_W;
|
||||
|
||||
wmb(); /* Drain the writebuffer */
|
||||
}
|
||||
|
||||
int au1550_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return (alchemy_rdsmem(AU1000_MEM_STSTAT) & 0x1) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* au1550_select_chip - control -CE line
|
||||
* Forbid driving -CE manually permitting the NAND controller to do this.
|
||||
* Keeping -CE asserted during the whole sector reads interferes with the
|
||||
* NOR flash and PCMCIA drivers as it causes contention on the static bus.
|
||||
* We only have to hold -CE low for the NAND read commands since the flash
|
||||
* chip needs it to be asserted during chip not ready time but the NAND
|
||||
* controller keeps it released.
|
||||
*
|
||||
* @mtd: MTD device structure
|
||||
* @chip: chipnumber to select, -1 for deselect
|
||||
*/
|
||||
static void au1550_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* au1550_command - Send command to NAND device
|
||||
* @mtd: MTD device structure
|
||||
* @command: the command to be sent
|
||||
* @column: the column address for this command, -1 if none
|
||||
* @page_addr: the page address for this command, -1 if none
|
||||
*/
|
||||
static void au1550_command(struct mtd_info *mtd, unsigned command, int column, int page_addr)
|
||||
{
|
||||
struct au1550nd_ctx *ctx = container_of(mtd, struct au1550nd_ctx, info);
|
||||
struct nand_chip *this = mtd->priv;
|
||||
int ce_override = 0, i;
|
||||
unsigned long flags = 0;
|
||||
|
||||
/* Begin command latch cycle */
|
||||
au1550_hwcontrol(mtd, NAND_CTL_SETCLE);
|
||||
/*
|
||||
* Write out the command to the device.
|
||||
*/
|
||||
if (command == NAND_CMD_SEQIN) {
|
||||
int readcmd;
|
||||
|
||||
if (column >= mtd->writesize) {
|
||||
/* OOB area */
|
||||
column -= mtd->writesize;
|
||||
readcmd = NAND_CMD_READOOB;
|
||||
} else if (column < 256) {
|
||||
/* First 256 bytes --> READ0 */
|
||||
readcmd = NAND_CMD_READ0;
|
||||
} else {
|
||||
column -= 256;
|
||||
readcmd = NAND_CMD_READ1;
|
||||
}
|
||||
ctx->write_byte(mtd, readcmd);
|
||||
}
|
||||
ctx->write_byte(mtd, command);
|
||||
|
||||
/* Set ALE and clear CLE to start address cycle */
|
||||
au1550_hwcontrol(mtd, NAND_CTL_CLRCLE);
|
||||
|
||||
if (column != -1 || page_addr != -1) {
|
||||
au1550_hwcontrol(mtd, NAND_CTL_SETALE);
|
||||
|
||||
/* Serially input address */
|
||||
if (column != -1) {
|
||||
/* Adjust columns for 16 bit buswidth */
|
||||
if (this->options & NAND_BUSWIDTH_16 &&
|
||||
!nand_opcode_8bits(command))
|
||||
column >>= 1;
|
||||
ctx->write_byte(mtd, column);
|
||||
}
|
||||
if (page_addr != -1) {
|
||||
ctx->write_byte(mtd, (u8)(page_addr & 0xff));
|
||||
|
||||
if (command == NAND_CMD_READ0 ||
|
||||
command == NAND_CMD_READ1 ||
|
||||
command == NAND_CMD_READOOB) {
|
||||
/*
|
||||
* NAND controller will release -CE after
|
||||
* the last address byte is written, so we'll
|
||||
* have to forcibly assert it. No interrupts
|
||||
* are allowed while we do this as we don't
|
||||
* want the NOR flash or PCMCIA drivers to
|
||||
* steal our precious bytes of data...
|
||||
*/
|
||||
ce_override = 1;
|
||||
local_irq_save(flags);
|
||||
au1550_hwcontrol(mtd, NAND_CTL_SETNCE);
|
||||
}
|
||||
|
||||
ctx->write_byte(mtd, (u8)(page_addr >> 8));
|
||||
|
||||
/* One more address cycle for devices > 32MiB */
|
||||
if (this->chipsize > (32 << 20))
|
||||
ctx->write_byte(mtd,
|
||||
((page_addr >> 16) & 0x0f));
|
||||
}
|
||||
/* Latch in address */
|
||||
au1550_hwcontrol(mtd, NAND_CTL_CLRALE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Program and erase have their own busy handlers.
|
||||
* Status and sequential in need no delay.
|
||||
*/
|
||||
switch (command) {
|
||||
|
||||
case NAND_CMD_PAGEPROG:
|
||||
case NAND_CMD_ERASE1:
|
||||
case NAND_CMD_ERASE2:
|
||||
case NAND_CMD_SEQIN:
|
||||
case NAND_CMD_STATUS:
|
||||
return;
|
||||
|
||||
case NAND_CMD_RESET:
|
||||
break;
|
||||
|
||||
case NAND_CMD_READ0:
|
||||
case NAND_CMD_READ1:
|
||||
case NAND_CMD_READOOB:
|
||||
/* Check if we're really driving -CE low (just in case) */
|
||||
if (unlikely(!ce_override))
|
||||
break;
|
||||
|
||||
/* Apply a short delay always to ensure that we do wait tWB. */
|
||||
ndelay(100);
|
||||
/* Wait for a chip to become ready... */
|
||||
for (i = this->chip_delay; !this->dev_ready(mtd) && i > 0; --i)
|
||||
udelay(1);
|
||||
|
||||
/* Release -CE and re-enable interrupts. */
|
||||
au1550_hwcontrol(mtd, NAND_CTL_CLRNCE);
|
||||
local_irq_restore(flags);
|
||||
return;
|
||||
}
|
||||
/* Apply this short delay always to ensure that we do wait tWB. */
|
||||
ndelay(100);
|
||||
|
||||
while(!this->dev_ready(mtd));
|
||||
}
|
||||
|
||||
static int find_nand_cs(unsigned long nand_base)
|
||||
{
|
||||
void __iomem *base =
|
||||
(void __iomem *)KSEG1ADDR(AU1000_STATIC_MEM_PHYS_ADDR);
|
||||
unsigned long addr, staddr, start, mask, end;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
addr = 0x1000 + (i * 0x10); /* CSx */
|
||||
staddr = __raw_readl(base + addr + 0x08); /* STADDRx */
|
||||
/* figure out the decoded range of this CS */
|
||||
start = (staddr << 4) & 0xfffc0000;
|
||||
mask = (staddr << 18) & 0xfffc0000;
|
||||
end = (start | (start - 1)) & ~(start ^ mask);
|
||||
if ((nand_base >= start) && (nand_base < end))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int au1550nd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct au1550nd_platdata *pd;
|
||||
struct au1550nd_ctx *ctx;
|
||||
struct nand_chip *this;
|
||||
struct resource *r;
|
||||
int ret, cs;
|
||||
|
||||
pd = dev_get_platdata(&pdev->dev);
|
||||
if (!pd) {
|
||||
dev_err(&pdev->dev, "missing platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "no NAND memory resource\n");
|
||||
ret = -ENODEV;
|
||||
goto out1;
|
||||
}
|
||||
if (request_mem_region(r->start, resource_size(r), "au1550-nand")) {
|
||||
dev_err(&pdev->dev, "cannot claim NAND memory area\n");
|
||||
ret = -ENOMEM;
|
||||
goto out1;
|
||||
}
|
||||
|
||||
ctx->base = ioremap_nocache(r->start, 0x1000);
|
||||
if (!ctx->base) {
|
||||
dev_err(&pdev->dev, "cannot remap NAND memory area\n");
|
||||
ret = -ENODEV;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
this = &ctx->chip;
|
||||
ctx->info.priv = this;
|
||||
ctx->info.owner = THIS_MODULE;
|
||||
|
||||
/* figure out which CS# r->start belongs to */
|
||||
cs = find_nand_cs(r->start);
|
||||
if (cs < 0) {
|
||||
dev_err(&pdev->dev, "cannot detect NAND chipselect\n");
|
||||
ret = -ENODEV;
|
||||
goto out3;
|
||||
}
|
||||
ctx->cs = cs;
|
||||
|
||||
this->dev_ready = au1550_device_ready;
|
||||
this->select_chip = au1550_select_chip;
|
||||
this->cmdfunc = au1550_command;
|
||||
|
||||
/* 30 us command delay time */
|
||||
this->chip_delay = 30;
|
||||
this->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
if (pd->devwidth)
|
||||
this->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
this->read_byte = (pd->devwidth) ? au_read_byte16 : au_read_byte;
|
||||
ctx->write_byte = (pd->devwidth) ? au_write_byte16 : au_write_byte;
|
||||
this->read_word = au_read_word;
|
||||
this->write_buf = (pd->devwidth) ? au_write_buf16 : au_write_buf;
|
||||
this->read_buf = (pd->devwidth) ? au_read_buf16 : au_read_buf;
|
||||
|
||||
ret = nand_scan(&ctx->info, 1);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "NAND scan failed with %d\n", ret);
|
||||
goto out3;
|
||||
}
|
||||
|
||||
mtd_device_register(&ctx->info, pd->parts, pd->num_parts);
|
||||
|
||||
platform_set_drvdata(pdev, ctx);
|
||||
|
||||
return 0;
|
||||
|
||||
out3:
|
||||
iounmap(ctx->base);
|
||||
out2:
|
||||
release_mem_region(r->start, resource_size(r));
|
||||
out1:
|
||||
kfree(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int au1550nd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct au1550nd_ctx *ctx = platform_get_drvdata(pdev);
|
||||
struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
nand_release(&ctx->info);
|
||||
iounmap(ctx->base);
|
||||
release_mem_region(r->start, 0x1000);
|
||||
kfree(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver au1550nd_driver = {
|
||||
.driver = {
|
||||
.name = "au1550-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = au1550nd_probe,
|
||||
.remove = au1550nd_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(au1550nd_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Embedded Edge, LLC");
|
||||
MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on Pb1550 board");
|
||||
4
drivers/mtd/nand/bcm47xxnflash/Makefile
Normal file
4
drivers/mtd/nand/bcm47xxnflash/Makefile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
bcm47xxnflash-y += main.o
|
||||
bcm47xxnflash-y += ops_bcm4706.o
|
||||
|
||||
obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash.o
|
||||
26
drivers/mtd/nand/bcm47xxnflash/bcm47xxnflash.h
Normal file
26
drivers/mtd/nand/bcm47xxnflash/bcm47xxnflash.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef __BCM47XXNFLASH_H
|
||||
#define __BCM47XXNFLASH_H
|
||||
|
||||
#ifndef pr_fmt
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
#endif
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
struct bcm47xxnflash {
|
||||
struct bcma_drv_cc *cc;
|
||||
|
||||
struct nand_chip nand_chip;
|
||||
struct mtd_info mtd;
|
||||
|
||||
unsigned curr_command;
|
||||
int curr_page_addr;
|
||||
int curr_column;
|
||||
|
||||
u8 id_data[8];
|
||||
};
|
||||
|
||||
int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n);
|
||||
|
||||
#endif /* BCM47XXNFLASH */
|
||||
80
drivers/mtd/nand/bcm47xxnflash/main.c
Normal file
80
drivers/mtd/nand/bcm47xxnflash/main.c
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* BCM47XX NAND flash driver
|
||||
*
|
||||
* Copyright (C) 2012 Rafał Miłecki <zajec5@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bcm47xxnflash.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/bcma/bcma.h>
|
||||
|
||||
MODULE_DESCRIPTION("NAND flash driver for BCMA bus");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Rafał Miłecki");
|
||||
|
||||
static const char *probes[] = { "bcm47xxpart", NULL };
|
||||
|
||||
static int bcm47xxnflash_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev);
|
||||
struct bcm47xxnflash *b47n;
|
||||
int err = 0;
|
||||
|
||||
b47n = devm_kzalloc(&pdev->dev, sizeof(*b47n), GFP_KERNEL);
|
||||
if (!b47n)
|
||||
return -ENOMEM;
|
||||
|
||||
b47n->nand_chip.priv = b47n;
|
||||
b47n->mtd.owner = THIS_MODULE;
|
||||
b47n->mtd.priv = &b47n->nand_chip; /* Required */
|
||||
b47n->cc = container_of(nflash, struct bcma_drv_cc, nflash);
|
||||
|
||||
if (b47n->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) {
|
||||
err = bcm47xxnflash_ops_bcm4706_init(b47n);
|
||||
} else {
|
||||
pr_err("Device not supported\n");
|
||||
err = -ENOTSUPP;
|
||||
}
|
||||
if (err) {
|
||||
pr_err("Initialization failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = mtd_device_parse_register(&b47n->mtd, probes, NULL, NULL, 0);
|
||||
if (err) {
|
||||
pr_err("Failed to register MTD device: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm47xxnflash_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev);
|
||||
|
||||
if (nflash->mtd)
|
||||
mtd_device_unregister(nflash->mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bcm47xxnflash_driver = {
|
||||
.probe = bcm47xxnflash_probe,
|
||||
.remove = bcm47xxnflash_remove,
|
||||
.driver = {
|
||||
.name = "bcma_nflash",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(bcm47xxnflash_driver);
|
||||
454
drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c
Normal file
454
drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
/*
|
||||
* BCM47XX NAND flash driver
|
||||
*
|
||||
* Copyright (C) 2012 Rafał Miłecki <zajec5@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bcm47xxnflash.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/bcma/bcma.h>
|
||||
|
||||
/* Broadcom uses 1'000'000 but it seems to be too many. Tests on WNDR4500 has
|
||||
* shown ~1000 retries as maxiumum. */
|
||||
#define NFLASH_READY_RETRIES 10000
|
||||
|
||||
#define NFLASH_SECTOR_SIZE 512
|
||||
|
||||
#define NCTL_CMD0 0x00010000
|
||||
#define NCTL_COL 0x00020000 /* Update column with value from BCMA_CC_NFLASH_COL_ADDR */
|
||||
#define NCTL_ROW 0x00040000 /* Update row (page) with value from BCMA_CC_NFLASH_ROW_ADDR */
|
||||
#define NCTL_CMD1W 0x00080000
|
||||
#define NCTL_READ 0x00100000
|
||||
#define NCTL_WRITE 0x00200000
|
||||
#define NCTL_SPECADDR 0x01000000
|
||||
#define NCTL_READY 0x04000000
|
||||
#define NCTL_ERR 0x08000000
|
||||
#define NCTL_CSA 0x40000000
|
||||
#define NCTL_START 0x80000000
|
||||
|
||||
/**************************************************
|
||||
* Various helpers
|
||||
**************************************************/
|
||||
|
||||
static inline u8 bcm47xxnflash_ops_bcm4706_ns_to_cycle(u16 ns, u16 clock)
|
||||
{
|
||||
return ((ns * 1000 * clock) / 1000000) + 1;
|
||||
}
|
||||
|
||||
static int bcm47xxnflash_ops_bcm4706_ctl_cmd(struct bcma_drv_cc *cc, u32 code)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
bcma_cc_write32(cc, BCMA_CC_NFLASH_CTL, NCTL_START | code);
|
||||
for (i = 0; i < NFLASH_READY_RETRIES; i++) {
|
||||
if (!(bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_START)) {
|
||||
i = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i) {
|
||||
pr_err("NFLASH control command not ready!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm47xxnflash_ops_bcm4706_poll(struct bcma_drv_cc *cc)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NFLASH_READY_RETRIES; i++) {
|
||||
if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_READY) {
|
||||
if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) &
|
||||
BCMA_CC_NFLASH_CTL_ERR) {
|
||||
pr_err("Error on polling\n");
|
||||
return -EBUSY;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("Polling timeout!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* R/W
|
||||
**************************************************/
|
||||
|
||||
static void bcm47xxnflash_ops_bcm4706_read(struct mtd_info *mtd, uint8_t *buf,
|
||||
int len)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
|
||||
u32 ctlcode;
|
||||
u32 *dest = (u32 *)buf;
|
||||
int i;
|
||||
int toread;
|
||||
|
||||
BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask);
|
||||
/* Don't validate column using nand_chip->page_shift, it may be bigger
|
||||
* when accessing OOB */
|
||||
|
||||
while (len) {
|
||||
/* We can read maximum of 0x200 bytes at once */
|
||||
toread = min(len, 0x200);
|
||||
|
||||
/* Set page and column */
|
||||
bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_COL_ADDR,
|
||||
b47n->curr_column);
|
||||
bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_ROW_ADDR,
|
||||
b47n->curr_page_addr);
|
||||
|
||||
/* Prepare to read */
|
||||
ctlcode = NCTL_CSA | NCTL_CMD1W | NCTL_ROW | NCTL_COL |
|
||||
NCTL_CMD0;
|
||||
ctlcode |= NAND_CMD_READSTART << 8;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode))
|
||||
return;
|
||||
if (bcm47xxnflash_ops_bcm4706_poll(b47n->cc))
|
||||
return;
|
||||
|
||||
/* Eventually read some data :) */
|
||||
for (i = 0; i < toread; i += 4, dest++) {
|
||||
ctlcode = NCTL_CSA | 0x30000000 | NCTL_READ;
|
||||
if (i == toread - 4) /* Last read goes without that */
|
||||
ctlcode &= ~NCTL_CSA;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc,
|
||||
ctlcode))
|
||||
return;
|
||||
*dest = bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA);
|
||||
}
|
||||
|
||||
b47n->curr_column += toread;
|
||||
len -= toread;
|
||||
}
|
||||
}
|
||||
|
||||
static void bcm47xxnflash_ops_bcm4706_write(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
struct bcma_drv_cc *cc = b47n->cc;
|
||||
|
||||
u32 ctlcode;
|
||||
const u32 *data = (u32 *)buf;
|
||||
int i;
|
||||
|
||||
BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask);
|
||||
/* Don't validate column using nand_chip->page_shift, it may be bigger
|
||||
* when accessing OOB */
|
||||
|
||||
for (i = 0; i < len; i += 4, data++) {
|
||||
bcma_cc_write32(cc, BCMA_CC_NFLASH_DATA, *data);
|
||||
|
||||
ctlcode = NCTL_CSA | 0x30000000 | NCTL_WRITE;
|
||||
if (i == len - 4) /* Last read goes without that */
|
||||
ctlcode &= ~NCTL_CSA;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) {
|
||||
pr_err("%s ctl_cmd didn't work!\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
b47n->curr_column += len;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* NAND chip ops
|
||||
**************************************************/
|
||||
|
||||
static void bcm47xxnflash_ops_bcm4706_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
u32 code = 0;
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
if (cmd & NAND_CTRL_CLE)
|
||||
code = cmd | NCTL_CMD0;
|
||||
|
||||
/* nCS is not needed for reset command */
|
||||
if (cmd != NAND_CMD_RESET)
|
||||
code |= NCTL_CSA;
|
||||
|
||||
bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, code);
|
||||
}
|
||||
|
||||
/* Default nand_select_chip calls cmd_ctrl, which is not used in BCM4706 */
|
||||
static void bcm47xxnflash_ops_bcm4706_select_chip(struct mtd_info *mtd,
|
||||
int chip)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static int bcm47xxnflash_ops_bcm4706_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
|
||||
return !!(bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_CTL) & NCTL_READY);
|
||||
}
|
||||
|
||||
/*
|
||||
* Default nand_command and nand_command_lp don't match BCM4706 hardware layout.
|
||||
* For example, reading chip id is performed in a non-standard way.
|
||||
* Setting column and page is also handled differently, we use a special
|
||||
* registers of ChipCommon core. Hacking cmd_ctrl to understand and convert
|
||||
* standard commands would be much more complicated.
|
||||
*/
|
||||
static void bcm47xxnflash_ops_bcm4706_cmdfunc(struct mtd_info *mtd,
|
||||
unsigned command, int column,
|
||||
int page_addr)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
struct bcma_drv_cc *cc = b47n->cc;
|
||||
u32 ctlcode;
|
||||
int i;
|
||||
|
||||
if (column != -1)
|
||||
b47n->curr_column = column;
|
||||
if (page_addr != -1)
|
||||
b47n->curr_page_addr = page_addr;
|
||||
|
||||
switch (command) {
|
||||
case NAND_CMD_RESET:
|
||||
nand_chip->cmd_ctrl(mtd, command, NAND_CTRL_CLE);
|
||||
|
||||
ndelay(100);
|
||||
nand_wait_ready(mtd);
|
||||
break;
|
||||
case NAND_CMD_READID:
|
||||
ctlcode = NCTL_CSA | 0x01000000 | NCTL_CMD1W | NCTL_CMD0;
|
||||
ctlcode |= NAND_CMD_READID;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode)) {
|
||||
pr_err("READID error\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reading is specific, last one has to go without NCTL_CSA
|
||||
* bit. We don't know how many reads NAND subsystem is going
|
||||
* to perform, so cache everything.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(b47n->id_data); i++) {
|
||||
ctlcode = NCTL_CSA | NCTL_READ;
|
||||
if (i == ARRAY_SIZE(b47n->id_data) - 1)
|
||||
ctlcode &= ~NCTL_CSA;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc,
|
||||
ctlcode)) {
|
||||
pr_err("READID error\n");
|
||||
break;
|
||||
}
|
||||
b47n->id_data[i] =
|
||||
bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA)
|
||||
& 0xFF;
|
||||
}
|
||||
|
||||
break;
|
||||
case NAND_CMD_STATUS:
|
||||
ctlcode = NCTL_CSA | NCTL_CMD0 | NAND_CMD_STATUS;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode))
|
||||
pr_err("STATUS command error\n");
|
||||
break;
|
||||
case NAND_CMD_READ0:
|
||||
break;
|
||||
case NAND_CMD_READOOB:
|
||||
if (page_addr != -1)
|
||||
b47n->curr_column += mtd->writesize;
|
||||
break;
|
||||
case NAND_CMD_ERASE1:
|
||||
bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR,
|
||||
b47n->curr_page_addr);
|
||||
ctlcode = NCTL_ROW | NCTL_CMD1W | NCTL_CMD0 |
|
||||
NAND_CMD_ERASE1 | (NAND_CMD_ERASE2 << 8);
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode))
|
||||
pr_err("ERASE1 failed\n");
|
||||
break;
|
||||
case NAND_CMD_ERASE2:
|
||||
break;
|
||||
case NAND_CMD_SEQIN:
|
||||
/* Set page and column */
|
||||
bcma_cc_write32(cc, BCMA_CC_NFLASH_COL_ADDR,
|
||||
b47n->curr_column);
|
||||
bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR,
|
||||
b47n->curr_page_addr);
|
||||
|
||||
/* Prepare to write */
|
||||
ctlcode = 0x40000000 | NCTL_ROW | NCTL_COL | NCTL_CMD0;
|
||||
ctlcode |= NAND_CMD_SEQIN;
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode))
|
||||
pr_err("SEQIN failed\n");
|
||||
break;
|
||||
case NAND_CMD_PAGEPROG:
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, NCTL_CMD0 |
|
||||
NAND_CMD_PAGEPROG))
|
||||
pr_err("PAGEPROG failed\n");
|
||||
if (bcm47xxnflash_ops_bcm4706_poll(cc))
|
||||
pr_err("PAGEPROG not ready\n");
|
||||
break;
|
||||
default:
|
||||
pr_err("Command 0x%X unsupported\n", command);
|
||||
break;
|
||||
}
|
||||
b47n->curr_command = command;
|
||||
}
|
||||
|
||||
static u8 bcm47xxnflash_ops_bcm4706_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
struct bcma_drv_cc *cc = b47n->cc;
|
||||
u32 tmp = 0;
|
||||
|
||||
switch (b47n->curr_command) {
|
||||
case NAND_CMD_READID:
|
||||
if (b47n->curr_column >= ARRAY_SIZE(b47n->id_data)) {
|
||||
pr_err("Requested invalid id_data: %d\n",
|
||||
b47n->curr_column);
|
||||
return 0;
|
||||
}
|
||||
return b47n->id_data[b47n->curr_column++];
|
||||
case NAND_CMD_STATUS:
|
||||
if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, NCTL_READ))
|
||||
return 0;
|
||||
return bcma_cc_read32(cc, BCMA_CC_NFLASH_DATA) & 0xff;
|
||||
case NAND_CMD_READOOB:
|
||||
bcm47xxnflash_ops_bcm4706_read(mtd, (u8 *)&tmp, 4);
|
||||
return tmp & 0xFF;
|
||||
}
|
||||
|
||||
pr_err("Invalid command for byte read: 0x%X\n", b47n->curr_command);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcm47xxnflash_ops_bcm4706_read_buf(struct mtd_info *mtd,
|
||||
uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
|
||||
switch (b47n->curr_command) {
|
||||
case NAND_CMD_READ0:
|
||||
case NAND_CMD_READOOB:
|
||||
bcm47xxnflash_ops_bcm4706_read(mtd, buf, len);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_err("Invalid command for buf read: 0x%X\n", b47n->curr_command);
|
||||
}
|
||||
|
||||
static void bcm47xxnflash_ops_bcm4706_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv;
|
||||
struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv;
|
||||
|
||||
switch (b47n->curr_command) {
|
||||
case NAND_CMD_SEQIN:
|
||||
bcm47xxnflash_ops_bcm4706_write(mtd, buf, len);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_err("Invalid command for buf write: 0x%X\n", b47n->curr_command);
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* Init
|
||||
**************************************************/
|
||||
|
||||
int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n)
|
||||
{
|
||||
struct nand_chip *nand_chip = (struct nand_chip *)&b47n->nand_chip;
|
||||
int err;
|
||||
u32 freq;
|
||||
u16 clock;
|
||||
u8 w0, w1, w2, w3, w4;
|
||||
|
||||
unsigned long chipsize; /* MiB */
|
||||
u8 tbits, col_bits, col_size, row_bits, row_bsize;
|
||||
u32 val;
|
||||
|
||||
b47n->nand_chip.select_chip = bcm47xxnflash_ops_bcm4706_select_chip;
|
||||
nand_chip->cmd_ctrl = bcm47xxnflash_ops_bcm4706_cmd_ctrl;
|
||||
nand_chip->dev_ready = bcm47xxnflash_ops_bcm4706_dev_ready;
|
||||
b47n->nand_chip.cmdfunc = bcm47xxnflash_ops_bcm4706_cmdfunc;
|
||||
b47n->nand_chip.read_byte = bcm47xxnflash_ops_bcm4706_read_byte;
|
||||
b47n->nand_chip.read_buf = bcm47xxnflash_ops_bcm4706_read_buf;
|
||||
b47n->nand_chip.write_buf = bcm47xxnflash_ops_bcm4706_write_buf;
|
||||
|
||||
nand_chip->chip_delay = 50;
|
||||
b47n->nand_chip.bbt_options = NAND_BBT_USE_FLASH;
|
||||
b47n->nand_chip.ecc.mode = NAND_ECC_NONE; /* TODO: implement ECC */
|
||||
|
||||
/* Enable NAND flash access */
|
||||
bcma_cc_set32(b47n->cc, BCMA_CC_4706_FLASHSCFG,
|
||||
BCMA_CC_4706_FLASHSCFG_NF1);
|
||||
|
||||
/* Configure wait counters */
|
||||
if (b47n->cc->status & BCMA_CC_CHIPST_4706_PKG_OPTION) {
|
||||
/* 400 MHz */
|
||||
freq = 400000000 / 4;
|
||||
} else {
|
||||
freq = bcma_chipco_pll_read(b47n->cc, 4);
|
||||
freq = (freq & 0xFFF) >> 3;
|
||||
/* Fixed reference clock 25 MHz and m = 2 */
|
||||
freq = (freq * 25000000 / 2) / 4;
|
||||
}
|
||||
clock = freq / 1000000;
|
||||
w0 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(15, clock);
|
||||
w1 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(20, clock);
|
||||
w2 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock);
|
||||
w3 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock);
|
||||
w4 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(100, clock);
|
||||
bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_WAITCNT0,
|
||||
(w4 << 24 | w3 << 18 | w2 << 12 | w1 << 6 | w0));
|
||||
|
||||
/* Scan NAND */
|
||||
err = nand_scan(&b47n->mtd, 1);
|
||||
if (err) {
|
||||
pr_err("Could not scan NAND flash: %d\n", err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Configure FLASH */
|
||||
chipsize = b47n->nand_chip.chipsize >> 20;
|
||||
tbits = ffs(chipsize); /* find first bit set */
|
||||
if (!tbits || tbits != fls(chipsize)) {
|
||||
pr_err("Invalid flash size: 0x%lX\n", chipsize);
|
||||
err = -ENOTSUPP;
|
||||
goto exit;
|
||||
}
|
||||
tbits += 19; /* Broadcom increases *index* by 20, we increase *pos* */
|
||||
|
||||
col_bits = b47n->nand_chip.page_shift + 1;
|
||||
col_size = (col_bits + 7) / 8;
|
||||
|
||||
row_bits = tbits - col_bits + 1;
|
||||
row_bsize = (row_bits + 7) / 8;
|
||||
|
||||
val = ((row_bsize - 1) << 6) | ((col_size - 1) << 4) | 2;
|
||||
bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_CONF, val);
|
||||
|
||||
exit:
|
||||
if (err)
|
||||
bcma_cc_mask32(b47n->cc, BCMA_CC_4706_FLASHSCFG,
|
||||
~BCMA_CC_4706_FLASHSCFG_NF1);
|
||||
return err;
|
||||
}
|
||||
848
drivers/mtd/nand/bf5xx_nand.c
Normal file
848
drivers/mtd/nand/bf5xx_nand.c
Normal file
|
|
@ -0,0 +1,848 @@
|
|||
/* linux/drivers/mtd/nand/bf5xx_nand.c
|
||||
*
|
||||
* Copyright 2006-2008 Analog Devices Inc.
|
||||
* http://blackfin.uclinux.org/
|
||||
* Bryan Wu <bryan.wu@analog.com>
|
||||
*
|
||||
* Blackfin BF5xx on-chip NAND flash controller driver
|
||||
*
|
||||
* Derived from drivers/mtd/nand/s3c2410.c
|
||||
* Copyright (c) 2007 Ben Dooks <ben@simtec.co.uk>
|
||||
*
|
||||
* Derived from drivers/mtd/nand/cafe.c
|
||||
* Copyright © 2006 Red Hat, Inc.
|
||||
* Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
|
||||
*
|
||||
* Changelog:
|
||||
* 12-Jun-2007 Bryan Wu: Initial version
|
||||
* 18-Jul-2007 Bryan Wu:
|
||||
* - ECC_HW and ECC_SW supported
|
||||
* - DMA supported in ECC_HW
|
||||
* - YAFFS tested as rootfs in both ECC_HW and ECC_SW
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/blackfin.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/nand.h>
|
||||
#include <asm/portmux.h>
|
||||
|
||||
#define DRV_NAME "bf5xx-nand"
|
||||
#define DRV_VERSION "1.2"
|
||||
#define DRV_AUTHOR "Bryan Wu <bryan.wu@analog.com>"
|
||||
#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
|
||||
|
||||
/* NFC_STAT Masks */
|
||||
#define NBUSY 0x01 /* Not Busy */
|
||||
#define WB_FULL 0x02 /* Write Buffer Full */
|
||||
#define PG_WR_STAT 0x04 /* Page Write Pending */
|
||||
#define PG_RD_STAT 0x08 /* Page Read Pending */
|
||||
#define WB_EMPTY 0x10 /* Write Buffer Empty */
|
||||
|
||||
/* NFC_IRQSTAT Masks */
|
||||
#define NBUSYIRQ 0x01 /* Not Busy IRQ */
|
||||
#define WB_OVF 0x02 /* Write Buffer Overflow */
|
||||
#define WB_EDGE 0x04 /* Write Buffer Edge Detect */
|
||||
#define RD_RDY 0x08 /* Read Data Ready */
|
||||
#define WR_DONE 0x10 /* Page Write Done */
|
||||
|
||||
/* NFC_RST Masks */
|
||||
#define ECC_RST 0x01 /* ECC (and NFC counters) Reset */
|
||||
|
||||
/* NFC_PGCTL Masks */
|
||||
#define PG_RD_START 0x01 /* Page Read Start */
|
||||
#define PG_WR_START 0x02 /* Page Write Start */
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_BF5XX_HWECC
|
||||
static int hardware_ecc = 1;
|
||||
#else
|
||||
static int hardware_ecc;
|
||||
#endif
|
||||
|
||||
static const unsigned short bfin_nfc_pin_req[] =
|
||||
{P_NAND_CE,
|
||||
P_NAND_RB,
|
||||
P_NAND_D0,
|
||||
P_NAND_D1,
|
||||
P_NAND_D2,
|
||||
P_NAND_D3,
|
||||
P_NAND_D4,
|
||||
P_NAND_D5,
|
||||
P_NAND_D6,
|
||||
P_NAND_D7,
|
||||
P_NAND_WE,
|
||||
P_NAND_RE,
|
||||
P_NAND_CLE,
|
||||
P_NAND_ALE,
|
||||
0};
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
||||
static struct nand_ecclayout bootrom_ecclayout = {
|
||||
.eccbytes = 24,
|
||||
.eccpos = {
|
||||
0x8 * 0, 0x8 * 0 + 1, 0x8 * 0 + 2,
|
||||
0x8 * 1, 0x8 * 1 + 1, 0x8 * 1 + 2,
|
||||
0x8 * 2, 0x8 * 2 + 1, 0x8 * 2 + 2,
|
||||
0x8 * 3, 0x8 * 3 + 1, 0x8 * 3 + 2,
|
||||
0x8 * 4, 0x8 * 4 + 1, 0x8 * 4 + 2,
|
||||
0x8 * 5, 0x8 * 5 + 1, 0x8 * 5 + 2,
|
||||
0x8 * 6, 0x8 * 6 + 1, 0x8 * 6 + 2,
|
||||
0x8 * 7, 0x8 * 7 + 1, 0x8 * 7 + 2
|
||||
},
|
||||
.oobfree = {
|
||||
{ 0x8 * 0 + 3, 5 },
|
||||
{ 0x8 * 1 + 3, 5 },
|
||||
{ 0x8 * 2 + 3, 5 },
|
||||
{ 0x8 * 3 + 3, 5 },
|
||||
{ 0x8 * 4 + 3, 5 },
|
||||
{ 0x8 * 5 + 3, 5 },
|
||||
{ 0x8 * 6 + 3, 5 },
|
||||
{ 0x8 * 7 + 3, 5 },
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Data structures for bf5xx nand flash controller driver
|
||||
*/
|
||||
|
||||
/* bf5xx nand info */
|
||||
struct bf5xx_nand_info {
|
||||
/* mtd info */
|
||||
struct nand_hw_control controller;
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
|
||||
/* platform info */
|
||||
struct bf5xx_nand_platform *platform;
|
||||
|
||||
/* device info */
|
||||
struct device *device;
|
||||
|
||||
/* DMA stuff */
|
||||
struct completion dma_completion;
|
||||
};
|
||||
|
||||
/*
|
||||
* Conversion functions
|
||||
*/
|
||||
static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd)
|
||||
{
|
||||
return container_of(mtd, struct bf5xx_nand_info, mtd);
|
||||
}
|
||||
|
||||
static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev)
|
||||
{
|
||||
return platform_get_drvdata(pdev);
|
||||
}
|
||||
|
||||
static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev)
|
||||
{
|
||||
return dev_get_platdata(&pdev->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* struct nand_chip interface function pointers
|
||||
*/
|
||||
|
||||
/*
|
||||
* bf5xx_nand_hwcontrol
|
||||
*
|
||||
* Issue command and address cycles to the chip
|
||||
*/
|
||||
static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
while (bfin_read_NFC_STAT() & WB_FULL)
|
||||
cpu_relax();
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
bfin_write_NFC_CMD(cmd);
|
||||
else if (ctrl & NAND_ALE)
|
||||
bfin_write_NFC_ADDR(cmd);
|
||||
SSYNC();
|
||||
}
|
||||
|
||||
/*
|
||||
* bf5xx_nand_devready()
|
||||
*
|
||||
* returns 0 if the nand is busy, 1 if it is ready
|
||||
*/
|
||||
static int bf5xx_nand_devready(struct mtd_info *mtd)
|
||||
{
|
||||
unsigned short val = bfin_read_NFC_STAT();
|
||||
|
||||
if ((val & NBUSY) == NBUSY)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ECC functions
|
||||
* These allow the bf5xx to use the controller's ECC
|
||||
* generator block to ECC the data as it passes through
|
||||
*/
|
||||
|
||||
/*
|
||||
* ECC error correction function
|
||||
*/
|
||||
static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat,
|
||||
u_char *read_ecc, u_char *calc_ecc)
|
||||
{
|
||||
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
||||
u32 syndrome[5];
|
||||
u32 calced, stored;
|
||||
int i;
|
||||
unsigned short failing_bit, failing_byte;
|
||||
u_char data;
|
||||
|
||||
calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16);
|
||||
stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16);
|
||||
|
||||
syndrome[0] = (calced ^ stored);
|
||||
|
||||
/*
|
||||
* syndrome 0: all zero
|
||||
* No error in data
|
||||
* No action
|
||||
*/
|
||||
if (!syndrome[0] || !calced || !stored)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* sysdrome 0: only one bit is one
|
||||
* ECC data was incorrect
|
||||
* No action
|
||||
*/
|
||||
if (hweight32(syndrome[0]) == 1) {
|
||||
dev_err(info->device, "ECC data was incorrect!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF);
|
||||
syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF);
|
||||
syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF);
|
||||
syndrome[4] = syndrome[2] ^ syndrome[3];
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]);
|
||||
|
||||
dev_info(info->device,
|
||||
"calced[0x%08x], stored[0x%08x]\n",
|
||||
calced, stored);
|
||||
|
||||
/*
|
||||
* sysdrome 0: exactly 11 bits are one, each parity
|
||||
* and parity' pair is 1 & 0 or 0 & 1.
|
||||
* 1-bit correctable error
|
||||
* Correct the error
|
||||
*/
|
||||
if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) {
|
||||
dev_info(info->device,
|
||||
"1-bit correctable error, correct it.\n");
|
||||
dev_info(info->device,
|
||||
"syndrome[1] 0x%08x\n", syndrome[1]);
|
||||
|
||||
failing_bit = syndrome[1] & 0x7;
|
||||
failing_byte = syndrome[1] >> 0x3;
|
||||
data = *(dat + failing_byte);
|
||||
data = data ^ (0x1 << failing_bit);
|
||||
*(dat + failing_byte) = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* sysdrome 0: random data
|
||||
* More than 1-bit error, non-correctable error
|
||||
* Discard data, mark bad block
|
||||
*/
|
||||
dev_err(info->device,
|
||||
"More than 1-bit error, non-correctable error.\n");
|
||||
dev_err(info->device,
|
||||
"Please discard data, mark bad block\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat,
|
||||
u_char *read_ecc, u_char *calc_ecc)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int ret;
|
||||
|
||||
ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
|
||||
|
||||
/* If ecc size is 512, correct second 256 bytes */
|
||||
if (chip->ecc.size == 512) {
|
||||
dat += 256;
|
||||
read_ecc += 3;
|
||||
calc_ecc += 3;
|
||||
ret |= bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd,
|
||||
const u_char *dat, u_char *ecc_code)
|
||||
{
|
||||
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
u16 ecc0, ecc1;
|
||||
u32 code[2];
|
||||
u8 *p;
|
||||
|
||||
/* first 3 bytes ECC code for 256 page size */
|
||||
ecc0 = bfin_read_NFC_ECC0();
|
||||
ecc1 = bfin_read_NFC_ECC1();
|
||||
|
||||
code[0] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11);
|
||||
|
||||
dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]);
|
||||
|
||||
p = (u8 *) code;
|
||||
memcpy(ecc_code, p, 3);
|
||||
|
||||
/* second 3 bytes ECC code for 512 ecc size */
|
||||
if (chip->ecc.size == 512) {
|
||||
ecc0 = bfin_read_NFC_ECC2();
|
||||
ecc1 = bfin_read_NFC_ECC3();
|
||||
code[1] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11);
|
||||
|
||||
/* second 3 bytes in ecc_code for second 256
|
||||
* bytes of 512 page size
|
||||
*/
|
||||
p = (u8 *) (code + 1);
|
||||
memcpy((ecc_code + 3), p, 3);
|
||||
dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* PIO mode for buffer writing and reading
|
||||
*/
|
||||
static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
unsigned short val;
|
||||
|
||||
/*
|
||||
* Data reads are requested by first writing to NFC_DATA_RD
|
||||
* and then reading back from NFC_READ.
|
||||
*/
|
||||
for (i = 0; i < len; i++) {
|
||||
while (bfin_read_NFC_STAT() & WB_FULL)
|
||||
cpu_relax();
|
||||
|
||||
/* Contents do not matter */
|
||||
bfin_write_NFC_DATA_RD(0x0000);
|
||||
SSYNC();
|
||||
|
||||
while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY)
|
||||
cpu_relax();
|
||||
|
||||
buf[i] = bfin_read_NFC_READ();
|
||||
|
||||
val = bfin_read_NFC_IRQSTAT();
|
||||
val |= RD_RDY;
|
||||
bfin_write_NFC_IRQSTAT(val);
|
||||
SSYNC();
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
uint8_t val;
|
||||
|
||||
bf5xx_nand_read_buf(mtd, &val, 1);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void bf5xx_nand_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
while (bfin_read_NFC_STAT() & WB_FULL)
|
||||
cpu_relax();
|
||||
|
||||
bfin_write_NFC_DATA_WR(buf[i]);
|
||||
SSYNC();
|
||||
}
|
||||
}
|
||||
|
||||
static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
/*
|
||||
* Data reads are requested by first writing to NFC_DATA_RD
|
||||
* and then reading back from NFC_READ.
|
||||
*/
|
||||
bfin_write_NFC_DATA_RD(0x5555);
|
||||
|
||||
SSYNC();
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
p[i] = bfin_read_NFC_READ();
|
||||
}
|
||||
|
||||
static void bf5xx_nand_write_buf16(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
bfin_write_NFC_DATA_WR(p[i]);
|
||||
|
||||
SSYNC();
|
||||
}
|
||||
|
||||
/*
|
||||
* DMA functions for buffer writing and reading
|
||||
*/
|
||||
static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct bf5xx_nand_info *info = dev_id;
|
||||
|
||||
clear_dma_irqstat(CH_NFC);
|
||||
disable_dma(CH_NFC);
|
||||
complete(&info->dma_completion);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void bf5xx_nand_dma_rw(struct mtd_info *mtd,
|
||||
uint8_t *buf, int is_read)
|
||||
{
|
||||
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
unsigned short val;
|
||||
|
||||
dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n",
|
||||
mtd, buf, is_read);
|
||||
|
||||
/*
|
||||
* Before starting a dma transfer, be sure to invalidate/flush
|
||||
* the cache over the address range of your DMA buffer to
|
||||
* prevent cache coherency problems. Otherwise very subtle bugs
|
||||
* can be introduced to your driver.
|
||||
*/
|
||||
if (is_read)
|
||||
invalidate_dcache_range((unsigned int)buf,
|
||||
(unsigned int)(buf + chip->ecc.size));
|
||||
else
|
||||
flush_dcache_range((unsigned int)buf,
|
||||
(unsigned int)(buf + chip->ecc.size));
|
||||
|
||||
/*
|
||||
* This register must be written before each page is
|
||||
* transferred to generate the correct ECC register
|
||||
* values.
|
||||
*/
|
||||
bfin_write_NFC_RST(ECC_RST);
|
||||
SSYNC();
|
||||
while (bfin_read_NFC_RST() & ECC_RST)
|
||||
cpu_relax();
|
||||
|
||||
disable_dma(CH_NFC);
|
||||
clear_dma_irqstat(CH_NFC);
|
||||
|
||||
/* setup DMA register with Blackfin DMA API */
|
||||
set_dma_config(CH_NFC, 0x0);
|
||||
set_dma_start_addr(CH_NFC, (unsigned long) buf);
|
||||
|
||||
/* The DMAs have different size on BF52x and BF54x */
|
||||
#ifdef CONFIG_BF52x
|
||||
set_dma_x_count(CH_NFC, (chip->ecc.size >> 1));
|
||||
set_dma_x_modify(CH_NFC, 2);
|
||||
val = DI_EN | WDSIZE_16;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BF54x
|
||||
set_dma_x_count(CH_NFC, (chip->ecc.size >> 2));
|
||||
set_dma_x_modify(CH_NFC, 4);
|
||||
val = DI_EN | WDSIZE_32;
|
||||
#endif
|
||||
/* setup write or read operation */
|
||||
if (is_read)
|
||||
val |= WNR;
|
||||
set_dma_config(CH_NFC, val);
|
||||
enable_dma(CH_NFC);
|
||||
|
||||
/* Start PAGE read/write operation */
|
||||
if (is_read)
|
||||
bfin_write_NFC_PGCTL(PG_RD_START);
|
||||
else
|
||||
bfin_write_NFC_PGCTL(PG_WR_START);
|
||||
wait_for_completion(&info->dma_completion);
|
||||
}
|
||||
|
||||
static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd,
|
||||
uint8_t *buf, int len)
|
||||
{
|
||||
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len);
|
||||
|
||||
if (len == chip->ecc.size)
|
||||
bf5xx_nand_dma_rw(mtd, buf, 1);
|
||||
else
|
||||
bf5xx_nand_read_buf(mtd, buf, len);
|
||||
}
|
||||
|
||||
static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len);
|
||||
|
||||
if (len == chip->ecc.size)
|
||||
bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0);
|
||||
else
|
||||
bf5xx_nand_write_buf(mtd, buf, len);
|
||||
}
|
||||
|
||||
static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
bf5xx_nand_read_buf(mtd, buf, mtd->writesize);
|
||||
bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_nand_write_page_raw(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, const uint8_t *buf, int oob_required)
|
||||
{
|
||||
bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
|
||||
bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* System initialization functions
|
||||
*/
|
||||
static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Do not use dma */
|
||||
if (!hardware_ecc)
|
||||
return 0;
|
||||
|
||||
init_completion(&info->dma_completion);
|
||||
|
||||
/* Request NFC DMA channel */
|
||||
ret = request_dma(CH_NFC, "BF5XX NFC driver");
|
||||
if (ret < 0) {
|
||||
dev_err(info->device, " unable to get DMA channel\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BF54x
|
||||
/* Setup DMAC1 channel mux for NFC which shared with SDH */
|
||||
bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() & ~1);
|
||||
SSYNC();
|
||||
#endif
|
||||
|
||||
set_dma_callback(CH_NFC, bf5xx_nand_dma_irq, info);
|
||||
|
||||
/* Turn off the DMA channel first */
|
||||
disable_dma(CH_NFC);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bf5xx_nand_dma_remove(struct bf5xx_nand_info *info)
|
||||
{
|
||||
/* Free NFC DMA channel */
|
||||
if (hardware_ecc)
|
||||
free_dma(CH_NFC);
|
||||
}
|
||||
|
||||
/*
|
||||
* BF5XX NFC hardware initialization
|
||||
* - pin mux setup
|
||||
* - clear interrupt status
|
||||
*/
|
||||
static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned short val;
|
||||
struct bf5xx_nand_platform *plat = info->platform;
|
||||
|
||||
/* setup NFC_CTL register */
|
||||
dev_info(info->device,
|
||||
"data_width=%d, wr_dly=%d, rd_dly=%d\n",
|
||||
(plat->data_width ? 16 : 8),
|
||||
plat->wr_dly, plat->rd_dly);
|
||||
|
||||
val = (1 << NFC_PG_SIZE_OFFSET) |
|
||||
(plat->data_width << NFC_NWIDTH_OFFSET) |
|
||||
(plat->rd_dly << NFC_RDDLY_OFFSET) |
|
||||
(plat->wr_dly << NFC_WRDLY_OFFSET);
|
||||
dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val);
|
||||
|
||||
bfin_write_NFC_CTL(val);
|
||||
SSYNC();
|
||||
|
||||
/* clear interrupt status */
|
||||
bfin_write_NFC_IRQMASK(0x0);
|
||||
SSYNC();
|
||||
val = bfin_read_NFC_IRQSTAT();
|
||||
bfin_write_NFC_IRQSTAT(val);
|
||||
SSYNC();
|
||||
|
||||
/* DMA initialization */
|
||||
if (bf5xx_nand_dma_init(info))
|
||||
err = -ENXIO;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device management interface
|
||||
*/
|
||||
static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info)
|
||||
{
|
||||
struct mtd_info *mtd = &info->mtd;
|
||||
struct mtd_partition *parts = info->platform->partitions;
|
||||
int nr = info->platform->nr_partitions;
|
||||
|
||||
return mtd_device_register(mtd, parts, nr);
|
||||
}
|
||||
|
||||
static int bf5xx_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bf5xx_nand_info *info = to_nand_info(pdev);
|
||||
|
||||
/* first thing we need to do is release all our mtds
|
||||
* and their partitions, then go through freeing the
|
||||
* resources used
|
||||
*/
|
||||
nand_release(&info->mtd);
|
||||
|
||||
peripheral_free_list(bfin_nfc_pin_req);
|
||||
bf5xx_nand_dma_remove(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_nand_scan(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int ret;
|
||||
|
||||
ret = nand_scan_ident(mtd, 1, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hardware_ecc) {
|
||||
/*
|
||||
* for nand with page size > 512B, think it as several sections with 512B
|
||||
*/
|
||||
if (likely(mtd->writesize >= 512)) {
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.bytes = 6;
|
||||
chip->ecc.strength = 2;
|
||||
} else {
|
||||
chip->ecc.size = 256;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecc.strength = 1;
|
||||
bfin_write_NFC_CTL(bfin_read_NFC_CTL() & ~(1 << NFC_PG_SIZE_OFFSET));
|
||||
SSYNC();
|
||||
}
|
||||
}
|
||||
|
||||
return nand_scan_tail(mtd);
|
||||
}
|
||||
|
||||
/*
|
||||
* bf5xx_nand_probe
|
||||
*
|
||||
* called by device layer when it finds a device matching
|
||||
* one our driver can handled. This code checks to see if
|
||||
* it can allocate all necessary resources then calls the
|
||||
* nand layer to look for devices
|
||||
*/
|
||||
static int bf5xx_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bf5xx_nand_platform *plat = to_nand_plat(pdev);
|
||||
struct bf5xx_nand_info *info = NULL;
|
||||
struct nand_chip *chip = NULL;
|
||||
struct mtd_info *mtd = NULL;
|
||||
int err = 0;
|
||||
|
||||
dev_dbg(&pdev->dev, "(%p)\n", pdev);
|
||||
|
||||
if (!plat) {
|
||||
dev_err(&pdev->dev, "no platform specific information\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) {
|
||||
dev_err(&pdev->dev, "requesting Peripherals failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (info == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
spin_lock_init(&info->controller.lock);
|
||||
init_waitqueue_head(&info->controller.wq);
|
||||
|
||||
info->device = &pdev->dev;
|
||||
info->platform = plat;
|
||||
|
||||
/* initialise chip data struct */
|
||||
chip = &info->chip;
|
||||
|
||||
if (plat->data_width)
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN;
|
||||
|
||||
chip->read_buf = (plat->data_width) ?
|
||||
bf5xx_nand_read_buf16 : bf5xx_nand_read_buf;
|
||||
chip->write_buf = (plat->data_width) ?
|
||||
bf5xx_nand_write_buf16 : bf5xx_nand_write_buf;
|
||||
|
||||
chip->read_byte = bf5xx_nand_read_byte;
|
||||
|
||||
chip->cmd_ctrl = bf5xx_nand_hwcontrol;
|
||||
chip->dev_ready = bf5xx_nand_devready;
|
||||
|
||||
chip->priv = &info->mtd;
|
||||
chip->controller = &info->controller;
|
||||
|
||||
chip->IO_ADDR_R = (void __iomem *) NFC_READ;
|
||||
chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR;
|
||||
|
||||
chip->chip_delay = 0;
|
||||
|
||||
/* initialise mtd info data struct */
|
||||
mtd = &info->mtd;
|
||||
mtd->priv = chip;
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
/* initialise the hardware */
|
||||
err = bf5xx_nand_hw_init(info);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
/* setup hardware ECC data struct */
|
||||
if (hardware_ecc) {
|
||||
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
||||
chip->ecc.layout = &bootrom_ecclayout;
|
||||
#endif
|
||||
chip->read_buf = bf5xx_nand_dma_read_buf;
|
||||
chip->write_buf = bf5xx_nand_dma_write_buf;
|
||||
chip->ecc.calculate = bf5xx_nand_calculate_ecc;
|
||||
chip->ecc.correct = bf5xx_nand_correct_data;
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
chip->ecc.hwctl = bf5xx_nand_enable_hwecc;
|
||||
chip->ecc.read_page_raw = bf5xx_nand_read_page_raw;
|
||||
chip->ecc.write_page_raw = bf5xx_nand_write_page_raw;
|
||||
} else {
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
}
|
||||
|
||||
/* scan hardware nand chip and setup mtd info data struct */
|
||||
if (bf5xx_nand_scan(mtd)) {
|
||||
err = -ENXIO;
|
||||
goto out_err_nand_scan;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
|
||||
chip->badblockpos = 63;
|
||||
#endif
|
||||
|
||||
/* add NAND partition */
|
||||
bf5xx_nand_add_partition(info);
|
||||
|
||||
dev_dbg(&pdev->dev, "initialised ok\n");
|
||||
return 0;
|
||||
|
||||
out_err_nand_scan:
|
||||
bf5xx_nand_dma_remove(info);
|
||||
out_err:
|
||||
peripheral_free_list(bfin_nfc_pin_req);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* driver device registration */
|
||||
static struct platform_driver bf5xx_nand_driver = {
|
||||
.probe = bf5xx_nand_probe,
|
||||
.remove = bf5xx_nand_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(bf5xx_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR(DRV_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRV_DESC);
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
||||
914
drivers/mtd/nand/cafe_nand.c
Normal file
914
drivers/mtd/nand/cafe_nand.c
Normal file
|
|
@ -0,0 +1,914 @@
|
|||
/*
|
||||
* Driver for One Laptop Per Child ‘CAFÉ’ controller, aka Marvell 88ALP01
|
||||
*
|
||||
* The data sheet for this device can be found at:
|
||||
* http://wiki.laptop.org/go/Datasheets
|
||||
*
|
||||
* Copyright © 2006 Red Hat, Inc.
|
||||
* Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
|
||||
*/
|
||||
|
||||
#define DEBUG
|
||||
|
||||
#include <linux/device.h>
|
||||
#undef DEBUG
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/rslib.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#define CAFE_NAND_CTRL1 0x00
|
||||
#define CAFE_NAND_CTRL2 0x04
|
||||
#define CAFE_NAND_CTRL3 0x08
|
||||
#define CAFE_NAND_STATUS 0x0c
|
||||
#define CAFE_NAND_IRQ 0x10
|
||||
#define CAFE_NAND_IRQ_MASK 0x14
|
||||
#define CAFE_NAND_DATA_LEN 0x18
|
||||
#define CAFE_NAND_ADDR1 0x1c
|
||||
#define CAFE_NAND_ADDR2 0x20
|
||||
#define CAFE_NAND_TIMING1 0x24
|
||||
#define CAFE_NAND_TIMING2 0x28
|
||||
#define CAFE_NAND_TIMING3 0x2c
|
||||
#define CAFE_NAND_NONMEM 0x30
|
||||
#define CAFE_NAND_ECC_RESULT 0x3C
|
||||
#define CAFE_NAND_DMA_CTRL 0x40
|
||||
#define CAFE_NAND_DMA_ADDR0 0x44
|
||||
#define CAFE_NAND_DMA_ADDR1 0x48
|
||||
#define CAFE_NAND_ECC_SYN01 0x50
|
||||
#define CAFE_NAND_ECC_SYN23 0x54
|
||||
#define CAFE_NAND_ECC_SYN45 0x58
|
||||
#define CAFE_NAND_ECC_SYN67 0x5c
|
||||
#define CAFE_NAND_READ_DATA 0x1000
|
||||
#define CAFE_NAND_WRITE_DATA 0x2000
|
||||
|
||||
#define CAFE_GLOBAL_CTRL 0x3004
|
||||
#define CAFE_GLOBAL_IRQ 0x3008
|
||||
#define CAFE_GLOBAL_IRQ_MASK 0x300c
|
||||
#define CAFE_NAND_RESET 0x3034
|
||||
|
||||
/* Missing from the datasheet: bit 19 of CTRL1 sets CE0 vs. CE1 */
|
||||
#define CTRL1_CHIPSELECT (1<<19)
|
||||
|
||||
struct cafe_priv {
|
||||
struct nand_chip nand;
|
||||
struct pci_dev *pdev;
|
||||
void __iomem *mmio;
|
||||
struct rs_control *rs;
|
||||
uint32_t ctl1;
|
||||
uint32_t ctl2;
|
||||
int datalen;
|
||||
int nr_data;
|
||||
int data_pos;
|
||||
int page_addr;
|
||||
dma_addr_t dmaaddr;
|
||||
unsigned char *dmabuf;
|
||||
};
|
||||
|
||||
static int usedma = 1;
|
||||
module_param(usedma, int, 0644);
|
||||
|
||||
static int skipbbt = 0;
|
||||
module_param(skipbbt, int, 0644);
|
||||
|
||||
static int debug = 0;
|
||||
module_param(debug, int, 0644);
|
||||
|
||||
static int regdebug = 0;
|
||||
module_param(regdebug, int, 0644);
|
||||
|
||||
static int checkecc = 1;
|
||||
module_param(checkecc, int, 0644);
|
||||
|
||||
static unsigned int numtimings;
|
||||
static int timing[3];
|
||||
module_param_array(timing, int, &numtimings, 0644);
|
||||
|
||||
static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };
|
||||
|
||||
/* Hrm. Why isn't this already conditional on something in the struct device? */
|
||||
#define cafe_dev_dbg(dev, args...) do { if (debug) dev_dbg(dev, ##args); } while(0)
|
||||
|
||||
/* Make it easier to switch to PIO if we need to */
|
||||
#define cafe_readl(cafe, addr) readl((cafe)->mmio + CAFE_##addr)
|
||||
#define cafe_writel(cafe, datum, addr) writel(datum, (cafe)->mmio + CAFE_##addr)
|
||||
|
||||
static int cafe_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
int result = !!(cafe_readl(cafe, NAND_STATUS) & 0x40000000);
|
||||
uint32_t irqs = cafe_readl(cafe, NAND_IRQ);
|
||||
|
||||
cafe_writel(cafe, irqs, NAND_IRQ);
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "NAND device is%s ready, IRQ %x (%x) (%x,%x)\n",
|
||||
result?"":" not", irqs, cafe_readl(cafe, NAND_IRQ),
|
||||
cafe_readl(cafe, GLOBAL_IRQ), cafe_readl(cafe, GLOBAL_IRQ_MASK));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void cafe_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
if (usedma)
|
||||
memcpy(cafe->dmabuf + cafe->datalen, buf, len);
|
||||
else
|
||||
memcpy_toio(cafe->mmio + CAFE_NAND_WRITE_DATA + cafe->datalen, buf, len);
|
||||
|
||||
cafe->datalen += len;
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes to write buffer. datalen 0x%x\n",
|
||||
len, cafe->datalen);
|
||||
}
|
||||
|
||||
static void cafe_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
if (usedma)
|
||||
memcpy(buf, cafe->dmabuf + cafe->datalen, len);
|
||||
else
|
||||
memcpy_fromio(buf, cafe->mmio + CAFE_NAND_READ_DATA + cafe->datalen, len);
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes from position 0x%x in read buffer.\n",
|
||||
len, cafe->datalen);
|
||||
cafe->datalen += len;
|
||||
}
|
||||
|
||||
static uint8_t cafe_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
uint8_t d;
|
||||
|
||||
cafe_read_buf(mtd, &d, 1);
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Read %02x\n", d);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static void cafe_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
|
||||
int column, int page_addr)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
int adrbytes = 0;
|
||||
uint32_t ctl1;
|
||||
uint32_t doneint = 0x80000000;
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "cmdfunc %02x, 0x%x, 0x%x\n",
|
||||
command, column, page_addr);
|
||||
|
||||
if (command == NAND_CMD_ERASE2 || command == NAND_CMD_PAGEPROG) {
|
||||
/* Second half of a command we already calculated */
|
||||
cafe_writel(cafe, cafe->ctl2 | 0x100 | command, NAND_CTRL2);
|
||||
ctl1 = cafe->ctl1;
|
||||
cafe->ctl2 &= ~(1<<30);
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Continue command, ctl1 %08x, #data %d\n",
|
||||
cafe->ctl1, cafe->nr_data);
|
||||
goto do_command;
|
||||
}
|
||||
/* Reset ECC engine */
|
||||
cafe_writel(cafe, 0, NAND_CTRL2);
|
||||
|
||||
/* Emulate NAND_CMD_READOOB on large-page chips */
|
||||
if (mtd->writesize > 512 &&
|
||||
command == NAND_CMD_READOOB) {
|
||||
column += mtd->writesize;
|
||||
command = NAND_CMD_READ0;
|
||||
}
|
||||
|
||||
/* FIXME: Do we need to send read command before sending data
|
||||
for small-page chips, to position the buffer correctly? */
|
||||
|
||||
if (column != -1) {
|
||||
cafe_writel(cafe, column, NAND_ADDR1);
|
||||
adrbytes = 2;
|
||||
if (page_addr != -1)
|
||||
goto write_adr2;
|
||||
} else if (page_addr != -1) {
|
||||
cafe_writel(cafe, page_addr & 0xffff, NAND_ADDR1);
|
||||
page_addr >>= 16;
|
||||
write_adr2:
|
||||
cafe_writel(cafe, page_addr, NAND_ADDR2);
|
||||
adrbytes += 2;
|
||||
if (mtd->size > mtd->writesize << 16)
|
||||
adrbytes++;
|
||||
}
|
||||
|
||||
cafe->data_pos = cafe->datalen = 0;
|
||||
|
||||
/* Set command valid bit, mask in the chip select bit */
|
||||
ctl1 = 0x80000000 | command | (cafe->ctl1 & CTRL1_CHIPSELECT);
|
||||
|
||||
/* Set RD or WR bits as appropriate */
|
||||
if (command == NAND_CMD_READID || command == NAND_CMD_STATUS) {
|
||||
ctl1 |= (1<<26); /* rd */
|
||||
/* Always 5 bytes, for now */
|
||||
cafe->datalen = 4;
|
||||
/* And one address cycle -- even for STATUS, since the controller doesn't work without */
|
||||
adrbytes = 1;
|
||||
} else if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 ||
|
||||
command == NAND_CMD_READOOB || command == NAND_CMD_RNDOUT) {
|
||||
ctl1 |= 1<<26; /* rd */
|
||||
/* For now, assume just read to end of page */
|
||||
cafe->datalen = mtd->writesize + mtd->oobsize - column;
|
||||
} else if (command == NAND_CMD_SEQIN)
|
||||
ctl1 |= 1<<25; /* wr */
|
||||
|
||||
/* Set number of address bytes */
|
||||
if (adrbytes)
|
||||
ctl1 |= ((adrbytes-1)|8) << 27;
|
||||
|
||||
if (command == NAND_CMD_SEQIN || command == NAND_CMD_ERASE1) {
|
||||
/* Ignore the first command of a pair; the hardware
|
||||
deals with them both at once, later */
|
||||
cafe->ctl1 = ctl1;
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Setup for delayed command, ctl1 %08x, dlen %x\n",
|
||||
cafe->ctl1, cafe->datalen);
|
||||
return;
|
||||
}
|
||||
/* RNDOUT and READ0 commands need a following byte */
|
||||
if (command == NAND_CMD_RNDOUT)
|
||||
cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_RNDOUTSTART, NAND_CTRL2);
|
||||
else if (command == NAND_CMD_READ0 && mtd->writesize > 512)
|
||||
cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_READSTART, NAND_CTRL2);
|
||||
|
||||
do_command:
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "dlen %x, ctl1 %x, ctl2 %x\n",
|
||||
cafe->datalen, ctl1, cafe_readl(cafe, NAND_CTRL2));
|
||||
|
||||
/* NB: The datasheet lies -- we really should be subtracting 1 here */
|
||||
cafe_writel(cafe, cafe->datalen, NAND_DATA_LEN);
|
||||
cafe_writel(cafe, 0x90000000, NAND_IRQ);
|
||||
if (usedma && (ctl1 & (3<<25))) {
|
||||
uint32_t dmactl = 0xc0000000 + cafe->datalen;
|
||||
/* If WR or RD bits set, set up DMA */
|
||||
if (ctl1 & (1<<26)) {
|
||||
/* It's a read */
|
||||
dmactl |= (1<<29);
|
||||
/* ... so it's done when the DMA is done, not just
|
||||
the command. */
|
||||
doneint = 0x10000000;
|
||||
}
|
||||
cafe_writel(cafe, dmactl, NAND_DMA_CTRL);
|
||||
}
|
||||
cafe->datalen = 0;
|
||||
|
||||
if (unlikely(regdebug)) {
|
||||
int i;
|
||||
printk("About to write command %08x to register 0\n", ctl1);
|
||||
for (i=4; i< 0x5c; i+=4)
|
||||
printk("Register %x: %08x\n", i, readl(cafe->mmio + i));
|
||||
}
|
||||
|
||||
cafe_writel(cafe, ctl1, NAND_CTRL1);
|
||||
/* Apply this short delay always to ensure that we do wait tWB in
|
||||
* any case on any machine. */
|
||||
ndelay(100);
|
||||
|
||||
if (1) {
|
||||
int c;
|
||||
uint32_t irqs;
|
||||
|
||||
for (c = 500000; c != 0; c--) {
|
||||
irqs = cafe_readl(cafe, NAND_IRQ);
|
||||
if (irqs & doneint)
|
||||
break;
|
||||
udelay(1);
|
||||
if (!(c % 100000))
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Wait for ready, IRQ %x\n", irqs);
|
||||
cpu_relax();
|
||||
}
|
||||
cafe_writel(cafe, doneint, NAND_IRQ);
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Command %x completed after %d usec, irqs %x (%x)\n",
|
||||
command, 500000-c, irqs, cafe_readl(cafe, NAND_IRQ));
|
||||
}
|
||||
|
||||
WARN_ON(cafe->ctl2 & (1<<30));
|
||||
|
||||
switch (command) {
|
||||
|
||||
case NAND_CMD_CACHEDPROG:
|
||||
case NAND_CMD_PAGEPROG:
|
||||
case NAND_CMD_ERASE1:
|
||||
case NAND_CMD_ERASE2:
|
||||
case NAND_CMD_SEQIN:
|
||||
case NAND_CMD_RNDIN:
|
||||
case NAND_CMD_STATUS:
|
||||
case NAND_CMD_RNDOUT:
|
||||
cafe_writel(cafe, cafe->ctl2, NAND_CTRL2);
|
||||
return;
|
||||
}
|
||||
nand_wait_ready(mtd);
|
||||
cafe_writel(cafe, cafe->ctl2, NAND_CTRL2);
|
||||
}
|
||||
|
||||
static void cafe_select_chip(struct mtd_info *mtd, int chipnr)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "select_chip %d\n", chipnr);
|
||||
|
||||
/* Mask the appropriate bit into the stored value of ctl1
|
||||
which will be used by cafe_nand_cmdfunc() */
|
||||
if (chipnr)
|
||||
cafe->ctl1 |= CTRL1_CHIPSELECT;
|
||||
else
|
||||
cafe->ctl1 &= ~CTRL1_CHIPSELECT;
|
||||
}
|
||||
|
||||
static irqreturn_t cafe_nand_interrupt(int irq, void *id)
|
||||
{
|
||||
struct mtd_info *mtd = id;
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
uint32_t irqs = cafe_readl(cafe, NAND_IRQ);
|
||||
cafe_writel(cafe, irqs & ~0x90000000, NAND_IRQ);
|
||||
if (!irqs)
|
||||
return IRQ_NONE;
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "irq, bits %x (%x)\n", irqs, cafe_readl(cafe, NAND_IRQ));
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void cafe_nand_bug(struct mtd_info *mtd)
|
||||
{
|
||||
BUG();
|
||||
}
|
||||
|
||||
static int cafe_nand_write_oob(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, int page)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
|
||||
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
||||
}
|
||||
|
||||
/* Don't use -- use nand_read_oob_std for now */
|
||||
static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int page)
|
||||
{
|
||||
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
||||
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* cafe_nand_read_page_syndrome - [REPLACEABLE] hardware ecc syndrome based page read
|
||||
* @mtd: mtd info structure
|
||||
* @chip: nand chip info structure
|
||||
* @buf: buffer to store read data
|
||||
* @oob_required: caller expects OOB data read to chip->oob_poi
|
||||
*
|
||||
* The hw generator calculates the error syndrome automatically. Therefore
|
||||
* we need a special oob layout and handling.
|
||||
*/
|
||||
static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
unsigned int max_bitflips = 0;
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "ECC result %08x SYN1,2 %08x\n",
|
||||
cafe_readl(cafe, NAND_ECC_RESULT),
|
||||
cafe_readl(cafe, NAND_ECC_SYN01));
|
||||
|
||||
chip->read_buf(mtd, buf, mtd->writesize);
|
||||
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) {
|
||||
unsigned short syn[8], pat[4];
|
||||
int pos[4];
|
||||
u8 *oob = chip->oob_poi;
|
||||
int i, n;
|
||||
|
||||
for (i=0; i<8; i+=2) {
|
||||
uint32_t tmp = cafe_readl(cafe, NAND_ECC_SYN01 + (i*2));
|
||||
syn[i] = cafe->rs->index_of[tmp & 0xfff];
|
||||
syn[i+1] = cafe->rs->index_of[(tmp >> 16) & 0xfff];
|
||||
}
|
||||
|
||||
n = decode_rs16(cafe->rs, NULL, NULL, 1367, syn, 0, pos, 0,
|
||||
pat);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int p = pos[i];
|
||||
|
||||
/* The 12-bit symbols are mapped to bytes here */
|
||||
|
||||
if (p > 1374) {
|
||||
/* out of range */
|
||||
n = -1374;
|
||||
} else if (p == 0) {
|
||||
/* high four bits do not correspond to data */
|
||||
if (pat[i] > 0xff)
|
||||
n = -2048;
|
||||
else
|
||||
buf[0] ^= pat[i];
|
||||
} else if (p == 1365) {
|
||||
buf[2047] ^= pat[i] >> 4;
|
||||
oob[0] ^= pat[i] << 4;
|
||||
} else if (p > 1365) {
|
||||
if ((p & 1) == 1) {
|
||||
oob[3*p/2 - 2048] ^= pat[i] >> 4;
|
||||
oob[3*p/2 - 2047] ^= pat[i] << 4;
|
||||
} else {
|
||||
oob[3*p/2 - 2049] ^= pat[i] >> 8;
|
||||
oob[3*p/2 - 2048] ^= pat[i];
|
||||
}
|
||||
} else if ((p & 1) == 1) {
|
||||
buf[3*p/2] ^= pat[i] >> 4;
|
||||
buf[3*p/2 + 1] ^= pat[i] << 4;
|
||||
} else {
|
||||
buf[3*p/2 - 1] ^= pat[i] >> 8;
|
||||
buf[3*p/2] ^= pat[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (n < 0) {
|
||||
dev_dbg(&cafe->pdev->dev, "Failed to correct ECC at %08x\n",
|
||||
cafe_readl(cafe, NAND_ADDR2) * 2048);
|
||||
for (i = 0; i < 0x5c; i += 4)
|
||||
printk("Register %x: %08x\n", i, readl(cafe->mmio + i));
|
||||
mtd->ecc_stats.failed++;
|
||||
} else {
|
||||
dev_dbg(&cafe->pdev->dev, "Corrected %d symbol errors\n", n);
|
||||
mtd->ecc_stats.corrected += n;
|
||||
max_bitflips = max_t(unsigned int, max_bitflips, n);
|
||||
}
|
||||
}
|
||||
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
static struct nand_ecclayout cafe_oobinfo_2048 = {
|
||||
.eccbytes = 14,
|
||||
.eccpos = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13},
|
||||
.oobfree = {{14, 50}}
|
||||
};
|
||||
|
||||
/* Ick. The BBT code really ought to be able to work this bit out
|
||||
for itself from the above, at least for the 2KiB case */
|
||||
static uint8_t cafe_bbt_pattern_2048[] = { 'B', 'b', 't', '0' };
|
||||
static uint8_t cafe_mirror_pattern_2048[] = { '1', 't', 'b', 'B' };
|
||||
|
||||
static uint8_t cafe_bbt_pattern_512[] = { 0xBB };
|
||||
static uint8_t cafe_mirror_pattern_512[] = { 0xBC };
|
||||
|
||||
|
||||
static struct nand_bbt_descr cafe_bbt_main_descr_2048 = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 14,
|
||||
.len = 4,
|
||||
.veroffs = 18,
|
||||
.maxblocks = 4,
|
||||
.pattern = cafe_bbt_pattern_2048
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr cafe_bbt_mirror_descr_2048 = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 14,
|
||||
.len = 4,
|
||||
.veroffs = 18,
|
||||
.maxblocks = 4,
|
||||
.pattern = cafe_mirror_pattern_2048
|
||||
};
|
||||
|
||||
static struct nand_ecclayout cafe_oobinfo_512 = {
|
||||
.eccbytes = 14,
|
||||
.eccpos = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13},
|
||||
.oobfree = {{14, 2}}
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr cafe_bbt_main_descr_512 = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 14,
|
||||
.len = 1,
|
||||
.veroffs = 15,
|
||||
.maxblocks = 4,
|
||||
.pattern = cafe_bbt_pattern_512
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr cafe_bbt_mirror_descr_512 = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 14,
|
||||
.len = 1,
|
||||
.veroffs = 15,
|
||||
.maxblocks = 4,
|
||||
.pattern = cafe_mirror_pattern_512
|
||||
};
|
||||
|
||||
|
||||
static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
chip->write_buf(mtd, buf, mtd->writesize);
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
/* Set up ECC autogeneration */
|
||||
cafe->ctl2 |= (1<<30);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cafe_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint32_t offset, int data_len, const uint8_t *buf,
|
||||
int oob_required, int page, int cached, int raw)
|
||||
{
|
||||
int status;
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
|
||||
|
||||
if (unlikely(raw))
|
||||
status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
|
||||
else
|
||||
status = chip->ecc.write_page(mtd, chip, buf, oob_required);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/*
|
||||
* Cached progamming disabled for now, Not sure if its worth the
|
||||
* trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
|
||||
*/
|
||||
cached = 0;
|
||||
|
||||
if (!cached || !(chip->options & NAND_CACHEPRG)) {
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
/*
|
||||
* See if operation failed and additional status checks are
|
||||
* available
|
||||
*/
|
||||
if ((status & NAND_STATUS_FAIL) && (chip->errstat))
|
||||
status = chip->errstat(mtd, chip, FL_WRITING, status,
|
||||
page);
|
||||
|
||||
if (status & NAND_STATUS_FAIL)
|
||||
return -EIO;
|
||||
} else {
|
||||
chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cafe_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* F_2[X]/(X**6+X+1) */
|
||||
static unsigned short gf64_mul(u8 a, u8 b)
|
||||
{
|
||||
u8 c;
|
||||
unsigned int i;
|
||||
|
||||
c = 0;
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (a & 1)
|
||||
c ^= b;
|
||||
a >>= 1;
|
||||
b <<= 1;
|
||||
if ((b & 0x40) != 0)
|
||||
b ^= 0x43;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/* F_64[X]/(X**2+X+A**-1) with A the generator of F_64[X] */
|
||||
static u16 gf4096_mul(u16 a, u16 b)
|
||||
{
|
||||
u8 ah, al, bh, bl, ch, cl;
|
||||
|
||||
ah = a >> 6;
|
||||
al = a & 0x3f;
|
||||
bh = b >> 6;
|
||||
bl = b & 0x3f;
|
||||
|
||||
ch = gf64_mul(ah ^ al, bh ^ bl) ^ gf64_mul(al, bl);
|
||||
cl = gf64_mul(gf64_mul(ah, bh), 0x21) ^ gf64_mul(al, bl);
|
||||
|
||||
return (ch << 6) ^ cl;
|
||||
}
|
||||
|
||||
static int cafe_mul(int x)
|
||||
{
|
||||
if (x == 0)
|
||||
return 1;
|
||||
return gf4096_mul(x, 0xe01);
|
||||
}
|
||||
|
||||
static int cafe_nand_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
struct cafe_priv *cafe;
|
||||
uint32_t ctrl;
|
||||
int err = 0;
|
||||
int old_dma;
|
||||
struct nand_buffers *nbuf;
|
||||
|
||||
/* Very old versions shared the same PCI ident for all three
|
||||
functions on the chip. Verify the class too... */
|
||||
if ((pdev->class >> 8) != PCI_CLASS_MEMORY_FLASH)
|
||||
return -ENODEV;
|
||||
|
||||
err = pci_enable_device(pdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
mtd = kzalloc(sizeof(*mtd) + sizeof(struct cafe_priv), GFP_KERNEL);
|
||||
if (!mtd)
|
||||
return -ENOMEM;
|
||||
cafe = (void *)(&mtd[1]);
|
||||
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
mtd->priv = cafe;
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
cafe->pdev = pdev;
|
||||
cafe->mmio = pci_iomap(pdev, 0, 0);
|
||||
if (!cafe->mmio) {
|
||||
dev_warn(&pdev->dev, "failed to iomap\n");
|
||||
err = -ENOMEM;
|
||||
goto out_free_mtd;
|
||||
}
|
||||
|
||||
cafe->rs = init_rs_non_canonical(12, &cafe_mul, 0, 1, 8);
|
||||
if (!cafe->rs) {
|
||||
err = -ENOMEM;
|
||||
goto out_ior;
|
||||
}
|
||||
|
||||
cafe->nand.cmdfunc = cafe_nand_cmdfunc;
|
||||
cafe->nand.dev_ready = cafe_device_ready;
|
||||
cafe->nand.read_byte = cafe_read_byte;
|
||||
cafe->nand.read_buf = cafe_read_buf;
|
||||
cafe->nand.write_buf = cafe_write_buf;
|
||||
cafe->nand.select_chip = cafe_select_chip;
|
||||
|
||||
cafe->nand.chip_delay = 0;
|
||||
|
||||
/* Enable the following for a flash based bad block table */
|
||||
cafe->nand.bbt_options = NAND_BBT_USE_FLASH;
|
||||
cafe->nand.options = NAND_OWN_BUFFERS;
|
||||
|
||||
if (skipbbt) {
|
||||
cafe->nand.options |= NAND_SKIP_BBTSCAN;
|
||||
cafe->nand.block_bad = cafe_nand_block_bad;
|
||||
}
|
||||
|
||||
if (numtimings && numtimings != 3) {
|
||||
dev_warn(&cafe->pdev->dev, "%d timing register values ignored; precisely three are required\n", numtimings);
|
||||
}
|
||||
|
||||
if (numtimings == 3) {
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Using provided timings (%08x %08x %08x)\n",
|
||||
timing[0], timing[1], timing[2]);
|
||||
} else {
|
||||
timing[0] = cafe_readl(cafe, NAND_TIMING1);
|
||||
timing[1] = cafe_readl(cafe, NAND_TIMING2);
|
||||
timing[2] = cafe_readl(cafe, NAND_TIMING3);
|
||||
|
||||
if (timing[0] | timing[1] | timing[2]) {
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Timing registers already set (%08x %08x %08x)\n",
|
||||
timing[0], timing[1], timing[2]);
|
||||
} else {
|
||||
dev_warn(&cafe->pdev->dev, "Timing registers unset; using most conservative defaults\n");
|
||||
timing[0] = timing[1] = timing[2] = 0xffffffff;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start off by resetting the NAND controller completely */
|
||||
cafe_writel(cafe, 1, NAND_RESET);
|
||||
cafe_writel(cafe, 0, NAND_RESET);
|
||||
|
||||
cafe_writel(cafe, timing[0], NAND_TIMING1);
|
||||
cafe_writel(cafe, timing[1], NAND_TIMING2);
|
||||
cafe_writel(cafe, timing[2], NAND_TIMING3);
|
||||
|
||||
cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK);
|
||||
err = request_irq(pdev->irq, &cafe_nand_interrupt, IRQF_SHARED,
|
||||
"CAFE NAND", mtd);
|
||||
if (err) {
|
||||
dev_warn(&pdev->dev, "Could not register IRQ %d\n", pdev->irq);
|
||||
goto out_ior;
|
||||
}
|
||||
|
||||
/* Disable master reset, enable NAND clock */
|
||||
ctrl = cafe_readl(cafe, GLOBAL_CTRL);
|
||||
ctrl &= 0xffffeff0;
|
||||
ctrl |= 0x00007000;
|
||||
cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, 0, NAND_DMA_CTRL);
|
||||
|
||||
cafe_writel(cafe, 0x7006, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, 0x700a, GLOBAL_CTRL);
|
||||
|
||||
/* Enable NAND IRQ in global IRQ mask register */
|
||||
cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK);
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Control %x, IRQ mask %x\n",
|
||||
cafe_readl(cafe, GLOBAL_CTRL),
|
||||
cafe_readl(cafe, GLOBAL_IRQ_MASK));
|
||||
|
||||
/* Do not use the DMA for the nand_scan_ident() */
|
||||
old_dma = usedma;
|
||||
usedma = 0;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan_ident(mtd, 2, NULL)) {
|
||||
err = -ENXIO;
|
||||
goto out_irq;
|
||||
}
|
||||
|
||||
cafe->dmabuf = dma_alloc_coherent(&cafe->pdev->dev,
|
||||
2112 + sizeof(struct nand_buffers) +
|
||||
mtd->writesize + mtd->oobsize,
|
||||
&cafe->dmaaddr, GFP_KERNEL);
|
||||
if (!cafe->dmabuf) {
|
||||
err = -ENOMEM;
|
||||
goto out_irq;
|
||||
}
|
||||
cafe->nand.buffers = nbuf = (void *)cafe->dmabuf + 2112;
|
||||
|
||||
/* Set up DMA address */
|
||||
cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0);
|
||||
if (sizeof(cafe->dmaaddr) > 4)
|
||||
/* Shift in two parts to shut the compiler up */
|
||||
cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1);
|
||||
else
|
||||
cafe_writel(cafe, 0, NAND_DMA_ADDR1);
|
||||
|
||||
cafe_dev_dbg(&cafe->pdev->dev, "Set DMA address to %x (virt %p)\n",
|
||||
cafe_readl(cafe, NAND_DMA_ADDR0), cafe->dmabuf);
|
||||
|
||||
/* this driver does not need the @ecccalc and @ecccode */
|
||||
nbuf->ecccalc = NULL;
|
||||
nbuf->ecccode = NULL;
|
||||
nbuf->databuf = (uint8_t *)(nbuf + 1);
|
||||
|
||||
/* Restore the DMA flag */
|
||||
usedma = old_dma;
|
||||
|
||||
cafe->ctl2 = 1<<27; /* Reed-Solomon ECC */
|
||||
if (mtd->writesize == 2048)
|
||||
cafe->ctl2 |= 1<<29; /* 2KiB page size */
|
||||
|
||||
/* Set up ECC according to the type of chip we found */
|
||||
if (mtd->writesize == 2048) {
|
||||
cafe->nand.ecc.layout = &cafe_oobinfo_2048;
|
||||
cafe->nand.bbt_td = &cafe_bbt_main_descr_2048;
|
||||
cafe->nand.bbt_md = &cafe_bbt_mirror_descr_2048;
|
||||
} else if (mtd->writesize == 512) {
|
||||
cafe->nand.ecc.layout = &cafe_oobinfo_512;
|
||||
cafe->nand.bbt_td = &cafe_bbt_main_descr_512;
|
||||
cafe->nand.bbt_md = &cafe_bbt_mirror_descr_512;
|
||||
} else {
|
||||
printk(KERN_WARNING "Unexpected NAND flash writesize %d. Aborting\n",
|
||||
mtd->writesize);
|
||||
goto out_free_dma;
|
||||
}
|
||||
cafe->nand.ecc.mode = NAND_ECC_HW_SYNDROME;
|
||||
cafe->nand.ecc.size = mtd->writesize;
|
||||
cafe->nand.ecc.bytes = 14;
|
||||
cafe->nand.ecc.strength = 4;
|
||||
cafe->nand.ecc.hwctl = (void *)cafe_nand_bug;
|
||||
cafe->nand.ecc.calculate = (void *)cafe_nand_bug;
|
||||
cafe->nand.ecc.correct = (void *)cafe_nand_bug;
|
||||
cafe->nand.write_page = cafe_nand_write_page;
|
||||
cafe->nand.ecc.write_page = cafe_nand_write_page_lowlevel;
|
||||
cafe->nand.ecc.write_oob = cafe_nand_write_oob;
|
||||
cafe->nand.ecc.read_page = cafe_nand_read_page;
|
||||
cafe->nand.ecc.read_oob = cafe_nand_read_oob;
|
||||
|
||||
err = nand_scan_tail(mtd);
|
||||
if (err)
|
||||
goto out_free_dma;
|
||||
|
||||
pci_set_drvdata(pdev, mtd);
|
||||
|
||||
mtd->name = "cafe_nand";
|
||||
mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0);
|
||||
|
||||
goto out;
|
||||
|
||||
out_free_dma:
|
||||
dma_free_coherent(&cafe->pdev->dev,
|
||||
2112 + sizeof(struct nand_buffers) +
|
||||
mtd->writesize + mtd->oobsize,
|
||||
cafe->dmabuf, cafe->dmaaddr);
|
||||
out_irq:
|
||||
/* Disable NAND IRQ in global IRQ mask register */
|
||||
cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK);
|
||||
free_irq(pdev->irq, mtd);
|
||||
out_ior:
|
||||
pci_iounmap(pdev, cafe->mmio);
|
||||
out_free_mtd:
|
||||
kfree(mtd);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cafe_nand_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct mtd_info *mtd = pci_get_drvdata(pdev);
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
/* Disable NAND IRQ in global IRQ mask register */
|
||||
cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK);
|
||||
free_irq(pdev->irq, mtd);
|
||||
nand_release(mtd);
|
||||
free_rs(cafe->rs);
|
||||
pci_iounmap(pdev, cafe->mmio);
|
||||
dma_free_coherent(&cafe->pdev->dev,
|
||||
2112 + sizeof(struct nand_buffers) +
|
||||
mtd->writesize + mtd->oobsize,
|
||||
cafe->dmabuf, cafe->dmaaddr);
|
||||
kfree(mtd);
|
||||
}
|
||||
|
||||
static const struct pci_device_id cafe_nand_tbl[] = {
|
||||
{ PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND,
|
||||
PCI_ANY_ID, PCI_ANY_ID },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, cafe_nand_tbl);
|
||||
|
||||
static int cafe_nand_resume(struct pci_dev *pdev)
|
||||
{
|
||||
uint32_t ctrl;
|
||||
struct mtd_info *mtd = pci_get_drvdata(pdev);
|
||||
struct cafe_priv *cafe = mtd->priv;
|
||||
|
||||
/* Start off by resetting the NAND controller completely */
|
||||
cafe_writel(cafe, 1, NAND_RESET);
|
||||
cafe_writel(cafe, 0, NAND_RESET);
|
||||
cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK);
|
||||
|
||||
/* Restore timing configuration */
|
||||
cafe_writel(cafe, timing[0], NAND_TIMING1);
|
||||
cafe_writel(cafe, timing[1], NAND_TIMING2);
|
||||
cafe_writel(cafe, timing[2], NAND_TIMING3);
|
||||
|
||||
/* Disable master reset, enable NAND clock */
|
||||
ctrl = cafe_readl(cafe, GLOBAL_CTRL);
|
||||
ctrl &= 0xffffeff0;
|
||||
ctrl |= 0x00007000;
|
||||
cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, 0, NAND_DMA_CTRL);
|
||||
cafe_writel(cafe, 0x7006, GLOBAL_CTRL);
|
||||
cafe_writel(cafe, 0x700a, GLOBAL_CTRL);
|
||||
|
||||
/* Set up DMA address */
|
||||
cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0);
|
||||
if (sizeof(cafe->dmaaddr) > 4)
|
||||
/* Shift in two parts to shut the compiler up */
|
||||
cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1);
|
||||
else
|
||||
cafe_writel(cafe, 0, NAND_DMA_ADDR1);
|
||||
|
||||
/* Enable NAND IRQ in global IRQ mask register */
|
||||
cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_driver cafe_nand_pci_driver = {
|
||||
.name = "CAFÉ NAND",
|
||||
.id_table = cafe_nand_tbl,
|
||||
.probe = cafe_nand_probe,
|
||||
.remove = cafe_nand_remove,
|
||||
.resume = cafe_nand_resume,
|
||||
};
|
||||
|
||||
module_pci_driver(cafe_nand_pci_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
||||
MODULE_DESCRIPTION("NAND flash driver for OLPC CAFÉ chip");
|
||||
250
drivers/mtd/nand/cmx270_nand.c
Normal file
250
drivers/mtd/nand/cmx270_nand.c
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* linux/drivers/mtd/nand/cmx270-nand.c
|
||||
*
|
||||
* Copyright (C) 2006 Compulab, Ltd.
|
||||
* Mike Rapoport <mike@compulab.co.il>
|
||||
*
|
||||
* Derived from drivers/mtd/nand/h1910.c
|
||||
* Copyright (C) 2002 Marius Gröger (mag@sysgo.de)
|
||||
* Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de)
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Overview:
|
||||
* This is a device driver for the NAND flash device found on the
|
||||
* CM-X270 board.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include <mach/pxa2xx-regs.h>
|
||||
|
||||
#define GPIO_NAND_CS (11)
|
||||
#define GPIO_NAND_RB (89)
|
||||
|
||||
/* MTD structure for CM-X270 board */
|
||||
static struct mtd_info *cmx270_nand_mtd;
|
||||
|
||||
/* remaped IO address of the device */
|
||||
static void __iomem *cmx270_nand_io;
|
||||
|
||||
/*
|
||||
* Define static partitions for flash device
|
||||
*/
|
||||
static struct mtd_partition partition_info[] = {
|
||||
[0] = {
|
||||
.name = "cmx270-0",
|
||||
.offset = 0,
|
||||
.size = MTDPART_SIZ_FULL
|
||||
}
|
||||
};
|
||||
#define NUM_PARTITIONS (ARRAY_SIZE(partition_info))
|
||||
|
||||
static u_char cmx270_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
return (readl(this->IO_ADDR_R) >> 16);
|
||||
}
|
||||
|
||||
static void cmx270_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
writel((*buf++ << 16), this->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static void cmx270_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
*buf++ = readl(this->IO_ADDR_R) >> 16;
|
||||
}
|
||||
|
||||
static inline void nand_cs_on(void)
|
||||
{
|
||||
gpio_set_value(GPIO_NAND_CS, 0);
|
||||
}
|
||||
|
||||
static void nand_cs_off(void)
|
||||
{
|
||||
dsb();
|
||||
|
||||
gpio_set_value(GPIO_NAND_CS, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* hardware specific access to control-lines
|
||||
*/
|
||||
static void cmx270_hwcontrol(struct mtd_info *mtd, int dat,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip* this = mtd->priv;
|
||||
unsigned int nandaddr = (unsigned int)this->IO_ADDR_W;
|
||||
|
||||
dsb();
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if ( ctrl & NAND_ALE )
|
||||
nandaddr |= (1 << 3);
|
||||
else
|
||||
nandaddr &= ~(1 << 3);
|
||||
if ( ctrl & NAND_CLE )
|
||||
nandaddr |= (1 << 2);
|
||||
else
|
||||
nandaddr &= ~(1 << 2);
|
||||
if ( ctrl & NAND_NCE )
|
||||
nand_cs_on();
|
||||
else
|
||||
nand_cs_off();
|
||||
}
|
||||
|
||||
dsb();
|
||||
this->IO_ADDR_W = (void __iomem*)nandaddr;
|
||||
if (dat != NAND_CMD_NONE)
|
||||
writel((dat << 16), this->IO_ADDR_W);
|
||||
|
||||
dsb();
|
||||
}
|
||||
|
||||
/*
|
||||
* read device ready pin
|
||||
*/
|
||||
static int cmx270_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
dsb();
|
||||
|
||||
return (gpio_get_value(GPIO_NAND_RB));
|
||||
}
|
||||
|
||||
/*
|
||||
* Main initialization routine
|
||||
*/
|
||||
static int __init cmx270_init(void)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
int ret;
|
||||
|
||||
if (!(machine_is_armcore() && cpu_is_pxa27x()))
|
||||
return -ENODEV;
|
||||
|
||||
ret = gpio_request(GPIO_NAND_CS, "NAND CS");
|
||||
if (ret) {
|
||||
pr_warning("CM-X270: failed to request NAND CS gpio\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpio_direction_output(GPIO_NAND_CS, 1);
|
||||
|
||||
ret = gpio_request(GPIO_NAND_RB, "NAND R/B");
|
||||
if (ret) {
|
||||
pr_warning("CM-X270: failed to request NAND R/B gpio\n");
|
||||
goto err_gpio_request;
|
||||
}
|
||||
|
||||
gpio_direction_input(GPIO_NAND_RB);
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
cmx270_nand_mtd = kzalloc(sizeof(struct mtd_info) +
|
||||
sizeof(struct nand_chip),
|
||||
GFP_KERNEL);
|
||||
if (!cmx270_nand_mtd) {
|
||||
ret = -ENOMEM;
|
||||
goto err_kzalloc;
|
||||
}
|
||||
|
||||
cmx270_nand_io = ioremap(PXA_CS1_PHYS, 12);
|
||||
if (!cmx270_nand_io) {
|
||||
pr_debug("Unable to ioremap NAND device\n");
|
||||
ret = -EINVAL;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *)(&cmx270_nand_mtd[1]);
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
cmx270_nand_mtd->owner = THIS_MODULE;
|
||||
cmx270_nand_mtd->priv = this;
|
||||
|
||||
/* insert callbacks */
|
||||
this->IO_ADDR_R = cmx270_nand_io;
|
||||
this->IO_ADDR_W = cmx270_nand_io;
|
||||
this->cmd_ctrl = cmx270_hwcontrol;
|
||||
this->dev_ready = cmx270_device_ready;
|
||||
|
||||
/* 15 us command delay time */
|
||||
this->chip_delay = 20;
|
||||
this->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
/* read/write functions */
|
||||
this->read_byte = cmx270_read_byte;
|
||||
this->read_buf = cmx270_read_buf;
|
||||
this->write_buf = cmx270_write_buf;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan (cmx270_nand_mtd, 1)) {
|
||||
pr_notice("No NAND device\n");
|
||||
ret = -ENXIO;
|
||||
goto err_scan;
|
||||
}
|
||||
|
||||
/* Register the partitions */
|
||||
ret = mtd_device_parse_register(cmx270_nand_mtd, NULL, NULL,
|
||||
partition_info, NUM_PARTITIONS);
|
||||
if (ret)
|
||||
goto err_scan;
|
||||
|
||||
/* Return happy */
|
||||
return 0;
|
||||
|
||||
err_scan:
|
||||
iounmap(cmx270_nand_io);
|
||||
err_ioremap:
|
||||
kfree(cmx270_nand_mtd);
|
||||
err_kzalloc:
|
||||
gpio_free(GPIO_NAND_RB);
|
||||
err_gpio_request:
|
||||
gpio_free(GPIO_NAND_CS);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
module_init(cmx270_init);
|
||||
|
||||
/*
|
||||
* Clean up routine
|
||||
*/
|
||||
static void __exit cmx270_cleanup(void)
|
||||
{
|
||||
/* Release resources, unregister device */
|
||||
nand_release(cmx270_nand_mtd);
|
||||
|
||||
gpio_free(GPIO_NAND_RB);
|
||||
gpio_free(GPIO_NAND_CS);
|
||||
|
||||
iounmap(cmx270_nand_io);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree (cmx270_nand_mtd);
|
||||
}
|
||||
module_exit(cmx270_cleanup);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
|
||||
MODULE_DESCRIPTION("NAND flash driver for Compulab CM-X270 Module");
|
||||
354
drivers/mtd/nand/cs553x_nand.c
Normal file
354
drivers/mtd/nand/cs553x_nand.c
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* drivers/mtd/nand/cs553x_nand.c
|
||||
*
|
||||
* (C) 2005, 2006 Red Hat Inc.
|
||||
*
|
||||
* Author: David Woodhouse <dwmw2@infradead.org>
|
||||
* Tom Sylla <tom.sylla@amd.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Overview:
|
||||
* This is a device driver for the NAND flash controller found on
|
||||
* the AMD CS5535/CS5536 companion chipsets for the Geode processor.
|
||||
* mtd-id for command line partitioning is cs553x_nand_cs[0-3]
|
||||
* where 0-3 reflects the chip select for NAND.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/msr.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#define NR_CS553X_CONTROLLERS 4
|
||||
|
||||
#define MSR_DIVIL_GLD_CAP 0x51400000 /* DIVIL capabilitiies */
|
||||
#define CAP_CS5535 0x2df000ULL
|
||||
#define CAP_CS5536 0x5df500ULL
|
||||
|
||||
/* NAND Timing MSRs */
|
||||
#define MSR_NANDF_DATA 0x5140001b /* NAND Flash Data Timing MSR */
|
||||
#define MSR_NANDF_CTL 0x5140001c /* NAND Flash Control Timing */
|
||||
#define MSR_NANDF_RSVD 0x5140001d /* Reserved */
|
||||
|
||||
/* NAND BAR MSRs */
|
||||
#define MSR_DIVIL_LBAR_FLSH0 0x51400010 /* Flash Chip Select 0 */
|
||||
#define MSR_DIVIL_LBAR_FLSH1 0x51400011 /* Flash Chip Select 1 */
|
||||
#define MSR_DIVIL_LBAR_FLSH2 0x51400012 /* Flash Chip Select 2 */
|
||||
#define MSR_DIVIL_LBAR_FLSH3 0x51400013 /* Flash Chip Select 3 */
|
||||
/* Each made up of... */
|
||||
#define FLSH_LBAR_EN (1ULL<<32)
|
||||
#define FLSH_NOR_NAND (1ULL<<33) /* 1 for NAND */
|
||||
#define FLSH_MEM_IO (1ULL<<34) /* 1 for MMIO */
|
||||
/* I/O BARs have BASE_ADDR in bits 15:4, IO_MASK in 47:36 */
|
||||
/* MMIO BARs have BASE_ADDR in bits 31:12, MEM_MASK in 63:44 */
|
||||
|
||||
/* Pin function selection MSR (IDE vs. flash on the IDE pins) */
|
||||
#define MSR_DIVIL_BALL_OPTS 0x51400015
|
||||
#define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */
|
||||
|
||||
/* Registers within the NAND flash controller BAR -- memory mapped */
|
||||
#define MM_NAND_DATA 0x00 /* 0 to 0x7ff, in fact */
|
||||
#define MM_NAND_CTL 0x800 /* Any even address 0x800-0x80e */
|
||||
#define MM_NAND_IO 0x801 /* Any odd address 0x801-0x80f */
|
||||
#define MM_NAND_STS 0x810
|
||||
#define MM_NAND_ECC_LSB 0x811
|
||||
#define MM_NAND_ECC_MSB 0x812
|
||||
#define MM_NAND_ECC_COL 0x813
|
||||
#define MM_NAND_LAC 0x814
|
||||
#define MM_NAND_ECC_CTL 0x815
|
||||
|
||||
/* Registers within the NAND flash controller BAR -- I/O mapped */
|
||||
#define IO_NAND_DATA 0x00 /* 0 to 3, in fact */
|
||||
#define IO_NAND_CTL 0x04
|
||||
#define IO_NAND_IO 0x05
|
||||
#define IO_NAND_STS 0x06
|
||||
#define IO_NAND_ECC_CTL 0x08
|
||||
#define IO_NAND_ECC_LSB 0x09
|
||||
#define IO_NAND_ECC_MSB 0x0a
|
||||
#define IO_NAND_ECC_COL 0x0b
|
||||
#define IO_NAND_LAC 0x0c
|
||||
|
||||
#define CS_NAND_CTL_DIST_EN (1<<4) /* Enable NAND Distract interrupt */
|
||||
#define CS_NAND_CTL_RDY_INT_MASK (1<<3) /* Enable RDY/BUSY# interrupt */
|
||||
#define CS_NAND_CTL_ALE (1<<2)
|
||||
#define CS_NAND_CTL_CLE (1<<1)
|
||||
#define CS_NAND_CTL_CE (1<<0) /* Keep low; 1 to reset */
|
||||
|
||||
#define CS_NAND_STS_FLASH_RDY (1<<3)
|
||||
#define CS_NAND_CTLR_BUSY (1<<2)
|
||||
#define CS_NAND_CMD_COMP (1<<1)
|
||||
#define CS_NAND_DIST_ST (1<<0)
|
||||
|
||||
#define CS_NAND_ECC_PARITY (1<<2)
|
||||
#define CS_NAND_ECC_CLRECC (1<<1)
|
||||
#define CS_NAND_ECC_ENECC (1<<0)
|
||||
|
||||
static void cs553x_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
while (unlikely(len > 0x800)) {
|
||||
memcpy_fromio(buf, this->IO_ADDR_R, 0x800);
|
||||
buf += 0x800;
|
||||
len -= 0x800;
|
||||
}
|
||||
memcpy_fromio(buf, this->IO_ADDR_R, len);
|
||||
}
|
||||
|
||||
static void cs553x_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
while (unlikely(len > 0x800)) {
|
||||
memcpy_toio(this->IO_ADDR_R, buf, 0x800);
|
||||
buf += 0x800;
|
||||
len -= 0x800;
|
||||
}
|
||||
memcpy_toio(this->IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
static unsigned char cs553x_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
return readb(this->IO_ADDR_R);
|
||||
}
|
||||
|
||||
static void cs553x_write_byte(struct mtd_info *mtd, u_char byte)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
int i = 100000;
|
||||
|
||||
while (i && readb(this->IO_ADDR_R + MM_NAND_STS) & CS_NAND_CTLR_BUSY) {
|
||||
udelay(1);
|
||||
i--;
|
||||
}
|
||||
writeb(byte, this->IO_ADDR_W + 0x801);
|
||||
}
|
||||
|
||||
static void cs553x_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *mmio_base = this->IO_ADDR_R;
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
unsigned char ctl = (ctrl & ~NAND_CTRL_CHANGE ) ^ 0x01;
|
||||
writeb(ctl, mmio_base + MM_NAND_CTL);
|
||||
}
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
cs553x_write_byte(mtd, cmd);
|
||||
}
|
||||
|
||||
static int cs553x_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *mmio_base = this->IO_ADDR_R;
|
||||
unsigned char foo = readb(mmio_base + MM_NAND_STS);
|
||||
|
||||
return (foo & CS_NAND_STS_FLASH_RDY) && !(foo & CS_NAND_CTLR_BUSY);
|
||||
}
|
||||
|
||||
static void cs_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *mmio_base = this->IO_ADDR_R;
|
||||
|
||||
writeb(0x07, mmio_base + MM_NAND_ECC_CTL);
|
||||
}
|
||||
|
||||
static int cs_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
|
||||
{
|
||||
uint32_t ecc;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
void __iomem *mmio_base = this->IO_ADDR_R;
|
||||
|
||||
ecc = readl(mmio_base + MM_NAND_STS);
|
||||
|
||||
ecc_code[1] = ecc >> 8;
|
||||
ecc_code[0] = ecc >> 16;
|
||||
ecc_code[2] = ecc >> 24;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mtd_info *cs553x_mtd[4];
|
||||
|
||||
static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
|
||||
{
|
||||
int err = 0;
|
||||
struct nand_chip *this;
|
||||
struct mtd_info *new_mtd;
|
||||
|
||||
printk(KERN_NOTICE "Probing CS553x NAND controller CS#%d at %sIO 0x%08lx\n", cs, mmio?"MM":"P", adr);
|
||||
|
||||
if (!mmio) {
|
||||
printk(KERN_NOTICE "PIO mode not yet implemented for CS553X NAND controller\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
new_mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
|
||||
if (!new_mtd) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *)(&new_mtd[1]);
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
new_mtd->priv = this;
|
||||
new_mtd->owner = THIS_MODULE;
|
||||
|
||||
/* map physical address */
|
||||
this->IO_ADDR_R = this->IO_ADDR_W = ioremap(adr, 4096);
|
||||
if (!this->IO_ADDR_R) {
|
||||
printk(KERN_WARNING "ioremap cs553x NAND @0x%08lx failed\n", adr);
|
||||
err = -EIO;
|
||||
goto out_mtd;
|
||||
}
|
||||
|
||||
this->cmd_ctrl = cs553x_hwcontrol;
|
||||
this->dev_ready = cs553x_device_ready;
|
||||
this->read_byte = cs553x_read_byte;
|
||||
this->read_buf = cs553x_read_buf;
|
||||
this->write_buf = cs553x_write_buf;
|
||||
|
||||
this->chip_delay = 0;
|
||||
|
||||
this->ecc.mode = NAND_ECC_HW;
|
||||
this->ecc.size = 256;
|
||||
this->ecc.bytes = 3;
|
||||
this->ecc.hwctl = cs_enable_hwecc;
|
||||
this->ecc.calculate = cs_calculate_ecc;
|
||||
this->ecc.correct = nand_correct_data;
|
||||
this->ecc.strength = 1;
|
||||
|
||||
/* Enable the following for a flash based bad block table */
|
||||
this->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan(new_mtd, 1)) {
|
||||
err = -ENXIO;
|
||||
goto out_ior;
|
||||
}
|
||||
|
||||
new_mtd->name = kasprintf(GFP_KERNEL, "cs553x_nand_cs%d", cs);
|
||||
|
||||
cs553x_mtd[cs] = new_mtd;
|
||||
goto out;
|
||||
|
||||
out_ior:
|
||||
iounmap(this->IO_ADDR_R);
|
||||
out_mtd:
|
||||
kfree(new_mtd);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int is_geode(void)
|
||||
{
|
||||
/* These are the CPUs which will have a CS553[56] companion chip */
|
||||
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
|
||||
boot_cpu_data.x86 == 5 &&
|
||||
boot_cpu_data.x86_model == 10)
|
||||
return 1; /* Geode LX */
|
||||
|
||||
if ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC ||
|
||||
boot_cpu_data.x86_vendor == X86_VENDOR_CYRIX) &&
|
||||
boot_cpu_data.x86 == 5 &&
|
||||
boot_cpu_data.x86_model == 5)
|
||||
return 1; /* Geode GX (née GX2) */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init cs553x_init(void)
|
||||
{
|
||||
int err = -ENXIO;
|
||||
int i;
|
||||
uint64_t val;
|
||||
|
||||
/* If the CPU isn't a Geode GX or LX, abort */
|
||||
if (!is_geode())
|
||||
return -ENXIO;
|
||||
|
||||
/* If it doesn't have the CS553[56], abort */
|
||||
rdmsrl(MSR_DIVIL_GLD_CAP, val);
|
||||
val &= ~0xFFULL;
|
||||
if (val != CAP_CS5535 && val != CAP_CS5536)
|
||||
return -ENXIO;
|
||||
|
||||
/* If it doesn't have the NAND controller enabled, abort */
|
||||
rdmsrl(MSR_DIVIL_BALL_OPTS, val);
|
||||
if (val & PIN_OPT_IDE) {
|
||||
printk(KERN_INFO "CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
|
||||
rdmsrl(MSR_DIVIL_LBAR_FLSH0 + i, val);
|
||||
|
||||
if ((val & (FLSH_LBAR_EN|FLSH_NOR_NAND)) == (FLSH_LBAR_EN|FLSH_NOR_NAND))
|
||||
err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
/* Register all devices together here. This means we can easily hack it to
|
||||
do mtdconcat etc. if we want to. */
|
||||
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
|
||||
if (cs553x_mtd[i]) {
|
||||
/* If any devices registered, return success. Else the last error. */
|
||||
mtd_device_parse_register(cs553x_mtd[i], NULL, NULL,
|
||||
NULL, 0);
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
module_init(cs553x_init);
|
||||
|
||||
static void __exit cs553x_cleanup(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
|
||||
struct mtd_info *mtd = cs553x_mtd[i];
|
||||
struct nand_chip *this;
|
||||
void __iomem *mmio_base;
|
||||
|
||||
if (!mtd)
|
||||
continue;
|
||||
|
||||
this = cs553x_mtd[i]->priv;
|
||||
mmio_base = this->IO_ADDR_R;
|
||||
|
||||
/* Release resources, unregister device */
|
||||
nand_release(cs553x_mtd[i]);
|
||||
kfree(cs553x_mtd[i]->name);
|
||||
cs553x_mtd[i] = NULL;
|
||||
|
||||
/* unmap physical address */
|
||||
iounmap(mmio_base);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(mtd);
|
||||
}
|
||||
}
|
||||
|
||||
module_exit(cs553x_cleanup);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
||||
MODULE_DESCRIPTION("NAND controller driver for AMD CS5535/CS5536 companion chip");
|
||||
884
drivers/mtd/nand/davinci_nand.c
Normal file
884
drivers/mtd/nand/davinci_nand.c
Normal file
|
|
@ -0,0 +1,884 @@
|
|||
/*
|
||||
* davinci_nand.c - NAND Flash Driver for DaVinci family chips
|
||||
*
|
||||
* Copyright © 2006 Texas Instruments.
|
||||
*
|
||||
* Port to 2.6.23 Copyright © 2008 by:
|
||||
* Sander Huijsen <Shuijsen@optelecom-nkf.com>
|
||||
* Troy Kisky <troy.kisky@boundarydevices.com>
|
||||
* Dirk Behme <Dirk.Behme@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mtd.h>
|
||||
|
||||
#include <linux/platform_data/mtd-davinci.h>
|
||||
#include <linux/platform_data/mtd-davinci-aemif.h>
|
||||
|
||||
/*
|
||||
* This is a device driver for the NAND flash controller found on the
|
||||
* various DaVinci family chips. It handles up to four SoC chipselects,
|
||||
* and some flavors of secondary chipselect (e.g. based on A12) as used
|
||||
* with multichip packages.
|
||||
*
|
||||
* The 1-bit ECC hardware is supported, as well as the newer 4-bit ECC
|
||||
* available on chips like the DM355 and OMAP-L137 and needed with the
|
||||
* more error-prone MLC NAND chips.
|
||||
*
|
||||
* This driver assumes EM_WAIT connects all the NAND devices' RDY/nBUSY
|
||||
* outputs in a "wire-AND" configuration, with no per-chip signals.
|
||||
*/
|
||||
struct davinci_nand_info {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
struct nand_ecclayout ecclayout;
|
||||
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
|
||||
bool is_readmode;
|
||||
|
||||
void __iomem *base;
|
||||
void __iomem *vaddr;
|
||||
|
||||
uint32_t ioaddr;
|
||||
uint32_t current_cs;
|
||||
|
||||
uint32_t mask_chipsel;
|
||||
uint32_t mask_ale;
|
||||
uint32_t mask_cle;
|
||||
|
||||
uint32_t core_chipsel;
|
||||
|
||||
struct davinci_aemif_timing *timing;
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(davinci_nand_lock);
|
||||
static bool ecc4_busy;
|
||||
|
||||
#define to_davinci_nand(m) container_of(m, struct davinci_nand_info, mtd)
|
||||
|
||||
|
||||
static inline unsigned int davinci_nand_readl(struct davinci_nand_info *info,
|
||||
int offset)
|
||||
{
|
||||
return __raw_readl(info->base + offset);
|
||||
}
|
||||
|
||||
static inline void davinci_nand_writel(struct davinci_nand_info *info,
|
||||
int offset, unsigned long value)
|
||||
{
|
||||
__raw_writel(value, info->base + offset);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Access to hardware control lines: ALE, CLE, secondary chipselect.
|
||||
*/
|
||||
|
||||
static void nand_davinci_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
uint32_t addr = info->current_cs;
|
||||
struct nand_chip *nand = mtd->priv;
|
||||
|
||||
/* Did the control lines change? */
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if ((ctrl & NAND_CTRL_CLE) == NAND_CTRL_CLE)
|
||||
addr |= info->mask_cle;
|
||||
else if ((ctrl & NAND_CTRL_ALE) == NAND_CTRL_ALE)
|
||||
addr |= info->mask_ale;
|
||||
|
||||
nand->IO_ADDR_W = (void __iomem __force *)addr;
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
iowrite8(cmd, nand->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static void nand_davinci_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
uint32_t addr = info->ioaddr;
|
||||
|
||||
/* maybe kick in a second chipselect */
|
||||
if (chip > 0)
|
||||
addr |= info->mask_chipsel;
|
||||
info->current_cs = addr;
|
||||
|
||||
info->chip.IO_ADDR_W = (void __iomem __force *)addr;
|
||||
info->chip.IO_ADDR_R = info->chip.IO_ADDR_W;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* 1-bit hardware ECC ... context maintained for each core chipselect
|
||||
*/
|
||||
|
||||
static inline uint32_t nand_davinci_readecc_1bit(struct mtd_info *mtd)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
|
||||
return davinci_nand_readl(info, NANDF1ECC_OFFSET
|
||||
+ 4 * info->core_chipsel);
|
||||
}
|
||||
|
||||
static void nand_davinci_hwctl_1bit(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct davinci_nand_info *info;
|
||||
uint32_t nandcfr;
|
||||
unsigned long flags;
|
||||
|
||||
info = to_davinci_nand(mtd);
|
||||
|
||||
/* Reset ECC hardware */
|
||||
nand_davinci_readecc_1bit(mtd);
|
||||
|
||||
spin_lock_irqsave(&davinci_nand_lock, flags);
|
||||
|
||||
/* Restart ECC hardware */
|
||||
nandcfr = davinci_nand_readl(info, NANDFCR_OFFSET);
|
||||
nandcfr |= BIT(8 + info->core_chipsel);
|
||||
davinci_nand_writel(info, NANDFCR_OFFSET, nandcfr);
|
||||
|
||||
spin_unlock_irqrestore(&davinci_nand_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read hardware ECC value and pack into three bytes
|
||||
*/
|
||||
static int nand_davinci_calculate_1bit(struct mtd_info *mtd,
|
||||
const u_char *dat, u_char *ecc_code)
|
||||
{
|
||||
unsigned int ecc_val = nand_davinci_readecc_1bit(mtd);
|
||||
unsigned int ecc24 = (ecc_val & 0x0fff) | ((ecc_val & 0x0fff0000) >> 4);
|
||||
|
||||
/* invert so that erased block ecc is correct */
|
||||
ecc24 = ~ecc24;
|
||||
ecc_code[0] = (u_char)(ecc24);
|
||||
ecc_code[1] = (u_char)(ecc24 >> 8);
|
||||
ecc_code[2] = (u_char)(ecc24 >> 16);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nand_davinci_correct_1bit(struct mtd_info *mtd, u_char *dat,
|
||||
u_char *read_ecc, u_char *calc_ecc)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
uint32_t eccNand = read_ecc[0] | (read_ecc[1] << 8) |
|
||||
(read_ecc[2] << 16);
|
||||
uint32_t eccCalc = calc_ecc[0] | (calc_ecc[1] << 8) |
|
||||
(calc_ecc[2] << 16);
|
||||
uint32_t diff = eccCalc ^ eccNand;
|
||||
|
||||
if (diff) {
|
||||
if ((((diff >> 12) ^ diff) & 0xfff) == 0xfff) {
|
||||
/* Correctable error */
|
||||
if ((diff >> (12 + 3)) < chip->ecc.size) {
|
||||
dat[diff >> (12 + 3)] ^= BIT((diff >> 12) & 7);
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (!(diff & (diff - 1))) {
|
||||
/* Single bit ECC error in the ECC itself,
|
||||
* nothing to fix */
|
||||
return 1;
|
||||
} else {
|
||||
/* Uncorrectable error */
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* 4-bit hardware ECC ... context maintained over entire AEMIF
|
||||
*
|
||||
* This is a syndrome engine, but we avoid NAND_ECC_HW_SYNDROME
|
||||
* since that forces use of a problematic "infix OOB" layout.
|
||||
* Among other things, it trashes manufacturer bad block markers.
|
||||
* Also, and specific to this hardware, it ECC-protects the "prepad"
|
||||
* in the OOB ... while having ECC protection for parts of OOB would
|
||||
* seem useful, the current MTD stack sometimes wants to update the
|
||||
* OOB without recomputing ECC.
|
||||
*/
|
||||
|
||||
static void nand_davinci_hwctl_4bit(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
unsigned long flags;
|
||||
u32 val;
|
||||
|
||||
spin_lock_irqsave(&davinci_nand_lock, flags);
|
||||
|
||||
/* Start 4-bit ECC calculation for read/write */
|
||||
val = davinci_nand_readl(info, NANDFCR_OFFSET);
|
||||
val &= ~(0x03 << 4);
|
||||
val |= (info->core_chipsel << 4) | BIT(12);
|
||||
davinci_nand_writel(info, NANDFCR_OFFSET, val);
|
||||
|
||||
info->is_readmode = (mode == NAND_ECC_READ);
|
||||
|
||||
spin_unlock_irqrestore(&davinci_nand_lock, flags);
|
||||
}
|
||||
|
||||
/* Read raw ECC code after writing to NAND. */
|
||||
static void
|
||||
nand_davinci_readecc_4bit(struct davinci_nand_info *info, u32 code[4])
|
||||
{
|
||||
const u32 mask = 0x03ff03ff;
|
||||
|
||||
code[0] = davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET) & mask;
|
||||
code[1] = davinci_nand_readl(info, NAND_4BIT_ECC2_OFFSET) & mask;
|
||||
code[2] = davinci_nand_readl(info, NAND_4BIT_ECC3_OFFSET) & mask;
|
||||
code[3] = davinci_nand_readl(info, NAND_4BIT_ECC4_OFFSET) & mask;
|
||||
}
|
||||
|
||||
/* Terminate read ECC; or return ECC (as bytes) of data written to NAND. */
|
||||
static int nand_davinci_calculate_4bit(struct mtd_info *mtd,
|
||||
const u_char *dat, u_char *ecc_code)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
u32 raw_ecc[4], *p;
|
||||
unsigned i;
|
||||
|
||||
/* After a read, terminate ECC calculation by a dummy read
|
||||
* of some 4-bit ECC register. ECC covers everything that
|
||||
* was read; correct() just uses the hardware state, so
|
||||
* ecc_code is not needed.
|
||||
*/
|
||||
if (info->is_readmode) {
|
||||
davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pack eight raw 10-bit ecc values into ten bytes, making
|
||||
* two passes which each convert four values (in upper and
|
||||
* lower halves of two 32-bit words) into five bytes. The
|
||||
* ROM boot loader uses this same packing scheme.
|
||||
*/
|
||||
nand_davinci_readecc_4bit(info, raw_ecc);
|
||||
for (i = 0, p = raw_ecc; i < 2; i++, p += 2) {
|
||||
*ecc_code++ = p[0] & 0xff;
|
||||
*ecc_code++ = ((p[0] >> 8) & 0x03) | ((p[0] >> 14) & 0xfc);
|
||||
*ecc_code++ = ((p[0] >> 22) & 0x0f) | ((p[1] << 4) & 0xf0);
|
||||
*ecc_code++ = ((p[1] >> 4) & 0x3f) | ((p[1] >> 10) & 0xc0);
|
||||
*ecc_code++ = (p[1] >> 18) & 0xff;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Correct up to 4 bits in data we just read, using state left in the
|
||||
* hardware plus the ecc_code computed when it was first written.
|
||||
*/
|
||||
static int nand_davinci_correct_4bit(struct mtd_info *mtd,
|
||||
u_char *data, u_char *ecc_code, u_char *null)
|
||||
{
|
||||
int i;
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
unsigned short ecc10[8];
|
||||
unsigned short *ecc16;
|
||||
u32 syndrome[4];
|
||||
u32 ecc_state;
|
||||
unsigned num_errors, corrected;
|
||||
unsigned long timeo;
|
||||
|
||||
/* All bytes 0xff? It's an erased page; ignore its ECC. */
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (ecc_code[i] != 0xff)
|
||||
goto compare;
|
||||
}
|
||||
return 0;
|
||||
|
||||
compare:
|
||||
/* Unpack ten bytes into eight 10 bit values. We know we're
|
||||
* little-endian, and use type punning for less shifting/masking.
|
||||
*/
|
||||
if (WARN_ON(0x01 & (unsigned) ecc_code))
|
||||
return -EINVAL;
|
||||
ecc16 = (unsigned short *)ecc_code;
|
||||
|
||||
ecc10[0] = (ecc16[0] >> 0) & 0x3ff;
|
||||
ecc10[1] = ((ecc16[0] >> 10) & 0x3f) | ((ecc16[1] << 6) & 0x3c0);
|
||||
ecc10[2] = (ecc16[1] >> 4) & 0x3ff;
|
||||
ecc10[3] = ((ecc16[1] >> 14) & 0x3) | ((ecc16[2] << 2) & 0x3fc);
|
||||
ecc10[4] = (ecc16[2] >> 8) | ((ecc16[3] << 8) & 0x300);
|
||||
ecc10[5] = (ecc16[3] >> 2) & 0x3ff;
|
||||
ecc10[6] = ((ecc16[3] >> 12) & 0xf) | ((ecc16[4] << 4) & 0x3f0);
|
||||
ecc10[7] = (ecc16[4] >> 6) & 0x3ff;
|
||||
|
||||
/* Tell ECC controller about the expected ECC codes. */
|
||||
for (i = 7; i >= 0; i--)
|
||||
davinci_nand_writel(info, NAND_4BIT_ECC_LOAD_OFFSET, ecc10[i]);
|
||||
|
||||
/* Allow time for syndrome calculation ... then read it.
|
||||
* A syndrome of all zeroes 0 means no detected errors.
|
||||
*/
|
||||
davinci_nand_readl(info, NANDFSR_OFFSET);
|
||||
nand_davinci_readecc_4bit(info, syndrome);
|
||||
if (!(syndrome[0] | syndrome[1] | syndrome[2] | syndrome[3]))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Clear any previous address calculation by doing a dummy read of an
|
||||
* error address register.
|
||||
*/
|
||||
davinci_nand_readl(info, NAND_ERR_ADD1_OFFSET);
|
||||
|
||||
/* Start address calculation, and wait for it to complete.
|
||||
* We _could_ start reading more data while this is working,
|
||||
* to speed up the overall page read.
|
||||
*/
|
||||
davinci_nand_writel(info, NANDFCR_OFFSET,
|
||||
davinci_nand_readl(info, NANDFCR_OFFSET) | BIT(13));
|
||||
|
||||
/*
|
||||
* ECC_STATE field reads 0x3 (Error correction complete) immediately
|
||||
* after setting the 4BITECC_ADD_CALC_START bit. So if you immediately
|
||||
* begin trying to poll for the state, you may fall right out of your
|
||||
* loop without any of the correction calculations having taken place.
|
||||
* The recommendation from the hardware team is to initially delay as
|
||||
* long as ECC_STATE reads less than 4. After that, ECC HW has entered
|
||||
* correction state.
|
||||
*/
|
||||
timeo = jiffies + usecs_to_jiffies(100);
|
||||
do {
|
||||
ecc_state = (davinci_nand_readl(info,
|
||||
NANDFSR_OFFSET) >> 8) & 0x0f;
|
||||
cpu_relax();
|
||||
} while ((ecc_state < 4) && time_before(jiffies, timeo));
|
||||
|
||||
for (;;) {
|
||||
u32 fsr = davinci_nand_readl(info, NANDFSR_OFFSET);
|
||||
|
||||
switch ((fsr >> 8) & 0x0f) {
|
||||
case 0: /* no error, should not happen */
|
||||
davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET);
|
||||
return 0;
|
||||
case 1: /* five or more errors detected */
|
||||
davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET);
|
||||
return -EIO;
|
||||
case 2: /* error addresses computed */
|
||||
case 3:
|
||||
num_errors = 1 + ((fsr >> 16) & 0x03);
|
||||
goto correct;
|
||||
default: /* still working on it */
|
||||
cpu_relax();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
correct:
|
||||
/* correct each error */
|
||||
for (i = 0, corrected = 0; i < num_errors; i++) {
|
||||
int error_address, error_value;
|
||||
|
||||
if (i > 1) {
|
||||
error_address = davinci_nand_readl(info,
|
||||
NAND_ERR_ADD2_OFFSET);
|
||||
error_value = davinci_nand_readl(info,
|
||||
NAND_ERR_ERRVAL2_OFFSET);
|
||||
} else {
|
||||
error_address = davinci_nand_readl(info,
|
||||
NAND_ERR_ADD1_OFFSET);
|
||||
error_value = davinci_nand_readl(info,
|
||||
NAND_ERR_ERRVAL1_OFFSET);
|
||||
}
|
||||
|
||||
if (i & 1) {
|
||||
error_address >>= 16;
|
||||
error_value >>= 16;
|
||||
}
|
||||
error_address &= 0x3ff;
|
||||
error_address = (512 + 7) - error_address;
|
||||
|
||||
if (error_address < 512) {
|
||||
data[error_address] ^= error_value;
|
||||
corrected++;
|
||||
}
|
||||
}
|
||||
|
||||
return corrected;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* NOTE: NAND boot requires ALE == EM_A[1], CLE == EM_A[2], so that's
|
||||
* how these chips are normally wired. This translates to both 8 and 16
|
||||
* bit busses using ALE == BIT(3) in byte addresses, and CLE == BIT(4).
|
||||
*
|
||||
* For now we assume that configuration, or any other one which ignores
|
||||
* the two LSBs for NAND access ... so we can issue 32-bit reads/writes
|
||||
* and have that transparently morphed into multiple NAND operations.
|
||||
*/
|
||||
static void nand_davinci_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if ((0x03 & ((unsigned)buf)) == 0 && (0x03 & len) == 0)
|
||||
ioread32_rep(chip->IO_ADDR_R, buf, len >> 2);
|
||||
else if ((0x01 & ((unsigned)buf)) == 0 && (0x01 & len) == 0)
|
||||
ioread16_rep(chip->IO_ADDR_R, buf, len >> 1);
|
||||
else
|
||||
ioread8_rep(chip->IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
static void nand_davinci_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if ((0x03 & ((unsigned)buf)) == 0 && (0x03 & len) == 0)
|
||||
iowrite32_rep(chip->IO_ADDR_R, buf, len >> 2);
|
||||
else if ((0x01 & ((unsigned)buf)) == 0 && (0x01 & len) == 0)
|
||||
iowrite16_rep(chip->IO_ADDR_R, buf, len >> 1);
|
||||
else
|
||||
iowrite8_rep(chip->IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check hardware register for wait status. Returns 1 if device is ready,
|
||||
* 0 if it is still busy.
|
||||
*/
|
||||
static int nand_davinci_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct davinci_nand_info *info = to_davinci_nand(mtd);
|
||||
|
||||
return davinci_nand_readl(info, NANDFSR_OFFSET) & BIT(0);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/* An ECC layout for using 4-bit ECC with small-page flash, storing
|
||||
* ten ECC bytes plus the manufacturer's bad block marker byte, and
|
||||
* and not overlapping the default BBT markers.
|
||||
*/
|
||||
static struct nand_ecclayout hwecc4_small = {
|
||||
.eccbytes = 10,
|
||||
.eccpos = { 0, 1, 2, 3, 4,
|
||||
/* offset 5 holds the badblock marker */
|
||||
6, 7,
|
||||
13, 14, 15, },
|
||||
.oobfree = {
|
||||
{.offset = 8, .length = 5, },
|
||||
{.offset = 16, },
|
||||
},
|
||||
};
|
||||
|
||||
/* An ECC layout for using 4-bit ECC with large-page (2048bytes) flash,
|
||||
* storing ten ECC bytes plus the manufacturer's bad block marker byte,
|
||||
* and not overlapping the default BBT markers.
|
||||
*/
|
||||
static struct nand_ecclayout hwecc4_2048 = {
|
||||
.eccbytes = 40,
|
||||
.eccpos = {
|
||||
/* at the end of spare sector */
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
|
||||
34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||
44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
||||
},
|
||||
.oobfree = {
|
||||
/* 2 bytes at offset 0 hold manufacturer badblock markers */
|
||||
{.offset = 2, .length = 22, },
|
||||
/* 5 bytes at offset 8 hold BBT markers */
|
||||
/* 8 bytes at offset 16 hold JFFS2 clean markers */
|
||||
},
|
||||
};
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id davinci_nand_of_match[] = {
|
||||
{.compatible = "ti,davinci-nand", },
|
||||
{.compatible = "ti,keystone-nand", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, davinci_nand_of_match);
|
||||
|
||||
static struct davinci_nand_pdata
|
||||
*nand_davinci_get_pdata(struct platform_device *pdev)
|
||||
{
|
||||
if (!dev_get_platdata(&pdev->dev) && pdev->dev.of_node) {
|
||||
struct davinci_nand_pdata *pdata;
|
||||
const char *mode;
|
||||
u32 prop;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct davinci_nand_pdata),
|
||||
GFP_KERNEL);
|
||||
pdev->dev.platform_data = pdata;
|
||||
if (!pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-chipselect", &prop))
|
||||
pdev->id = prop;
|
||||
else
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-ale", &prop))
|
||||
pdata->mask_ale = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-cle", &prop))
|
||||
pdata->mask_cle = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-chipsel", &prop))
|
||||
pdata->mask_chipsel = prop;
|
||||
if (!of_property_read_string(pdev->dev.of_node,
|
||||
"nand-ecc-mode", &mode) ||
|
||||
!of_property_read_string(pdev->dev.of_node,
|
||||
"ti,davinci-ecc-mode", &mode)) {
|
||||
if (!strncmp("none", mode, 4))
|
||||
pdata->ecc_mode = NAND_ECC_NONE;
|
||||
if (!strncmp("soft", mode, 4))
|
||||
pdata->ecc_mode = NAND_ECC_SOFT;
|
||||
if (!strncmp("hw", mode, 2))
|
||||
pdata->ecc_mode = NAND_ECC_HW;
|
||||
}
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-ecc-bits", &prop))
|
||||
pdata->ecc_bits = prop;
|
||||
|
||||
prop = of_get_nand_bus_width(pdev->dev.of_node);
|
||||
if (0 < prop || !of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-nand-buswidth", &prop))
|
||||
if (prop == 16)
|
||||
pdata->options |= NAND_BUSWIDTH_16;
|
||||
if (of_property_read_bool(pdev->dev.of_node,
|
||||
"nand-on-flash-bbt") ||
|
||||
of_property_read_bool(pdev->dev.of_node,
|
||||
"ti,davinci-nand-use-bbt"))
|
||||
pdata->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node,
|
||||
"ti,keystone-nand")) {
|
||||
pdata->options |= NAND_NO_SUBPAGE_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
return dev_get_platdata(&pdev->dev);
|
||||
}
|
||||
#else
|
||||
static struct davinci_nand_pdata
|
||||
*nand_davinci_get_pdata(struct platform_device *pdev)
|
||||
{
|
||||
return dev_get_platdata(&pdev->dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int nand_davinci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct davinci_nand_pdata *pdata;
|
||||
struct davinci_nand_info *info;
|
||||
struct resource *res1;
|
||||
struct resource *res2;
|
||||
void __iomem *vaddr;
|
||||
void __iomem *base;
|
||||
int ret;
|
||||
uint32_t val;
|
||||
nand_ecc_modes_t ecc_mode;
|
||||
|
||||
pdata = nand_davinci_get_pdata(pdev);
|
||||
if (IS_ERR(pdata))
|
||||
return PTR_ERR(pdata);
|
||||
|
||||
/* insist on board-specific configuration */
|
||||
if (!pdata)
|
||||
return -ENODEV;
|
||||
|
||||
/* which external chipselect will we be managing? */
|
||||
if (pdev->id < 0 || pdev->id > 3)
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (!res1 || !res2) {
|
||||
dev_err(&pdev->dev, "resource missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vaddr = devm_ioremap_resource(&pdev->dev, res1);
|
||||
if (IS_ERR(vaddr))
|
||||
return PTR_ERR(vaddr);
|
||||
|
||||
/*
|
||||
* This registers range is used to setup NAND settings. In case with
|
||||
* TI AEMIF driver, the same memory address range is requested already
|
||||
* by AEMIF, so we cannot request it twice, just ioremap.
|
||||
* The AEMIF and NAND drivers not use the same registers in this range.
|
||||
*/
|
||||
base = devm_ioremap(&pdev->dev, res2->start, resource_size(res2));
|
||||
if (!base) {
|
||||
dev_err(&pdev->dev, "ioremap failed for resource %pR\n", res2);
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
|
||||
info->dev = &pdev->dev;
|
||||
info->base = base;
|
||||
info->vaddr = vaddr;
|
||||
|
||||
info->mtd.priv = &info->chip;
|
||||
info->mtd.name = dev_name(&pdev->dev);
|
||||
info->mtd.owner = THIS_MODULE;
|
||||
|
||||
info->mtd.dev.parent = &pdev->dev;
|
||||
|
||||
info->chip.IO_ADDR_R = vaddr;
|
||||
info->chip.IO_ADDR_W = vaddr;
|
||||
info->chip.chip_delay = 0;
|
||||
info->chip.select_chip = nand_davinci_select_chip;
|
||||
|
||||
/* options such as NAND_BBT_USE_FLASH */
|
||||
info->chip.bbt_options = pdata->bbt_options;
|
||||
/* options such as 16-bit widths */
|
||||
info->chip.options = pdata->options;
|
||||
info->chip.bbt_td = pdata->bbt_td;
|
||||
info->chip.bbt_md = pdata->bbt_md;
|
||||
info->timing = pdata->timing;
|
||||
|
||||
info->ioaddr = (uint32_t __force) vaddr;
|
||||
|
||||
info->current_cs = info->ioaddr;
|
||||
info->core_chipsel = pdev->id;
|
||||
info->mask_chipsel = pdata->mask_chipsel;
|
||||
|
||||
/* use nandboot-capable ALE/CLE masks by default */
|
||||
info->mask_ale = pdata->mask_ale ? : MASK_ALE;
|
||||
info->mask_cle = pdata->mask_cle ? : MASK_CLE;
|
||||
|
||||
/* Set address of hardware control function */
|
||||
info->chip.cmd_ctrl = nand_davinci_hwcontrol;
|
||||
info->chip.dev_ready = nand_davinci_dev_ready;
|
||||
|
||||
/* Speed up buffer I/O */
|
||||
info->chip.read_buf = nand_davinci_read_buf;
|
||||
info->chip.write_buf = nand_davinci_write_buf;
|
||||
|
||||
/* Use board-specific ECC config */
|
||||
ecc_mode = pdata->ecc_mode;
|
||||
|
||||
ret = -EINVAL;
|
||||
switch (ecc_mode) {
|
||||
case NAND_ECC_NONE:
|
||||
case NAND_ECC_SOFT:
|
||||
pdata->ecc_bits = 0;
|
||||
break;
|
||||
case NAND_ECC_HW:
|
||||
if (pdata->ecc_bits == 4) {
|
||||
/* No sanity checks: CPUs must support this,
|
||||
* and the chips may not use NAND_BUSWIDTH_16.
|
||||
*/
|
||||
|
||||
/* No sharing 4-bit hardware between chipselects yet */
|
||||
spin_lock_irq(&davinci_nand_lock);
|
||||
if (ecc4_busy)
|
||||
ret = -EBUSY;
|
||||
else
|
||||
ecc4_busy = true;
|
||||
spin_unlock_irq(&davinci_nand_lock);
|
||||
|
||||
if (ret == -EBUSY)
|
||||
return ret;
|
||||
|
||||
info->chip.ecc.calculate = nand_davinci_calculate_4bit;
|
||||
info->chip.ecc.correct = nand_davinci_correct_4bit;
|
||||
info->chip.ecc.hwctl = nand_davinci_hwctl_4bit;
|
||||
info->chip.ecc.bytes = 10;
|
||||
} else {
|
||||
info->chip.ecc.calculate = nand_davinci_calculate_1bit;
|
||||
info->chip.ecc.correct = nand_davinci_correct_1bit;
|
||||
info->chip.ecc.hwctl = nand_davinci_hwctl_1bit;
|
||||
info->chip.ecc.bytes = 3;
|
||||
}
|
||||
info->chip.ecc.size = 512;
|
||||
info->chip.ecc.strength = pdata->ecc_bits;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
info->chip.ecc.mode = ecc_mode;
|
||||
|
||||
info->clk = devm_clk_get(&pdev->dev, "aemif");
|
||||
if (IS_ERR(info->clk)) {
|
||||
ret = PTR_ERR(info->clk);
|
||||
dev_dbg(&pdev->dev, "unable to get AEMIF clock, err %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(info->clk);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&pdev->dev, "unable to enable AEMIF clock, err %d\n",
|
||||
ret);
|
||||
goto err_clk_enable;
|
||||
}
|
||||
|
||||
spin_lock_irq(&davinci_nand_lock);
|
||||
|
||||
/* put CSxNAND into NAND mode */
|
||||
val = davinci_nand_readl(info, NANDFCR_OFFSET);
|
||||
val |= BIT(info->core_chipsel);
|
||||
davinci_nand_writel(info, NANDFCR_OFFSET, val);
|
||||
|
||||
spin_unlock_irq(&davinci_nand_lock);
|
||||
|
||||
/* Scan to find existence of the device(s) */
|
||||
ret = nand_scan_ident(&info->mtd, pdata->mask_chipsel ? 2 : 1, NULL);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&pdev->dev, "no NAND chip(s) found\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Update ECC layout if needed ... for 1-bit HW ECC, the default
|
||||
* is OK, but it allocates 6 bytes when only 3 are needed (for
|
||||
* each 512 bytes). For the 4-bit HW ECC, that default is not
|
||||
* usable: 10 bytes are needed, not 6.
|
||||
*/
|
||||
if (pdata->ecc_bits == 4) {
|
||||
int chunks = info->mtd.writesize / 512;
|
||||
|
||||
if (!chunks || info->mtd.oobsize < 16) {
|
||||
dev_dbg(&pdev->dev, "too small\n");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* For small page chips, preserve the manufacturer's
|
||||
* badblock marking data ... and make sure a flash BBT
|
||||
* table marker fits in the free bytes.
|
||||
*/
|
||||
if (chunks == 1) {
|
||||
info->ecclayout = hwecc4_small;
|
||||
info->ecclayout.oobfree[1].length =
|
||||
info->mtd.oobsize - 16;
|
||||
goto syndrome_done;
|
||||
}
|
||||
if (chunks == 4) {
|
||||
info->ecclayout = hwecc4_2048;
|
||||
info->chip.ecc.mode = NAND_ECC_HW_OOB_FIRST;
|
||||
goto syndrome_done;
|
||||
}
|
||||
|
||||
/* 4KiB page chips are not yet supported. The eccpos from
|
||||
* nand_ecclayout cannot hold 80 bytes and change to eccpos[]
|
||||
* breaks userspace ioctl interface with mtd-utils. Once we
|
||||
* resolve this issue, NAND_ECC_HW_OOB_FIRST mode can be used
|
||||
* for the 4KiB page chips.
|
||||
*
|
||||
* TODO: Note that nand_ecclayout has now been expanded and can
|
||||
* hold plenty of OOB entries.
|
||||
*/
|
||||
dev_warn(&pdev->dev, "no 4-bit ECC support yet "
|
||||
"for 4KiB-page NAND\n");
|
||||
ret = -EIO;
|
||||
goto err;
|
||||
|
||||
syndrome_done:
|
||||
info->chip.ecc.layout = &info->ecclayout;
|
||||
}
|
||||
|
||||
ret = nand_scan_tail(&info->mtd);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (pdata->parts)
|
||||
ret = mtd_device_parse_register(&info->mtd, NULL, NULL,
|
||||
pdata->parts, pdata->nr_parts);
|
||||
else {
|
||||
struct mtd_part_parser_data ppdata;
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
ret = mtd_device_parse_register(&info->mtd, NULL, &ppdata,
|
||||
NULL, 0);
|
||||
}
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
val = davinci_nand_readl(info, NRCSR_OFFSET);
|
||||
dev_info(&pdev->dev, "controller rev. %d.%d\n",
|
||||
(val >> 8) & 0xff, val & 0xff);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
clk_disable_unprepare(info->clk);
|
||||
|
||||
err_clk_enable:
|
||||
spin_lock_irq(&davinci_nand_lock);
|
||||
if (ecc_mode == NAND_ECC_HW_SYNDROME)
|
||||
ecc4_busy = false;
|
||||
spin_unlock_irq(&davinci_nand_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nand_davinci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct davinci_nand_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
spin_lock_irq(&davinci_nand_lock);
|
||||
if (info->chip.ecc.mode == NAND_ECC_HW_SYNDROME)
|
||||
ecc4_busy = false;
|
||||
spin_unlock_irq(&davinci_nand_lock);
|
||||
|
||||
nand_release(&info->mtd);
|
||||
|
||||
clk_disable_unprepare(info->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver nand_davinci_driver = {
|
||||
.probe = nand_davinci_probe,
|
||||
.remove = nand_davinci_remove,
|
||||
.driver = {
|
||||
.name = "davinci_nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(davinci_nand_of_match),
|
||||
},
|
||||
};
|
||||
MODULE_ALIAS("platform:davinci_nand");
|
||||
|
||||
module_platform_driver(nand_davinci_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Texas Instruments");
|
||||
MODULE_DESCRIPTION("Davinci NAND flash driver");
|
||||
|
||||
1659
drivers/mtd/nand/denali.c
Normal file
1659
drivers/mtd/nand/denali.c
Normal file
File diff suppressed because it is too large
Load diff
483
drivers/mtd/nand/denali.h
Normal file
483
drivers/mtd/nand/denali.h
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* NAND Flash Controller Device Driver
|
||||
* Copyright (c) 2009 - 2010, Intel Corporation and its suppliers.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __DENALI_H__
|
||||
#define __DENALI_H__
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
#define DEVICE_RESET 0x0
|
||||
#define DEVICE_RESET__BANK0 0x0001
|
||||
#define DEVICE_RESET__BANK1 0x0002
|
||||
#define DEVICE_RESET__BANK2 0x0004
|
||||
#define DEVICE_RESET__BANK3 0x0008
|
||||
|
||||
#define TRANSFER_SPARE_REG 0x10
|
||||
#define TRANSFER_SPARE_REG__FLAG 0x0001
|
||||
|
||||
#define LOAD_WAIT_CNT 0x20
|
||||
#define LOAD_WAIT_CNT__VALUE 0xffff
|
||||
|
||||
#define PROGRAM_WAIT_CNT 0x30
|
||||
#define PROGRAM_WAIT_CNT__VALUE 0xffff
|
||||
|
||||
#define ERASE_WAIT_CNT 0x40
|
||||
#define ERASE_WAIT_CNT__VALUE 0xffff
|
||||
|
||||
#define INT_MON_CYCCNT 0x50
|
||||
#define INT_MON_CYCCNT__VALUE 0xffff
|
||||
|
||||
#define RB_PIN_ENABLED 0x60
|
||||
#define RB_PIN_ENABLED__BANK0 0x0001
|
||||
#define RB_PIN_ENABLED__BANK1 0x0002
|
||||
#define RB_PIN_ENABLED__BANK2 0x0004
|
||||
#define RB_PIN_ENABLED__BANK3 0x0008
|
||||
|
||||
#define MULTIPLANE_OPERATION 0x70
|
||||
#define MULTIPLANE_OPERATION__FLAG 0x0001
|
||||
|
||||
#define MULTIPLANE_READ_ENABLE 0x80
|
||||
#define MULTIPLANE_READ_ENABLE__FLAG 0x0001
|
||||
|
||||
#define COPYBACK_DISABLE 0x90
|
||||
#define COPYBACK_DISABLE__FLAG 0x0001
|
||||
|
||||
#define CACHE_WRITE_ENABLE 0xa0
|
||||
#define CACHE_WRITE_ENABLE__FLAG 0x0001
|
||||
|
||||
#define CACHE_READ_ENABLE 0xb0
|
||||
#define CACHE_READ_ENABLE__FLAG 0x0001
|
||||
|
||||
#define PREFETCH_MODE 0xc0
|
||||
#define PREFETCH_MODE__PREFETCH_EN 0x0001
|
||||
#define PREFETCH_MODE__PREFETCH_BURST_LENGTH 0xfff0
|
||||
|
||||
#define CHIP_ENABLE_DONT_CARE 0xd0
|
||||
#define CHIP_EN_DONT_CARE__FLAG 0x01
|
||||
|
||||
#define ECC_ENABLE 0xe0
|
||||
#define ECC_ENABLE__FLAG 0x0001
|
||||
|
||||
#define GLOBAL_INT_ENABLE 0xf0
|
||||
#define GLOBAL_INT_EN_FLAG 0x01
|
||||
|
||||
#define WE_2_RE 0x100
|
||||
#define WE_2_RE__VALUE 0x003f
|
||||
|
||||
#define ADDR_2_DATA 0x110
|
||||
#define ADDR_2_DATA__VALUE 0x003f
|
||||
|
||||
#define RE_2_WE 0x120
|
||||
#define RE_2_WE__VALUE 0x003f
|
||||
|
||||
#define ACC_CLKS 0x130
|
||||
#define ACC_CLKS__VALUE 0x000f
|
||||
|
||||
#define NUMBER_OF_PLANES 0x140
|
||||
#define NUMBER_OF_PLANES__VALUE 0x0007
|
||||
|
||||
#define PAGES_PER_BLOCK 0x150
|
||||
#define PAGES_PER_BLOCK__VALUE 0xffff
|
||||
|
||||
#define DEVICE_WIDTH 0x160
|
||||
#define DEVICE_WIDTH__VALUE 0x0003
|
||||
|
||||
#define DEVICE_MAIN_AREA_SIZE 0x170
|
||||
#define DEVICE_MAIN_AREA_SIZE__VALUE 0xffff
|
||||
|
||||
#define DEVICE_SPARE_AREA_SIZE 0x180
|
||||
#define DEVICE_SPARE_AREA_SIZE__VALUE 0xffff
|
||||
|
||||
#define TWO_ROW_ADDR_CYCLES 0x190
|
||||
#define TWO_ROW_ADDR_CYCLES__FLAG 0x0001
|
||||
|
||||
#define MULTIPLANE_ADDR_RESTRICT 0x1a0
|
||||
#define MULTIPLANE_ADDR_RESTRICT__FLAG 0x0001
|
||||
|
||||
#define ECC_CORRECTION 0x1b0
|
||||
#define ECC_CORRECTION__VALUE 0x001f
|
||||
|
||||
#define READ_MODE 0x1c0
|
||||
#define READ_MODE__VALUE 0x000f
|
||||
|
||||
#define WRITE_MODE 0x1d0
|
||||
#define WRITE_MODE__VALUE 0x000f
|
||||
|
||||
#define COPYBACK_MODE 0x1e0
|
||||
#define COPYBACK_MODE__VALUE 0x000f
|
||||
|
||||
#define RDWR_EN_LO_CNT 0x1f0
|
||||
#define RDWR_EN_LO_CNT__VALUE 0x001f
|
||||
|
||||
#define RDWR_EN_HI_CNT 0x200
|
||||
#define RDWR_EN_HI_CNT__VALUE 0x001f
|
||||
|
||||
#define MAX_RD_DELAY 0x210
|
||||
#define MAX_RD_DELAY__VALUE 0x000f
|
||||
|
||||
#define CS_SETUP_CNT 0x220
|
||||
#define CS_SETUP_CNT__VALUE 0x001f
|
||||
|
||||
#define SPARE_AREA_SKIP_BYTES 0x230
|
||||
#define SPARE_AREA_SKIP_BYTES__VALUE 0x003f
|
||||
|
||||
#define SPARE_AREA_MARKER 0x240
|
||||
#define SPARE_AREA_MARKER__VALUE 0xffff
|
||||
|
||||
#define DEVICES_CONNECTED 0x250
|
||||
#define DEVICES_CONNECTED__VALUE 0x0007
|
||||
|
||||
#define DIE_MASK 0x260
|
||||
#define DIE_MASK__VALUE 0x00ff
|
||||
|
||||
#define FIRST_BLOCK_OF_NEXT_PLANE 0x270
|
||||
#define FIRST_BLOCK_OF_NEXT_PLANE__VALUE 0xffff
|
||||
|
||||
#define WRITE_PROTECT 0x280
|
||||
#define WRITE_PROTECT__FLAG 0x0001
|
||||
|
||||
#define RE_2_RE 0x290
|
||||
#define RE_2_RE__VALUE 0x003f
|
||||
|
||||
#define MANUFACTURER_ID 0x300
|
||||
#define MANUFACTURER_ID__VALUE 0x00ff
|
||||
|
||||
#define DEVICE_ID 0x310
|
||||
#define DEVICE_ID__VALUE 0x00ff
|
||||
|
||||
#define DEVICE_PARAM_0 0x320
|
||||
#define DEVICE_PARAM_0__VALUE 0x00ff
|
||||
|
||||
#define DEVICE_PARAM_1 0x330
|
||||
#define DEVICE_PARAM_1__VALUE 0x00ff
|
||||
|
||||
#define DEVICE_PARAM_2 0x340
|
||||
#define DEVICE_PARAM_2__VALUE 0x00ff
|
||||
|
||||
#define LOGICAL_PAGE_DATA_SIZE 0x350
|
||||
#define LOGICAL_PAGE_DATA_SIZE__VALUE 0xffff
|
||||
|
||||
#define LOGICAL_PAGE_SPARE_SIZE 0x360
|
||||
#define LOGICAL_PAGE_SPARE_SIZE__VALUE 0xffff
|
||||
|
||||
#define REVISION 0x370
|
||||
#define REVISION__VALUE 0xffff
|
||||
|
||||
#define ONFI_DEVICE_FEATURES 0x380
|
||||
#define ONFI_DEVICE_FEATURES__VALUE 0x003f
|
||||
|
||||
#define ONFI_OPTIONAL_COMMANDS 0x390
|
||||
#define ONFI_OPTIONAL_COMMANDS__VALUE 0x003f
|
||||
|
||||
#define ONFI_TIMING_MODE 0x3a0
|
||||
#define ONFI_TIMING_MODE__VALUE 0x003f
|
||||
|
||||
#define ONFI_PGM_CACHE_TIMING_MODE 0x3b0
|
||||
#define ONFI_PGM_CACHE_TIMING_MODE__VALUE 0x003f
|
||||
|
||||
#define ONFI_DEVICE_NO_OF_LUNS 0x3c0
|
||||
#define ONFI_DEVICE_NO_OF_LUNS__NO_OF_LUNS 0x00ff
|
||||
#define ONFI_DEVICE_NO_OF_LUNS__ONFI_DEVICE 0x0100
|
||||
|
||||
#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L 0x3d0
|
||||
#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L__VALUE 0xffff
|
||||
|
||||
#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U 0x3e0
|
||||
#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U__VALUE 0xffff
|
||||
|
||||
#define FEATURES 0x3f0
|
||||
#define FEATURES__N_BANKS 0x0003
|
||||
#define FEATURES__ECC_MAX_ERR 0x003c
|
||||
#define FEATURES__DMA 0x0040
|
||||
#define FEATURES__CMD_DMA 0x0080
|
||||
#define FEATURES__PARTITION 0x0100
|
||||
#define FEATURES__XDMA_SIDEBAND 0x0200
|
||||
#define FEATURES__GPREG 0x0400
|
||||
#define FEATURES__INDEX_ADDR 0x0800
|
||||
|
||||
#define TRANSFER_MODE 0x400
|
||||
#define TRANSFER_MODE__VALUE 0x0003
|
||||
|
||||
#define INTR_STATUS(__bank) (0x410 + ((__bank) * 0x50))
|
||||
#define INTR_EN(__bank) (0x420 + ((__bank) * 0x50))
|
||||
|
||||
#define INTR_STATUS__ECC_TRANSACTION_DONE 0x0001
|
||||
#define INTR_STATUS__ECC_ERR 0x0002
|
||||
#define INTR_STATUS__DMA_CMD_COMP 0x0004
|
||||
#define INTR_STATUS__TIME_OUT 0x0008
|
||||
#define INTR_STATUS__PROGRAM_FAIL 0x0010
|
||||
#define INTR_STATUS__ERASE_FAIL 0x0020
|
||||
#define INTR_STATUS__LOAD_COMP 0x0040
|
||||
#define INTR_STATUS__PROGRAM_COMP 0x0080
|
||||
#define INTR_STATUS__ERASE_COMP 0x0100
|
||||
#define INTR_STATUS__PIPE_CPYBCK_CMD_COMP 0x0200
|
||||
#define INTR_STATUS__LOCKED_BLK 0x0400
|
||||
#define INTR_STATUS__UNSUP_CMD 0x0800
|
||||
#define INTR_STATUS__INT_ACT 0x1000
|
||||
#define INTR_STATUS__RST_COMP 0x2000
|
||||
#define INTR_STATUS__PIPE_CMD_ERR 0x4000
|
||||
#define INTR_STATUS__PAGE_XFER_INC 0x8000
|
||||
|
||||
#define INTR_EN__ECC_TRANSACTION_DONE 0x0001
|
||||
#define INTR_EN__ECC_ERR 0x0002
|
||||
#define INTR_EN__DMA_CMD_COMP 0x0004
|
||||
#define INTR_EN__TIME_OUT 0x0008
|
||||
#define INTR_EN__PROGRAM_FAIL 0x0010
|
||||
#define INTR_EN__ERASE_FAIL 0x0020
|
||||
#define INTR_EN__LOAD_COMP 0x0040
|
||||
#define INTR_EN__PROGRAM_COMP 0x0080
|
||||
#define INTR_EN__ERASE_COMP 0x0100
|
||||
#define INTR_EN__PIPE_CPYBCK_CMD_COMP 0x0200
|
||||
#define INTR_EN__LOCKED_BLK 0x0400
|
||||
#define INTR_EN__UNSUP_CMD 0x0800
|
||||
#define INTR_EN__INT_ACT 0x1000
|
||||
#define INTR_EN__RST_COMP 0x2000
|
||||
#define INTR_EN__PIPE_CMD_ERR 0x4000
|
||||
#define INTR_EN__PAGE_XFER_INC 0x8000
|
||||
|
||||
#define PAGE_CNT(__bank) (0x430 + ((__bank) * 0x50))
|
||||
#define ERR_PAGE_ADDR(__bank) (0x440 + ((__bank) * 0x50))
|
||||
#define ERR_BLOCK_ADDR(__bank) (0x450 + ((__bank) * 0x50))
|
||||
|
||||
#define DATA_INTR 0x550
|
||||
#define DATA_INTR__WRITE_SPACE_AV 0x0001
|
||||
#define DATA_INTR__READ_DATA_AV 0x0002
|
||||
|
||||
#define DATA_INTR_EN 0x560
|
||||
#define DATA_INTR_EN__WRITE_SPACE_AV 0x0001
|
||||
#define DATA_INTR_EN__READ_DATA_AV 0x0002
|
||||
|
||||
#define GPREG_0 0x570
|
||||
#define GPREG_0__VALUE 0xffff
|
||||
|
||||
#define GPREG_1 0x580
|
||||
#define GPREG_1__VALUE 0xffff
|
||||
|
||||
#define GPREG_2 0x590
|
||||
#define GPREG_2__VALUE 0xffff
|
||||
|
||||
#define GPREG_3 0x5a0
|
||||
#define GPREG_3__VALUE 0xffff
|
||||
|
||||
#define ECC_THRESHOLD 0x600
|
||||
#define ECC_THRESHOLD__VALUE 0x03ff
|
||||
|
||||
#define ECC_ERROR_BLOCK_ADDRESS 0x610
|
||||
#define ECC_ERROR_BLOCK_ADDRESS__VALUE 0xffff
|
||||
|
||||
#define ECC_ERROR_PAGE_ADDRESS 0x620
|
||||
#define ECC_ERROR_PAGE_ADDRESS__VALUE 0x0fff
|
||||
#define ECC_ERROR_PAGE_ADDRESS__BANK 0xf000
|
||||
|
||||
#define ECC_ERROR_ADDRESS 0x630
|
||||
#define ECC_ERROR_ADDRESS__OFFSET 0x0fff
|
||||
#define ECC_ERROR_ADDRESS__SECTOR_NR 0xf000
|
||||
|
||||
#define ERR_CORRECTION_INFO 0x640
|
||||
#define ERR_CORRECTION_INFO__BYTEMASK 0x00ff
|
||||
#define ERR_CORRECTION_INFO__DEVICE_NR 0x0f00
|
||||
#define ERR_CORRECTION_INFO__ERROR_TYPE 0x4000
|
||||
#define ERR_CORRECTION_INFO__LAST_ERR_INFO 0x8000
|
||||
|
||||
#define DMA_ENABLE 0x700
|
||||
#define DMA_ENABLE__FLAG 0x0001
|
||||
|
||||
#define IGNORE_ECC_DONE 0x710
|
||||
#define IGNORE_ECC_DONE__FLAG 0x0001
|
||||
|
||||
#define DMA_INTR 0x720
|
||||
#define DMA_INTR__TARGET_ERROR 0x0001
|
||||
#define DMA_INTR__DESC_COMP_CHANNEL0 0x0002
|
||||
#define DMA_INTR__DESC_COMP_CHANNEL1 0x0004
|
||||
#define DMA_INTR__DESC_COMP_CHANNEL2 0x0008
|
||||
#define DMA_INTR__DESC_COMP_CHANNEL3 0x0010
|
||||
#define DMA_INTR__MEMCOPY_DESC_COMP 0x0020
|
||||
|
||||
#define DMA_INTR_EN 0x730
|
||||
#define DMA_INTR_EN__TARGET_ERROR 0x0001
|
||||
#define DMA_INTR_EN__DESC_COMP_CHANNEL0 0x0002
|
||||
#define DMA_INTR_EN__DESC_COMP_CHANNEL1 0x0004
|
||||
#define DMA_INTR_EN__DESC_COMP_CHANNEL2 0x0008
|
||||
#define DMA_INTR_EN__DESC_COMP_CHANNEL3 0x0010
|
||||
#define DMA_INTR_EN__MEMCOPY_DESC_COMP 0x0020
|
||||
|
||||
#define TARGET_ERR_ADDR_LO 0x740
|
||||
#define TARGET_ERR_ADDR_LO__VALUE 0xffff
|
||||
|
||||
#define TARGET_ERR_ADDR_HI 0x750
|
||||
#define TARGET_ERR_ADDR_HI__VALUE 0xffff
|
||||
|
||||
#define CHNL_ACTIVE 0x760
|
||||
#define CHNL_ACTIVE__CHANNEL0 0x0001
|
||||
#define CHNL_ACTIVE__CHANNEL1 0x0002
|
||||
#define CHNL_ACTIVE__CHANNEL2 0x0004
|
||||
#define CHNL_ACTIVE__CHANNEL3 0x0008
|
||||
|
||||
#define ACTIVE_SRC_ID 0x800
|
||||
#define ACTIVE_SRC_ID__VALUE 0x00ff
|
||||
|
||||
#define PTN_INTR 0x810
|
||||
#define PTN_INTR__CONFIG_ERROR 0x0001
|
||||
#define PTN_INTR__ACCESS_ERROR_BANK0 0x0002
|
||||
#define PTN_INTR__ACCESS_ERROR_BANK1 0x0004
|
||||
#define PTN_INTR__ACCESS_ERROR_BANK2 0x0008
|
||||
#define PTN_INTR__ACCESS_ERROR_BANK3 0x0010
|
||||
#define PTN_INTR__REG_ACCESS_ERROR 0x0020
|
||||
|
||||
#define PTN_INTR_EN 0x820
|
||||
#define PTN_INTR_EN__CONFIG_ERROR 0x0001
|
||||
#define PTN_INTR_EN__ACCESS_ERROR_BANK0 0x0002
|
||||
#define PTN_INTR_EN__ACCESS_ERROR_BANK1 0x0004
|
||||
#define PTN_INTR_EN__ACCESS_ERROR_BANK2 0x0008
|
||||
#define PTN_INTR_EN__ACCESS_ERROR_BANK3 0x0010
|
||||
#define PTN_INTR_EN__REG_ACCESS_ERROR 0x0020
|
||||
|
||||
#define PERM_SRC_ID(__bank) (0x830 + ((__bank) * 0x40))
|
||||
#define PERM_SRC_ID__SRCID 0x00ff
|
||||
#define PERM_SRC_ID__DIRECT_ACCESS_ACTIVE 0x0800
|
||||
#define PERM_SRC_ID__WRITE_ACTIVE 0x2000
|
||||
#define PERM_SRC_ID__READ_ACTIVE 0x4000
|
||||
#define PERM_SRC_ID__PARTITION_VALID 0x8000
|
||||
|
||||
#define MIN_BLK_ADDR(__bank) (0x840 + ((__bank) * 0x40))
|
||||
#define MIN_BLK_ADDR__VALUE 0xffff
|
||||
|
||||
#define MAX_BLK_ADDR(__bank) (0x850 + ((__bank) * 0x40))
|
||||
#define MAX_BLK_ADDR__VALUE 0xffff
|
||||
|
||||
#define MIN_MAX_BANK(__bank) (0x860 + ((__bank) * 0x40))
|
||||
#define MIN_MAX_BANK__MIN_VALUE 0x0003
|
||||
#define MIN_MAX_BANK__MAX_VALUE 0x000c
|
||||
|
||||
|
||||
/* ffsdefs.h */
|
||||
#define CLEAR 0 /*use this to clear a field instead of "fail"*/
|
||||
#define SET 1 /*use this to set a field instead of "pass"*/
|
||||
#define FAIL 1 /*failed flag*/
|
||||
#define PASS 0 /*success flag*/
|
||||
#define ERR -1 /*error flag*/
|
||||
|
||||
/* lld.h */
|
||||
#define GOOD_BLOCK 0
|
||||
#define DEFECTIVE_BLOCK 1
|
||||
#define READ_ERROR 2
|
||||
|
||||
#define CLK_X 5
|
||||
#define CLK_MULTI 4
|
||||
|
||||
/* spectraswconfig.h */
|
||||
#define CMD_DMA 0
|
||||
|
||||
#define SPECTRA_PARTITION_ID 0
|
||||
/**** Block Table and Reserved Block Parameters *****/
|
||||
#define SPECTRA_START_BLOCK 3
|
||||
#define NUM_FREE_BLOCKS_GATE 30
|
||||
|
||||
/* KBV - Updated to LNW scratch register address */
|
||||
#define SCRATCH_REG_ADDR CONFIG_MTD_NAND_DENALI_SCRATCH_REG_ADDR
|
||||
#define SCRATCH_REG_SIZE 64
|
||||
|
||||
#define GLOB_HWCTL_DEFAULT_BLKS 2048
|
||||
|
||||
#define SUPPORT_15BITECC 1
|
||||
#define SUPPORT_8BITECC 1
|
||||
|
||||
#define CUSTOM_CONF_PARAMS 0
|
||||
|
||||
#define ONFI_BLOOM_TIME 1
|
||||
#define MODE5_WORKAROUND 0
|
||||
|
||||
|
||||
#define MODE_00 0x00000000
|
||||
#define MODE_01 0x04000000
|
||||
#define MODE_10 0x08000000
|
||||
#define MODE_11 0x0C000000
|
||||
|
||||
|
||||
#define DATA_TRANSFER_MODE 0
|
||||
#define PROTECTION_PER_BLOCK 1
|
||||
#define LOAD_WAIT_COUNT 2
|
||||
#define PROGRAM_WAIT_COUNT 3
|
||||
#define ERASE_WAIT_COUNT 4
|
||||
#define INT_MONITOR_CYCLE_COUNT 5
|
||||
#define READ_BUSY_PIN_ENABLED 6
|
||||
#define MULTIPLANE_OPERATION_SUPPORT 7
|
||||
#define PRE_FETCH_MODE 8
|
||||
#define CE_DONT_CARE_SUPPORT 9
|
||||
#define COPYBACK_SUPPORT 10
|
||||
#define CACHE_WRITE_SUPPORT 11
|
||||
#define CACHE_READ_SUPPORT 12
|
||||
#define NUM_PAGES_IN_BLOCK 13
|
||||
#define ECC_ENABLE_SELECT 14
|
||||
#define WRITE_ENABLE_2_READ_ENABLE 15
|
||||
#define ADDRESS_2_DATA 16
|
||||
#define READ_ENABLE_2_WRITE_ENABLE 17
|
||||
#define TWO_ROW_ADDRESS_CYCLES 18
|
||||
#define MULTIPLANE_ADDRESS_RESTRICT 19
|
||||
#define ACC_CLOCKS 20
|
||||
#define READ_WRITE_ENABLE_LOW_COUNT 21
|
||||
#define READ_WRITE_ENABLE_HIGH_COUNT 22
|
||||
|
||||
#define ECC_SECTOR_SIZE 512
|
||||
|
||||
struct nand_buf {
|
||||
int head;
|
||||
int tail;
|
||||
uint8_t *buf;
|
||||
dma_addr_t dma_buf;
|
||||
};
|
||||
|
||||
#define INTEL_CE4100 1
|
||||
#define INTEL_MRST 2
|
||||
#define DT 3
|
||||
|
||||
struct denali_nand_info {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip nand;
|
||||
int flash_bank; /* currently selected chip */
|
||||
int status;
|
||||
int platform;
|
||||
struct nand_buf buf;
|
||||
struct device *dev;
|
||||
int total_used_banks;
|
||||
uint32_t block; /* stored for future use */
|
||||
uint16_t page;
|
||||
void __iomem *flash_reg; /* Mapped io reg base address */
|
||||
void __iomem *flash_mem; /* Mapped io reg base address */
|
||||
|
||||
/* elements used by ISR */
|
||||
struct completion complete;
|
||||
spinlock_t irq_lock;
|
||||
uint32_t irq_status;
|
||||
int irq_debug_array[32];
|
||||
int idx;
|
||||
int irq;
|
||||
|
||||
uint32_t devnum; /* represent how many nands connected */
|
||||
uint32_t fwblks; /* represent how many blocks FW used */
|
||||
uint32_t totalblks;
|
||||
uint32_t blksperchip;
|
||||
uint32_t bbtskipbytes;
|
||||
uint32_t max_banks;
|
||||
};
|
||||
|
||||
extern int denali_init(struct denali_nand_info *denali);
|
||||
extern void denali_remove(struct denali_nand_info *denali);
|
||||
|
||||
#endif /* __DENALI_H__ */
|
||||
132
drivers/mtd/nand/denali_dt.c
Normal file
132
drivers/mtd/nand/denali_dt.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* NAND Flash Controller Device Driver for DT
|
||||
*
|
||||
* Copyright © 2011, Picochip.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "denali.h"
|
||||
|
||||
struct denali_dt {
|
||||
struct denali_nand_info denali;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static const struct of_device_id denali_nand_dt_ids[] = {
|
||||
{ .compatible = "denali,denali-nand-dt" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, denali_nand_dt_ids);
|
||||
|
||||
static u64 denali_dma_mask;
|
||||
|
||||
static int denali_dt_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct resource *denali_reg, *nand_data;
|
||||
struct denali_dt *dt;
|
||||
struct denali_nand_info *denali;
|
||||
int ret;
|
||||
const struct of_device_id *of_id;
|
||||
|
||||
of_id = of_match_device(denali_nand_dt_ids, &ofdev->dev);
|
||||
if (of_id) {
|
||||
ofdev->id_entry = of_id->data;
|
||||
} else {
|
||||
pr_err("Failed to find the right device id.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dt = devm_kzalloc(&ofdev->dev, sizeof(*dt), GFP_KERNEL);
|
||||
if (!dt)
|
||||
return -ENOMEM;
|
||||
denali = &dt->denali;
|
||||
|
||||
denali->platform = DT;
|
||||
denali->dev = &ofdev->dev;
|
||||
denali->irq = platform_get_irq(ofdev, 0);
|
||||
if (denali->irq < 0) {
|
||||
dev_err(&ofdev->dev, "no irq defined\n");
|
||||
return denali->irq;
|
||||
}
|
||||
|
||||
denali_reg = platform_get_resource_byname(ofdev, IORESOURCE_MEM, "denali_reg");
|
||||
denali->flash_reg = devm_ioremap_resource(&ofdev->dev, denali_reg);
|
||||
if (IS_ERR(denali->flash_reg))
|
||||
return PTR_ERR(denali->flash_reg);
|
||||
|
||||
nand_data = platform_get_resource_byname(ofdev, IORESOURCE_MEM, "nand_data");
|
||||
denali->flash_mem = devm_ioremap_resource(&ofdev->dev, nand_data);
|
||||
if (IS_ERR(denali->flash_mem))
|
||||
return PTR_ERR(denali->flash_mem);
|
||||
|
||||
if (!of_property_read_u32(ofdev->dev.of_node,
|
||||
"dma-mask", (u32 *)&denali_dma_mask)) {
|
||||
denali->dev->dma_mask = &denali_dma_mask;
|
||||
} else {
|
||||
denali->dev->dma_mask = NULL;
|
||||
}
|
||||
|
||||
dt->clk = devm_clk_get(&ofdev->dev, NULL);
|
||||
if (IS_ERR(dt->clk)) {
|
||||
dev_err(&ofdev->dev, "no clk available\n");
|
||||
return PTR_ERR(dt->clk);
|
||||
}
|
||||
clk_prepare_enable(dt->clk);
|
||||
|
||||
ret = denali_init(denali);
|
||||
if (ret)
|
||||
goto out_disable_clk;
|
||||
|
||||
platform_set_drvdata(ofdev, dt);
|
||||
return 0;
|
||||
|
||||
out_disable_clk:
|
||||
clk_disable_unprepare(dt->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int denali_dt_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct denali_dt *dt = platform_get_drvdata(ofdev);
|
||||
|
||||
denali_remove(&dt->denali);
|
||||
clk_disable(dt->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver denali_dt_driver = {
|
||||
.probe = denali_dt_probe,
|
||||
.remove = denali_dt_remove,
|
||||
.driver = {
|
||||
.name = "denali-nand-dt",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = denali_nand_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(denali_dt_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Jamie Iles");
|
||||
MODULE_DESCRIPTION("DT driver for Denali NAND controller");
|
||||
142
drivers/mtd/nand/denali_pci.c
Normal file
142
drivers/mtd/nand/denali_pci.c
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* NAND Flash Controller Device Driver
|
||||
* Copyright © 2009-2010, Intel Corporation and its suppliers.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "denali.h"
|
||||
|
||||
#define DENALI_NAND_NAME "denali-nand-pci"
|
||||
|
||||
/* List of platforms this NAND controller has be integrated into */
|
||||
static const struct pci_device_id denali_pci_ids[] = {
|
||||
{ PCI_VDEVICE(INTEL, 0x0701), INTEL_CE4100 },
|
||||
{ PCI_VDEVICE(INTEL, 0x0809), INTEL_MRST },
|
||||
{ /* end: all zeroes */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, denali_pci_ids);
|
||||
|
||||
static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
resource_size_t csr_base, mem_base;
|
||||
unsigned long csr_len, mem_len;
|
||||
struct denali_nand_info *denali;
|
||||
|
||||
denali = kzalloc(sizeof(*denali), GFP_KERNEL);
|
||||
if (!denali)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pci_enable_device(dev);
|
||||
if (ret) {
|
||||
pr_err("Spectra: pci_enable_device failed.\n");
|
||||
goto failed_alloc_memery;
|
||||
}
|
||||
|
||||
if (id->driver_data == INTEL_CE4100) {
|
||||
denali->platform = INTEL_CE4100;
|
||||
mem_base = pci_resource_start(dev, 0);
|
||||
mem_len = pci_resource_len(dev, 1);
|
||||
csr_base = pci_resource_start(dev, 1);
|
||||
csr_len = pci_resource_len(dev, 1);
|
||||
} else {
|
||||
denali->platform = INTEL_MRST;
|
||||
csr_base = pci_resource_start(dev, 0);
|
||||
csr_len = pci_resource_len(dev, 0);
|
||||
mem_base = pci_resource_start(dev, 1);
|
||||
mem_len = pci_resource_len(dev, 1);
|
||||
if (!mem_len) {
|
||||
mem_base = csr_base + csr_len;
|
||||
mem_len = csr_len;
|
||||
}
|
||||
}
|
||||
|
||||
pci_set_master(dev);
|
||||
denali->dev = &dev->dev;
|
||||
denali->irq = dev->irq;
|
||||
|
||||
ret = pci_request_regions(dev, DENALI_NAND_NAME);
|
||||
if (ret) {
|
||||
pr_err("Spectra: Unable to request memory regions\n");
|
||||
goto failed_enable_dev;
|
||||
}
|
||||
|
||||
denali->flash_reg = ioremap_nocache(csr_base, csr_len);
|
||||
if (!denali->flash_reg) {
|
||||
pr_err("Spectra: Unable to remap memory region\n");
|
||||
ret = -ENOMEM;
|
||||
goto failed_req_regions;
|
||||
}
|
||||
|
||||
denali->flash_mem = ioremap_nocache(mem_base, mem_len);
|
||||
if (!denali->flash_mem) {
|
||||
pr_err("Spectra: ioremap_nocache failed!");
|
||||
ret = -ENOMEM;
|
||||
goto failed_remap_reg;
|
||||
}
|
||||
|
||||
ret = denali_init(denali);
|
||||
if (ret)
|
||||
goto failed_remap_mem;
|
||||
|
||||
pci_set_drvdata(dev, denali);
|
||||
|
||||
return 0;
|
||||
|
||||
failed_remap_mem:
|
||||
iounmap(denali->flash_mem);
|
||||
failed_remap_reg:
|
||||
iounmap(denali->flash_reg);
|
||||
failed_req_regions:
|
||||
pci_release_regions(dev);
|
||||
failed_enable_dev:
|
||||
pci_disable_device(dev);
|
||||
failed_alloc_memery:
|
||||
kfree(denali);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* driver exit point */
|
||||
static void denali_pci_remove(struct pci_dev *dev)
|
||||
{
|
||||
struct denali_nand_info *denali = pci_get_drvdata(dev);
|
||||
|
||||
denali_remove(denali);
|
||||
iounmap(denali->flash_reg);
|
||||
iounmap(denali->flash_mem);
|
||||
pci_release_regions(dev);
|
||||
pci_disable_device(dev);
|
||||
kfree(denali);
|
||||
}
|
||||
|
||||
static struct pci_driver denali_pci_driver = {
|
||||
.name = DENALI_NAND_NAME,
|
||||
.id_table = denali_pci_ids,
|
||||
.probe = denali_pci_probe,
|
||||
.remove = denali_pci_remove,
|
||||
};
|
||||
|
||||
static int denali_init_pci(void)
|
||||
{
|
||||
return pci_register_driver(&denali_pci_driver);
|
||||
}
|
||||
module_init(denali_init_pci);
|
||||
|
||||
static void denali_exit_pci(void)
|
||||
{
|
||||
pci_unregister_driver(&denali_pci_driver);
|
||||
}
|
||||
module_exit(denali_exit_pci);
|
||||
1716
drivers/mtd/nand/diskonchip.c
Normal file
1716
drivers/mtd/nand/diskonchip.c
Normal file
File diff suppressed because it is too large
Load diff
1394
drivers/mtd/nand/docg4.c
Normal file
1394
drivers/mtd/nand/docg4.c
Normal file
File diff suppressed because it is too large
Load diff
964
drivers/mtd/nand/fsl_elbc_nand.c
Normal file
964
drivers/mtd/nand/fsl_elbc_nand.c
Normal file
|
|
@ -0,0 +1,964 @@
|
|||
/* Freescale Enhanced Local Bus Controller NAND driver
|
||||
*
|
||||
* Copyright © 2006-2007, 2010 Freescale Semiconductor
|
||||
*
|
||||
* Authors: Nick Spence <nick.spence@freescale.com>,
|
||||
* Scott Wood <scottwood@freescale.com>
|
||||
* Jack Lan <jack.lan@freescale.com>
|
||||
* Roy Zang <tie-fei.zang@freescale.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/fsl_lbc.h>
|
||||
|
||||
#define MAX_BANKS 8
|
||||
#define ERR_BYTE 0xFF /* Value returned for read bytes when read failed */
|
||||
#define FCM_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait for FCM */
|
||||
|
||||
/* mtd information per set */
|
||||
|
||||
struct fsl_elbc_mtd {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
struct fsl_lbc_ctrl *ctrl;
|
||||
|
||||
struct device *dev;
|
||||
int bank; /* Chip select bank number */
|
||||
u8 __iomem *vbase; /* Chip select base virtual address */
|
||||
int page_size; /* NAND page size (0=512, 1=2048) */
|
||||
unsigned int fmr; /* FCM Flash Mode Register value */
|
||||
};
|
||||
|
||||
/* Freescale eLBC FCM controller information */
|
||||
|
||||
struct fsl_elbc_fcm_ctrl {
|
||||
struct nand_hw_control controller;
|
||||
struct fsl_elbc_mtd *chips[MAX_BANKS];
|
||||
|
||||
u8 __iomem *addr; /* Address of assigned FCM buffer */
|
||||
unsigned int page; /* Last page written to / read from */
|
||||
unsigned int read_bytes; /* Number of bytes read during command */
|
||||
unsigned int column; /* Saved column from SEQIN */
|
||||
unsigned int index; /* Pointer to next byte to 'read' */
|
||||
unsigned int status; /* status read from LTESR after last op */
|
||||
unsigned int mdr; /* UPM/FCM Data Register value */
|
||||
unsigned int use_mdr; /* Non zero if the MDR is to be set */
|
||||
unsigned int oob; /* Non zero if operating on OOB data */
|
||||
unsigned int counter; /* counter for the initializations */
|
||||
unsigned int max_bitflips; /* Saved during READ0 cmd */
|
||||
};
|
||||
|
||||
/* These map to the positions used by the FCM hardware ECC generator */
|
||||
|
||||
/* Small Page FLASH with FMR[ECCM] = 0 */
|
||||
static struct nand_ecclayout fsl_elbc_oob_sp_eccm0 = {
|
||||
.eccbytes = 3,
|
||||
.eccpos = {6, 7, 8},
|
||||
.oobfree = { {0, 5}, {9, 7} },
|
||||
};
|
||||
|
||||
/* Small Page FLASH with FMR[ECCM] = 1 */
|
||||
static struct nand_ecclayout fsl_elbc_oob_sp_eccm1 = {
|
||||
.eccbytes = 3,
|
||||
.eccpos = {8, 9, 10},
|
||||
.oobfree = { {0, 5}, {6, 2}, {11, 5} },
|
||||
};
|
||||
|
||||
/* Large Page FLASH with FMR[ECCM] = 0 */
|
||||
static struct nand_ecclayout fsl_elbc_oob_lp_eccm0 = {
|
||||
.eccbytes = 12,
|
||||
.eccpos = {6, 7, 8, 22, 23, 24, 38, 39, 40, 54, 55, 56},
|
||||
.oobfree = { {1, 5}, {9, 13}, {25, 13}, {41, 13}, {57, 7} },
|
||||
};
|
||||
|
||||
/* Large Page FLASH with FMR[ECCM] = 1 */
|
||||
static struct nand_ecclayout fsl_elbc_oob_lp_eccm1 = {
|
||||
.eccbytes = 12,
|
||||
.eccpos = {8, 9, 10, 24, 25, 26, 40, 41, 42, 56, 57, 58},
|
||||
.oobfree = { {1, 7}, {11, 13}, {27, 13}, {43, 13}, {59, 5} },
|
||||
};
|
||||
|
||||
/*
|
||||
* ELBC may use HW ECC, so that OOB offsets, that NAND core uses for bbt,
|
||||
* interfere with ECC positions, that's why we implement our own descriptors.
|
||||
* OOB {11, 5}, works for both SP and LP chips, with ECCM = 1 and ECCM = 0.
|
||||
*/
|
||||
static u8 bbt_pattern[] = {'B', 'b', 't', '0' };
|
||||
static u8 mirror_pattern[] = {'1', 't', 'b', 'B' };
|
||||
|
||||
static struct nand_bbt_descr bbt_main_descr = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
|
||||
NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 11,
|
||||
.len = 4,
|
||||
.veroffs = 15,
|
||||
.maxblocks = 4,
|
||||
.pattern = bbt_pattern,
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr bbt_mirror_descr = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
|
||||
NAND_BBT_2BIT | NAND_BBT_VERSION,
|
||||
.offs = 11,
|
||||
.len = 4,
|
||||
.veroffs = 15,
|
||||
.maxblocks = 4,
|
||||
.pattern = mirror_pattern,
|
||||
};
|
||||
|
||||
/*=================================*/
|
||||
|
||||
/*
|
||||
* Set up the FCM hardware block and page address fields, and the fcm
|
||||
* structure addr field to point to the correct FCM buffer in memory
|
||||
*/
|
||||
static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
|
||||
int buf_num;
|
||||
|
||||
elbc_fcm_ctrl->page = page_addr;
|
||||
|
||||
if (priv->page_size) {
|
||||
/*
|
||||
* large page size chip : FPAR[PI] save the lowest 6 bits,
|
||||
* FBAR[BLK] save the other bits.
|
||||
*/
|
||||
out_be32(&lbc->fbar, page_addr >> 6);
|
||||
out_be32(&lbc->fpar,
|
||||
((page_addr << FPAR_LP_PI_SHIFT) & FPAR_LP_PI) |
|
||||
(oob ? FPAR_LP_MS : 0) | column);
|
||||
buf_num = (page_addr & 1) << 2;
|
||||
} else {
|
||||
/*
|
||||
* small page size chip : FPAR[PI] save the lowest 5 bits,
|
||||
* FBAR[BLK] save the other bits.
|
||||
*/
|
||||
out_be32(&lbc->fbar, page_addr >> 5);
|
||||
out_be32(&lbc->fpar,
|
||||
((page_addr << FPAR_SP_PI_SHIFT) & FPAR_SP_PI) |
|
||||
(oob ? FPAR_SP_MS : 0) | column);
|
||||
buf_num = page_addr & 7;
|
||||
}
|
||||
|
||||
elbc_fcm_ctrl->addr = priv->vbase + buf_num * 1024;
|
||||
elbc_fcm_ctrl->index = column;
|
||||
|
||||
/* for OOB data point to the second half of the buffer */
|
||||
if (oob)
|
||||
elbc_fcm_ctrl->index += priv->page_size ? 2048 : 512;
|
||||
|
||||
dev_vdbg(priv->dev, "set_addr: bank=%d, "
|
||||
"elbc_fcm_ctrl->addr=0x%p (0x%p), "
|
||||
"index %x, pes %d ps %d\n",
|
||||
buf_num, elbc_fcm_ctrl->addr, priv->vbase,
|
||||
elbc_fcm_ctrl->index,
|
||||
chip->phys_erase_shift, chip->page_shift);
|
||||
}
|
||||
|
||||
/*
|
||||
* execute FCM command and wait for it to complete
|
||||
*/
|
||||
static int fsl_elbc_run_command(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
|
||||
/* Setup the FMR[OP] to execute without write protection */
|
||||
out_be32(&lbc->fmr, priv->fmr | 3);
|
||||
if (elbc_fcm_ctrl->use_mdr)
|
||||
out_be32(&lbc->mdr, elbc_fcm_ctrl->mdr);
|
||||
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_run_command: fmr=%08x fir=%08x fcr=%08x\n",
|
||||
in_be32(&lbc->fmr), in_be32(&lbc->fir), in_be32(&lbc->fcr));
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_run_command: fbar=%08x fpar=%08x "
|
||||
"fbcr=%08x bank=%d\n",
|
||||
in_be32(&lbc->fbar), in_be32(&lbc->fpar),
|
||||
in_be32(&lbc->fbcr), priv->bank);
|
||||
|
||||
ctrl->irq_status = 0;
|
||||
/* execute special operation */
|
||||
out_be32(&lbc->lsor, priv->bank);
|
||||
|
||||
/* wait for FCM complete flag or timeout */
|
||||
wait_event_timeout(ctrl->irq_wait, ctrl->irq_status,
|
||||
FCM_TIMEOUT_MSECS * HZ/1000);
|
||||
elbc_fcm_ctrl->status = ctrl->irq_status;
|
||||
/* store mdr value in case it was needed */
|
||||
if (elbc_fcm_ctrl->use_mdr)
|
||||
elbc_fcm_ctrl->mdr = in_be32(&lbc->mdr);
|
||||
|
||||
elbc_fcm_ctrl->use_mdr = 0;
|
||||
|
||||
if (elbc_fcm_ctrl->status != LTESR_CC) {
|
||||
dev_info(priv->dev,
|
||||
"command failed: fir %x fcr %x status %x mdr %x\n",
|
||||
in_be32(&lbc->fir), in_be32(&lbc->fcr),
|
||||
elbc_fcm_ctrl->status, elbc_fcm_ctrl->mdr);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (chip->ecc.mode != NAND_ECC_HW)
|
||||
return 0;
|
||||
|
||||
elbc_fcm_ctrl->max_bitflips = 0;
|
||||
|
||||
if (elbc_fcm_ctrl->read_bytes == mtd->writesize + mtd->oobsize) {
|
||||
uint32_t lteccr = in_be32(&lbc->lteccr);
|
||||
/*
|
||||
* if command was a full page read and the ELBC
|
||||
* has the LTECCR register, then bits 12-15 (ppc order) of
|
||||
* LTECCR indicates which 512 byte sub-pages had fixed errors.
|
||||
* bits 28-31 are uncorrectable errors, marked elsewhere.
|
||||
* for small page nand only 1 bit is used.
|
||||
* if the ELBC doesn't have the lteccr register it reads 0
|
||||
* FIXME: 4 bits can be corrected on NANDs with 2k pages, so
|
||||
* count the number of sub-pages with bitflips and update
|
||||
* ecc_stats.corrected accordingly.
|
||||
*/
|
||||
if (lteccr & 0x000F000F)
|
||||
out_be32(&lbc->lteccr, 0x000F000F); /* clear lteccr */
|
||||
if (lteccr & 0x000F0000) {
|
||||
mtd->ecc_stats.corrected++;
|
||||
elbc_fcm_ctrl->max_bitflips = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsl_elbc_do_read(struct nand_chip *chip, int oob)
|
||||
{
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
|
||||
if (priv->page_size) {
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_CA << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_PA << FIR_OP2_SHIFT) |
|
||||
(FIR_OP_CM1 << FIR_OP3_SHIFT) |
|
||||
(FIR_OP_RBW << FIR_OP4_SHIFT));
|
||||
|
||||
out_be32(&lbc->fcr, (NAND_CMD_READ0 << FCR_CMD0_SHIFT) |
|
||||
(NAND_CMD_READSTART << FCR_CMD1_SHIFT));
|
||||
} else {
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_CA << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_PA << FIR_OP2_SHIFT) |
|
||||
(FIR_OP_RBW << FIR_OP3_SHIFT));
|
||||
|
||||
if (oob)
|
||||
out_be32(&lbc->fcr, NAND_CMD_READOOB << FCR_CMD0_SHIFT);
|
||||
else
|
||||
out_be32(&lbc->fcr, NAND_CMD_READ0 << FCR_CMD0_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
/* cmdfunc send commands to the FCM */
|
||||
static void fsl_elbc_cmdfunc(struct mtd_info *mtd, unsigned int command,
|
||||
int column, int page_addr)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
|
||||
elbc_fcm_ctrl->use_mdr = 0;
|
||||
|
||||
/* clear the read buffer */
|
||||
elbc_fcm_ctrl->read_bytes = 0;
|
||||
if (command != NAND_CMD_PAGEPROG)
|
||||
elbc_fcm_ctrl->index = 0;
|
||||
|
||||
switch (command) {
|
||||
/* READ0 and READ1 read the entire buffer to use hardware ECC. */
|
||||
case NAND_CMD_READ1:
|
||||
column += 256;
|
||||
|
||||
/* fall-through */
|
||||
case NAND_CMD_READ0:
|
||||
dev_dbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_READ0, page_addr:"
|
||||
" 0x%x, column: 0x%x.\n", page_addr, column);
|
||||
|
||||
|
||||
out_be32(&lbc->fbcr, 0); /* read entire page to enable ECC */
|
||||
set_addr(mtd, 0, page_addr, 0);
|
||||
|
||||
elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize;
|
||||
elbc_fcm_ctrl->index += column;
|
||||
|
||||
fsl_elbc_do_read(chip, 0);
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
|
||||
/* READOOB reads only the OOB because no ECC is performed. */
|
||||
case NAND_CMD_READOOB:
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_READOOB, page_addr:"
|
||||
" 0x%x, column: 0x%x.\n", page_addr, column);
|
||||
|
||||
out_be32(&lbc->fbcr, mtd->oobsize - column);
|
||||
set_addr(mtd, column, page_addr, 1);
|
||||
|
||||
elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize;
|
||||
|
||||
fsl_elbc_do_read(chip, 1);
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
|
||||
case NAND_CMD_READID:
|
||||
case NAND_CMD_PARAM:
|
||||
dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD %x\n", command);
|
||||
|
||||
out_be32(&lbc->fir, (FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_UA << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_RBW << FIR_OP2_SHIFT));
|
||||
out_be32(&lbc->fcr, command << FCR_CMD0_SHIFT);
|
||||
/*
|
||||
* although currently it's 8 bytes for READID, we always read
|
||||
* the maximum 256 bytes(for PARAM)
|
||||
*/
|
||||
out_be32(&lbc->fbcr, 256);
|
||||
elbc_fcm_ctrl->read_bytes = 256;
|
||||
elbc_fcm_ctrl->use_mdr = 1;
|
||||
elbc_fcm_ctrl->mdr = column;
|
||||
set_addr(mtd, 0, 0, 0);
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
|
||||
/* ERASE1 stores the block and page address */
|
||||
case NAND_CMD_ERASE1:
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_ERASE1, "
|
||||
"page_addr: 0x%x.\n", page_addr);
|
||||
set_addr(mtd, 0, page_addr, 0);
|
||||
return;
|
||||
|
||||
/* ERASE2 uses the block and page address from ERASE1 */
|
||||
case NAND_CMD_ERASE2:
|
||||
dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_ERASE2.\n");
|
||||
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_PA << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_CM2 << FIR_OP2_SHIFT) |
|
||||
(FIR_OP_CW1 << FIR_OP3_SHIFT) |
|
||||
(FIR_OP_RS << FIR_OP4_SHIFT));
|
||||
|
||||
out_be32(&lbc->fcr,
|
||||
(NAND_CMD_ERASE1 << FCR_CMD0_SHIFT) |
|
||||
(NAND_CMD_STATUS << FCR_CMD1_SHIFT) |
|
||||
(NAND_CMD_ERASE2 << FCR_CMD2_SHIFT));
|
||||
|
||||
out_be32(&lbc->fbcr, 0);
|
||||
elbc_fcm_ctrl->read_bytes = 0;
|
||||
elbc_fcm_ctrl->use_mdr = 1;
|
||||
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
|
||||
/* SEQIN sets up the addr buffer and all registers except the length */
|
||||
case NAND_CMD_SEQIN: {
|
||||
__be32 fcr;
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_SEQIN/PAGE_PROG, "
|
||||
"page_addr: 0x%x, column: 0x%x.\n",
|
||||
page_addr, column);
|
||||
|
||||
elbc_fcm_ctrl->column = column;
|
||||
elbc_fcm_ctrl->use_mdr = 1;
|
||||
|
||||
if (column >= mtd->writesize) {
|
||||
/* OOB area */
|
||||
column -= mtd->writesize;
|
||||
elbc_fcm_ctrl->oob = 1;
|
||||
} else {
|
||||
WARN_ON(column != 0);
|
||||
elbc_fcm_ctrl->oob = 0;
|
||||
}
|
||||
|
||||
fcr = (NAND_CMD_STATUS << FCR_CMD1_SHIFT) |
|
||||
(NAND_CMD_SEQIN << FCR_CMD2_SHIFT) |
|
||||
(NAND_CMD_PAGEPROG << FCR_CMD3_SHIFT);
|
||||
|
||||
if (priv->page_size) {
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM2 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_CA << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_PA << FIR_OP2_SHIFT) |
|
||||
(FIR_OP_WB << FIR_OP3_SHIFT) |
|
||||
(FIR_OP_CM3 << FIR_OP4_SHIFT) |
|
||||
(FIR_OP_CW1 << FIR_OP5_SHIFT) |
|
||||
(FIR_OP_RS << FIR_OP6_SHIFT));
|
||||
} else {
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_CM2 << FIR_OP1_SHIFT) |
|
||||
(FIR_OP_CA << FIR_OP2_SHIFT) |
|
||||
(FIR_OP_PA << FIR_OP3_SHIFT) |
|
||||
(FIR_OP_WB << FIR_OP4_SHIFT) |
|
||||
(FIR_OP_CM3 << FIR_OP5_SHIFT) |
|
||||
(FIR_OP_CW1 << FIR_OP6_SHIFT) |
|
||||
(FIR_OP_RS << FIR_OP7_SHIFT));
|
||||
|
||||
if (elbc_fcm_ctrl->oob)
|
||||
/* OOB area --> READOOB */
|
||||
fcr |= NAND_CMD_READOOB << FCR_CMD0_SHIFT;
|
||||
else
|
||||
/* First 256 bytes --> READ0 */
|
||||
fcr |= NAND_CMD_READ0 << FCR_CMD0_SHIFT;
|
||||
}
|
||||
|
||||
out_be32(&lbc->fcr, fcr);
|
||||
set_addr(mtd, column, page_addr, elbc_fcm_ctrl->oob);
|
||||
return;
|
||||
}
|
||||
|
||||
/* PAGEPROG reuses all of the setup from SEQIN and adds the length */
|
||||
case NAND_CMD_PAGEPROG: {
|
||||
dev_vdbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_PAGEPROG "
|
||||
"writing %d bytes.\n", elbc_fcm_ctrl->index);
|
||||
|
||||
/* if the write did not start at 0 or is not a full page
|
||||
* then set the exact length, otherwise use a full page
|
||||
* write so the HW generates the ECC.
|
||||
*/
|
||||
if (elbc_fcm_ctrl->oob || elbc_fcm_ctrl->column != 0 ||
|
||||
elbc_fcm_ctrl->index != mtd->writesize + mtd->oobsize)
|
||||
out_be32(&lbc->fbcr,
|
||||
elbc_fcm_ctrl->index - elbc_fcm_ctrl->column);
|
||||
else
|
||||
out_be32(&lbc->fbcr, 0);
|
||||
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CMD_STATUS must read the status byte while CEB is active */
|
||||
/* Note - it does not wait for the ready line */
|
||||
case NAND_CMD_STATUS:
|
||||
out_be32(&lbc->fir,
|
||||
(FIR_OP_CM0 << FIR_OP0_SHIFT) |
|
||||
(FIR_OP_RBW << FIR_OP1_SHIFT));
|
||||
out_be32(&lbc->fcr, NAND_CMD_STATUS << FCR_CMD0_SHIFT);
|
||||
out_be32(&lbc->fbcr, 1);
|
||||
set_addr(mtd, 0, 0, 0);
|
||||
elbc_fcm_ctrl->read_bytes = 1;
|
||||
|
||||
fsl_elbc_run_command(mtd);
|
||||
|
||||
/* The chip always seems to report that it is
|
||||
* write-protected, even when it is not.
|
||||
*/
|
||||
setbits8(elbc_fcm_ctrl->addr, NAND_STATUS_WP);
|
||||
return;
|
||||
|
||||
/* RESET without waiting for the ready line */
|
||||
case NAND_CMD_RESET:
|
||||
dev_dbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_RESET.\n");
|
||||
out_be32(&lbc->fir, FIR_OP_CM0 << FIR_OP0_SHIFT);
|
||||
out_be32(&lbc->fcr, NAND_CMD_RESET << FCR_CMD0_SHIFT);
|
||||
fsl_elbc_run_command(mtd);
|
||||
return;
|
||||
|
||||
default:
|
||||
dev_err(priv->dev,
|
||||
"fsl_elbc_cmdfunc: error, unsupported command 0x%x.\n",
|
||||
command);
|
||||
}
|
||||
}
|
||||
|
||||
static void fsl_elbc_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
/* The hardware does not seem to support multiple
|
||||
* chips per bank.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* Write buf to the FCM Controller Data Buffer
|
||||
*/
|
||||
static void fsl_elbc_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
unsigned int bufsize = mtd->writesize + mtd->oobsize;
|
||||
|
||||
if (len <= 0) {
|
||||
dev_err(priv->dev, "write_buf of %d bytes", len);
|
||||
elbc_fcm_ctrl->status = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((unsigned int)len > bufsize - elbc_fcm_ctrl->index) {
|
||||
dev_err(priv->dev,
|
||||
"write_buf beyond end of buffer "
|
||||
"(%d requested, %u available)\n",
|
||||
len, bufsize - elbc_fcm_ctrl->index);
|
||||
len = bufsize - elbc_fcm_ctrl->index;
|
||||
}
|
||||
|
||||
memcpy_toio(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], buf, len);
|
||||
/*
|
||||
* This is workaround for the weird elbc hangs during nand write,
|
||||
* Scott Wood says: "...perhaps difference in how long it takes a
|
||||
* write to make it through the localbus compared to a write to IMMR
|
||||
* is causing problems, and sync isn't helping for some reason."
|
||||
* Reading back the last byte helps though.
|
||||
*/
|
||||
in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index] + len - 1);
|
||||
|
||||
elbc_fcm_ctrl->index += len;
|
||||
}
|
||||
|
||||
/*
|
||||
* read a byte from either the FCM hardware buffer if it has any data left
|
||||
* otherwise issue a command to read a single byte.
|
||||
*/
|
||||
static u8 fsl_elbc_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
|
||||
/* If there are still bytes in the FCM, then use the next byte. */
|
||||
if (elbc_fcm_ctrl->index < elbc_fcm_ctrl->read_bytes)
|
||||
return in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index++]);
|
||||
|
||||
dev_err(priv->dev, "read_byte beyond end of buffer\n");
|
||||
return ERR_BYTE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the FCM Controller Data Buffer
|
||||
*/
|
||||
static void fsl_elbc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
int avail;
|
||||
|
||||
if (len < 0)
|
||||
return;
|
||||
|
||||
avail = min((unsigned int)len,
|
||||
elbc_fcm_ctrl->read_bytes - elbc_fcm_ctrl->index);
|
||||
memcpy_fromio(buf, &elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], avail);
|
||||
elbc_fcm_ctrl->index += avail;
|
||||
|
||||
if (len > avail)
|
||||
dev_err(priv->dev,
|
||||
"read_buf beyond end of buffer "
|
||||
"(%d requested, %d available)\n",
|
||||
len, avail);
|
||||
}
|
||||
|
||||
/* This function is called after Program and Erase Operations to
|
||||
* check for success or failure.
|
||||
*/
|
||||
static int fsl_elbc_wait(struct mtd_info *mtd, struct nand_chip *chip)
|
||||
{
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
|
||||
if (elbc_fcm_ctrl->status != LTESR_CC)
|
||||
return NAND_STATUS_FAIL;
|
||||
|
||||
/* The chip always seems to report that it is
|
||||
* write-protected, even when it is not.
|
||||
*/
|
||||
return (elbc_fcm_ctrl->mdr & 0xff) | NAND_STATUS_WP;
|
||||
}
|
||||
|
||||
static int fsl_elbc_chip_init_tail(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
unsigned int al;
|
||||
|
||||
/* calculate FMR Address Length field */
|
||||
al = 0;
|
||||
if (chip->pagemask & 0xffff0000)
|
||||
al++;
|
||||
if (chip->pagemask & 0xff000000)
|
||||
al++;
|
||||
|
||||
priv->fmr |= al << FMR_AL_SHIFT;
|
||||
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->numchips = %d\n",
|
||||
chip->numchips);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->chipsize = %lld\n",
|
||||
chip->chipsize);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->pagemask = %8x\n",
|
||||
chip->pagemask);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->chip_delay = %d\n",
|
||||
chip->chip_delay);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->badblockpos = %d\n",
|
||||
chip->badblockpos);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->chip_shift = %d\n",
|
||||
chip->chip_shift);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->page_shift = %d\n",
|
||||
chip->page_shift);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->phys_erase_shift = %d\n",
|
||||
chip->phys_erase_shift);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.mode = %d\n",
|
||||
chip->ecc.mode);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.steps = %d\n",
|
||||
chip->ecc.steps);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.bytes = %d\n",
|
||||
chip->ecc.bytes);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.total = %d\n",
|
||||
chip->ecc.total);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.layout = %p\n",
|
||||
chip->ecc.layout);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: mtd->flags = %08x\n", mtd->flags);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: mtd->size = %lld\n", mtd->size);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: mtd->erasesize = %d\n",
|
||||
mtd->erasesize);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: mtd->writesize = %d\n",
|
||||
mtd->writesize);
|
||||
dev_dbg(priv->dev, "fsl_elbc_init: mtd->oobsize = %d\n",
|
||||
mtd->oobsize);
|
||||
|
||||
/* adjust Option Register and ECC to match Flash page size */
|
||||
if (mtd->writesize == 512) {
|
||||
priv->page_size = 0;
|
||||
clrbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS);
|
||||
} else if (mtd->writesize == 2048) {
|
||||
priv->page_size = 1;
|
||||
setbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS);
|
||||
/* adjust ecc setup if needed */
|
||||
if ((in_be32(&lbc->bank[priv->bank].br) & BR_DECC) ==
|
||||
BR_DECC_CHK_GEN) {
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.layout = (priv->fmr & FMR_ECCM) ?
|
||||
&fsl_elbc_oob_lp_eccm1 :
|
||||
&fsl_elbc_oob_lp_eccm0;
|
||||
}
|
||||
} else {
|
||||
dev_err(priv->dev,
|
||||
"fsl_elbc_init: page size %d is not supported\n",
|
||||
mtd->writesize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
|
||||
|
||||
fsl_elbc_read_buf(mtd, buf, mtd->writesize);
|
||||
if (oob_required)
|
||||
fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
if (fsl_elbc_wait(mtd, chip) & NAND_STATUS_FAIL)
|
||||
mtd->ecc_stats.failed++;
|
||||
|
||||
return elbc_fcm_ctrl->max_bitflips;
|
||||
}
|
||||
|
||||
/* ECC will be calculated automatically, and errors will be detected in
|
||||
* waitfunc.
|
||||
*/
|
||||
static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
fsl_elbc_write_buf(mtd, buf, mtd->writesize);
|
||||
fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ECC will be calculated automatically, and errors will be detected in
|
||||
* waitfunc.
|
||||
*/
|
||||
static int fsl_elbc_write_subpage(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint32_t offset, uint32_t data_len,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
fsl_elbc_write_buf(mtd, buf, mtd->writesize);
|
||||
fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
|
||||
{
|
||||
struct fsl_lbc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand;
|
||||
struct nand_chip *chip = &priv->chip;
|
||||
|
||||
dev_dbg(priv->dev, "eLBC Set Information for bank %d\n", priv->bank);
|
||||
|
||||
/* Fill in fsl_elbc_mtd structure */
|
||||
priv->mtd.priv = chip;
|
||||
priv->mtd.owner = THIS_MODULE;
|
||||
|
||||
/* set timeout to maximum */
|
||||
priv->fmr = 15 << FMR_CWTO_SHIFT;
|
||||
if (in_be32(&lbc->bank[priv->bank].or) & OR_FCM_PGS)
|
||||
priv->fmr |= FMR_ECCM;
|
||||
|
||||
/* fill in nand_chip structure */
|
||||
/* set up function call table */
|
||||
chip->read_byte = fsl_elbc_read_byte;
|
||||
chip->write_buf = fsl_elbc_write_buf;
|
||||
chip->read_buf = fsl_elbc_read_buf;
|
||||
chip->select_chip = fsl_elbc_select_chip;
|
||||
chip->cmdfunc = fsl_elbc_cmdfunc;
|
||||
chip->waitfunc = fsl_elbc_wait;
|
||||
|
||||
chip->bbt_td = &bbt_main_descr;
|
||||
chip->bbt_md = &bbt_mirror_descr;
|
||||
|
||||
/* set up nand options */
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
chip->controller = &elbc_fcm_ctrl->controller;
|
||||
chip->priv = priv;
|
||||
|
||||
chip->ecc.read_page = fsl_elbc_read_page;
|
||||
chip->ecc.write_page = fsl_elbc_write_page;
|
||||
chip->ecc.write_subpage = fsl_elbc_write_subpage;
|
||||
|
||||
/* If CS Base Register selects full hardware ECC then use it */
|
||||
if ((in_be32(&lbc->bank[priv->bank].br) & BR_DECC) ==
|
||||
BR_DECC_CHK_GEN) {
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
/* put in small page settings and adjust later if needed */
|
||||
chip->ecc.layout = (priv->fmr & FMR_ECCM) ?
|
||||
&fsl_elbc_oob_sp_eccm1 : &fsl_elbc_oob_sp_eccm0;
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecc.strength = 1;
|
||||
} else {
|
||||
/* otherwise fall back to default software ECC */
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv)
|
||||
{
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
nand_release(&priv->mtd);
|
||||
|
||||
kfree(priv->mtd.name);
|
||||
|
||||
if (priv->vbase)
|
||||
iounmap(priv->vbase);
|
||||
|
||||
elbc_fcm_ctrl->chips[priv->bank] = NULL;
|
||||
kfree(priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(fsl_elbc_nand_mutex);
|
||||
|
||||
static int fsl_elbc_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct fsl_lbc_regs __iomem *lbc;
|
||||
struct fsl_elbc_mtd *priv;
|
||||
struct resource res;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl;
|
||||
static const char *part_probe_types[]
|
||||
= { "cmdlinepart", "RedBoot", "ofpart", NULL };
|
||||
int ret;
|
||||
int bank;
|
||||
struct device *dev;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
|
||||
return -ENODEV;
|
||||
lbc = fsl_lbc_ctrl_dev->regs;
|
||||
dev = fsl_lbc_ctrl_dev->dev;
|
||||
|
||||
/* get, allocate and map the memory resource */
|
||||
ret = of_address_to_resource(node, 0, &res);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to get resource\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* find which chip select it is connected to */
|
||||
for (bank = 0; bank < MAX_BANKS; bank++)
|
||||
if ((in_be32(&lbc->bank[bank].br) & BR_V) &&
|
||||
(in_be32(&lbc->bank[bank].br) & BR_MSEL) == BR_MS_FCM &&
|
||||
(in_be32(&lbc->bank[bank].br) &
|
||||
in_be32(&lbc->bank[bank].or) & BR_BA)
|
||||
== fsl_lbc_addr(res.start))
|
||||
break;
|
||||
|
||||
if (bank >= MAX_BANKS) {
|
||||
dev_err(dev, "address did not match any chip selects\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&fsl_elbc_nand_mutex);
|
||||
if (!fsl_lbc_ctrl_dev->nand) {
|
||||
elbc_fcm_ctrl = kzalloc(sizeof(*elbc_fcm_ctrl), GFP_KERNEL);
|
||||
if (!elbc_fcm_ctrl) {
|
||||
mutex_unlock(&fsl_elbc_nand_mutex);
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
elbc_fcm_ctrl->counter++;
|
||||
|
||||
spin_lock_init(&elbc_fcm_ctrl->controller.lock);
|
||||
init_waitqueue_head(&elbc_fcm_ctrl->controller.wq);
|
||||
fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl;
|
||||
} else {
|
||||
elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
|
||||
}
|
||||
mutex_unlock(&fsl_elbc_nand_mutex);
|
||||
|
||||
elbc_fcm_ctrl->chips[bank] = priv;
|
||||
priv->bank = bank;
|
||||
priv->ctrl = fsl_lbc_ctrl_dev;
|
||||
priv->dev = &pdev->dev;
|
||||
dev_set_drvdata(priv->dev, priv);
|
||||
|
||||
priv->vbase = ioremap(res.start, resource_size(&res));
|
||||
if (!priv->vbase) {
|
||||
dev_err(dev, "failed to map chip region\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
priv->mtd.name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start);
|
||||
if (!priv->mtd.name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = fsl_elbc_chip_init(priv);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = nand_scan_ident(&priv->mtd, 1, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = fsl_elbc_chip_init_tail(&priv->mtd);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = nand_scan_tail(&priv->mtd);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* First look for RedBoot table or partitions on the command
|
||||
* line, these take precedence over device tree information */
|
||||
mtd_device_parse_register(&priv->mtd, part_probe_types, &ppdata,
|
||||
NULL, 0);
|
||||
|
||||
printk(KERN_INFO "eLBC NAND device at 0x%llx, bank %d\n",
|
||||
(unsigned long long)res.start, priv->bank);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
fsl_elbc_chip_remove(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_elbc_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
|
||||
struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
fsl_elbc_chip_remove(priv);
|
||||
|
||||
mutex_lock(&fsl_elbc_nand_mutex);
|
||||
elbc_fcm_ctrl->counter--;
|
||||
if (!elbc_fcm_ctrl->counter) {
|
||||
fsl_lbc_ctrl_dev->nand = NULL;
|
||||
kfree(elbc_fcm_ctrl);
|
||||
}
|
||||
mutex_unlock(&fsl_elbc_nand_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_elbc_nand_match[] = {
|
||||
{ .compatible = "fsl,elbc-fcm-nand", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver fsl_elbc_nand_driver = {
|
||||
.driver = {
|
||||
.name = "fsl,elbc-fcm-nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = fsl_elbc_nand_match,
|
||||
},
|
||||
.probe = fsl_elbc_nand_probe,
|
||||
.remove = fsl_elbc_nand_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(fsl_elbc_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Freescale");
|
||||
MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller MTD NAND driver");
|
||||
1181
drivers/mtd/nand/fsl_ifc_nand.c
Normal file
1181
drivers/mtd/nand/fsl_ifc_nand.c
Normal file
File diff suppressed because it is too large
Load diff
362
drivers/mtd/nand/fsl_upm.c
Normal file
362
drivers/mtd/nand/fsl_upm.c
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
* Freescale UPM NAND driver.
|
||||
*
|
||||
* Copyright © 2007-2008 MontaVista Software, Inc.
|
||||
*
|
||||
* Author: Anton Vorontsov <avorontsov@ru.mvista.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/fsl_lbc.h>
|
||||
|
||||
#define FSL_UPM_WAIT_RUN_PATTERN 0x1
|
||||
#define FSL_UPM_WAIT_WRITE_BYTE 0x2
|
||||
#define FSL_UPM_WAIT_WRITE_BUFFER 0x4
|
||||
|
||||
struct fsl_upm_nand {
|
||||
struct device *dev;
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
int last_ctrl;
|
||||
struct mtd_partition *parts;
|
||||
struct fsl_upm upm;
|
||||
uint8_t upm_addr_offset;
|
||||
uint8_t upm_cmd_offset;
|
||||
void __iomem *io_base;
|
||||
int rnb_gpio[NAND_MAX_CHIPS];
|
||||
uint32_t mchip_offsets[NAND_MAX_CHIPS];
|
||||
uint32_t mchip_count;
|
||||
uint32_t mchip_number;
|
||||
int chip_delay;
|
||||
uint32_t wait_flags;
|
||||
};
|
||||
|
||||
static inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo)
|
||||
{
|
||||
return container_of(mtdinfo, struct fsl_upm_nand, mtd);
|
||||
}
|
||||
|
||||
static int fun_chip_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
|
||||
if (gpio_get_value(fun->rnb_gpio[fun->mchip_number]))
|
||||
return 1;
|
||||
|
||||
dev_vdbg(fun->dev, "busy\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fun_wait_rnb(struct fsl_upm_nand *fun)
|
||||
{
|
||||
if (fun->rnb_gpio[fun->mchip_number] >= 0) {
|
||||
int cnt = 1000000;
|
||||
|
||||
while (--cnt && !fun_chip_ready(&fun->mtd))
|
||||
cpu_relax();
|
||||
if (!cnt)
|
||||
dev_err(fun->dev, "tired waiting for RNB\n");
|
||||
} else {
|
||||
ndelay(100);
|
||||
}
|
||||
}
|
||||
|
||||
static void fun_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
u32 mar;
|
||||
|
||||
if (!(ctrl & fun->last_ctrl)) {
|
||||
fsl_upm_end_pattern(&fun->upm);
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
fun->last_ctrl = ctrl & (NAND_ALE | NAND_CLE);
|
||||
}
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if (ctrl & NAND_ALE)
|
||||
fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset);
|
||||
else if (ctrl & NAND_CLE)
|
||||
fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset);
|
||||
}
|
||||
|
||||
mar = (cmd << (32 - fun->upm.width)) |
|
||||
fun->mchip_offsets[fun->mchip_number];
|
||||
fsl_upm_run_pattern(&fun->upm, chip->IO_ADDR_R, mar);
|
||||
|
||||
if (fun->wait_flags & FSL_UPM_WAIT_RUN_PATTERN)
|
||||
fun_wait_rnb(fun);
|
||||
}
|
||||
|
||||
static void fun_select_chip(struct mtd_info *mtd, int mchip_nr)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
|
||||
if (mchip_nr == -1) {
|
||||
chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);
|
||||
} else if (mchip_nr >= 0 && mchip_nr < NAND_MAX_CHIPS) {
|
||||
fun->mchip_number = mchip_nr;
|
||||
chip->IO_ADDR_R = fun->io_base + fun->mchip_offsets[mchip_nr];
|
||||
chip->IO_ADDR_W = chip->IO_ADDR_R;
|
||||
} else {
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t fun_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
|
||||
return in_8(fun->chip.IO_ADDR_R);
|
||||
}
|
||||
|
||||
static void fun_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = in_8(fun->chip.IO_ADDR_R);
|
||||
}
|
||||
|
||||
static void fun_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
out_8(fun->chip.IO_ADDR_W, buf[i]);
|
||||
if (fun->wait_flags & FSL_UPM_WAIT_WRITE_BYTE)
|
||||
fun_wait_rnb(fun);
|
||||
}
|
||||
if (fun->wait_flags & FSL_UPM_WAIT_WRITE_BUFFER)
|
||||
fun_wait_rnb(fun);
|
||||
}
|
||||
|
||||
static int fun_chip_init(struct fsl_upm_nand *fun,
|
||||
const struct device_node *upm_np,
|
||||
const struct resource *io_res)
|
||||
{
|
||||
int ret;
|
||||
struct device_node *flash_np;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
|
||||
fun->chip.IO_ADDR_R = fun->io_base;
|
||||
fun->chip.IO_ADDR_W = fun->io_base;
|
||||
fun->chip.cmd_ctrl = fun_cmd_ctrl;
|
||||
fun->chip.chip_delay = fun->chip_delay;
|
||||
fun->chip.read_byte = fun_read_byte;
|
||||
fun->chip.read_buf = fun_read_buf;
|
||||
fun->chip.write_buf = fun_write_buf;
|
||||
fun->chip.ecc.mode = NAND_ECC_SOFT;
|
||||
if (fun->mchip_count > 1)
|
||||
fun->chip.select_chip = fun_select_chip;
|
||||
|
||||
if (fun->rnb_gpio[0] >= 0)
|
||||
fun->chip.dev_ready = fun_chip_ready;
|
||||
|
||||
fun->mtd.priv = &fun->chip;
|
||||
fun->mtd.owner = THIS_MODULE;
|
||||
|
||||
flash_np = of_get_next_child(upm_np, NULL);
|
||||
if (!flash_np)
|
||||
return -ENODEV;
|
||||
|
||||
fun->mtd.name = kasprintf(GFP_KERNEL, "0x%llx.%s", (u64)io_res->start,
|
||||
flash_np->name);
|
||||
if (!fun->mtd.name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = nand_scan(&fun->mtd, fun->mchip_count);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ppdata.of_node = flash_np;
|
||||
ret = mtd_device_parse_register(&fun->mtd, NULL, &ppdata, NULL, 0);
|
||||
err:
|
||||
of_node_put(flash_np);
|
||||
if (ret)
|
||||
kfree(fun->mtd.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fun_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct fsl_upm_nand *fun;
|
||||
struct resource io_res;
|
||||
const __be32 *prop;
|
||||
int rnb_gpio;
|
||||
int ret;
|
||||
int size;
|
||||
int i;
|
||||
|
||||
fun = kzalloc(sizeof(*fun), GFP_KERNEL);
|
||||
if (!fun)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_address_to_resource(ofdev->dev.of_node, 0, &io_res);
|
||||
if (ret) {
|
||||
dev_err(&ofdev->dev, "can't get IO base\n");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
ret = fsl_upm_find(io_res.start, &fun->upm);
|
||||
if (ret) {
|
||||
dev_err(&ofdev->dev, "can't find UPM\n");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
prop = of_get_property(ofdev->dev.of_node, "fsl,upm-addr-offset",
|
||||
&size);
|
||||
if (!prop || size != sizeof(uint32_t)) {
|
||||
dev_err(&ofdev->dev, "can't get UPM address offset\n");
|
||||
ret = -EINVAL;
|
||||
goto err1;
|
||||
}
|
||||
fun->upm_addr_offset = *prop;
|
||||
|
||||
prop = of_get_property(ofdev->dev.of_node, "fsl,upm-cmd-offset", &size);
|
||||
if (!prop || size != sizeof(uint32_t)) {
|
||||
dev_err(&ofdev->dev, "can't get UPM command offset\n");
|
||||
ret = -EINVAL;
|
||||
goto err1;
|
||||
}
|
||||
fun->upm_cmd_offset = *prop;
|
||||
|
||||
prop = of_get_property(ofdev->dev.of_node,
|
||||
"fsl,upm-addr-line-cs-offsets", &size);
|
||||
if (prop && (size / sizeof(uint32_t)) > 0) {
|
||||
fun->mchip_count = size / sizeof(uint32_t);
|
||||
if (fun->mchip_count >= NAND_MAX_CHIPS) {
|
||||
dev_err(&ofdev->dev, "too much multiple chips\n");
|
||||
goto err1;
|
||||
}
|
||||
for (i = 0; i < fun->mchip_count; i++)
|
||||
fun->mchip_offsets[i] = be32_to_cpu(prop[i]);
|
||||
} else {
|
||||
fun->mchip_count = 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < fun->mchip_count; i++) {
|
||||
fun->rnb_gpio[i] = -1;
|
||||
rnb_gpio = of_get_gpio(ofdev->dev.of_node, i);
|
||||
if (rnb_gpio >= 0) {
|
||||
ret = gpio_request(rnb_gpio, dev_name(&ofdev->dev));
|
||||
if (ret) {
|
||||
dev_err(&ofdev->dev,
|
||||
"can't request RNB gpio #%d\n", i);
|
||||
goto err2;
|
||||
}
|
||||
gpio_direction_input(rnb_gpio);
|
||||
fun->rnb_gpio[i] = rnb_gpio;
|
||||
} else if (rnb_gpio == -EINVAL) {
|
||||
dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n", i);
|
||||
goto err2;
|
||||
}
|
||||
}
|
||||
|
||||
prop = of_get_property(ofdev->dev.of_node, "chip-delay", NULL);
|
||||
if (prop)
|
||||
fun->chip_delay = be32_to_cpup(prop);
|
||||
else
|
||||
fun->chip_delay = 50;
|
||||
|
||||
prop = of_get_property(ofdev->dev.of_node, "fsl,upm-wait-flags", &size);
|
||||
if (prop && size == sizeof(uint32_t))
|
||||
fun->wait_flags = be32_to_cpup(prop);
|
||||
else
|
||||
fun->wait_flags = FSL_UPM_WAIT_RUN_PATTERN |
|
||||
FSL_UPM_WAIT_WRITE_BYTE;
|
||||
|
||||
fun->io_base = devm_ioremap_nocache(&ofdev->dev, io_res.start,
|
||||
resource_size(&io_res));
|
||||
if (!fun->io_base) {
|
||||
ret = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
fun->dev = &ofdev->dev;
|
||||
fun->last_ctrl = NAND_CLE;
|
||||
|
||||
ret = fun_chip_init(fun, ofdev->dev.of_node, &io_res);
|
||||
if (ret)
|
||||
goto err2;
|
||||
|
||||
dev_set_drvdata(&ofdev->dev, fun);
|
||||
|
||||
return 0;
|
||||
err2:
|
||||
for (i = 0; i < fun->mchip_count; i++) {
|
||||
if (fun->rnb_gpio[i] < 0)
|
||||
break;
|
||||
gpio_free(fun->rnb_gpio[i]);
|
||||
}
|
||||
err1:
|
||||
kfree(fun);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fun_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev);
|
||||
int i;
|
||||
|
||||
nand_release(&fun->mtd);
|
||||
kfree(fun->mtd.name);
|
||||
|
||||
for (i = 0; i < fun->mchip_count; i++) {
|
||||
if (fun->rnb_gpio[i] < 0)
|
||||
break;
|
||||
gpio_free(fun->rnb_gpio[i]);
|
||||
}
|
||||
|
||||
kfree(fun);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_fun_match[] = {
|
||||
{ .compatible = "fsl,upm-nand" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_fun_match);
|
||||
|
||||
static struct platform_driver of_fun_driver = {
|
||||
.driver = {
|
||||
.name = "fsl,upm-nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_fun_match,
|
||||
},
|
||||
.probe = fun_probe,
|
||||
.remove = fun_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(of_fun_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
|
||||
MODULE_DESCRIPTION("Driver for NAND chips working through Freescale "
|
||||
"LocalBus User-Programmable Machine");
|
||||
1238
drivers/mtd/nand/fsmc_nand.c
Normal file
1238
drivers/mtd/nand/fsmc_nand.c
Normal file
File diff suppressed because it is too large
Load diff
320
drivers/mtd/nand/gpio.c
Normal file
320
drivers/mtd/nand/gpio.c
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* drivers/mtd/nand/gpio.c
|
||||
*
|
||||
* Updated, and converted to generic GPIO based driver by Russell King.
|
||||
*
|
||||
* Written by Ben Dooks <ben@simtec.co.uk>
|
||||
* Based on 2.4 version by Mark Whittaker
|
||||
*
|
||||
* © 2004 Simtec Electronics
|
||||
*
|
||||
* Device driver for NAND connected via GPIO
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/nand-gpio.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
struct gpiomtd {
|
||||
void __iomem *io_sync;
|
||||
struct mtd_info mtd_info;
|
||||
struct nand_chip nand_chip;
|
||||
struct gpio_nand_platdata plat;
|
||||
};
|
||||
|
||||
#define gpio_nand_getpriv(x) container_of(x, struct gpiomtd, mtd_info)
|
||||
|
||||
|
||||
#ifdef CONFIG_ARM
|
||||
/* gpio_nand_dosync()
|
||||
*
|
||||
* Make sure the GPIO state changes occur in-order with writes to NAND
|
||||
* memory region.
|
||||
* Needed on PXA due to bus-reordering within the SoC itself (see section on
|
||||
* I/O ordering in PXA manual (section 2.3, p35)
|
||||
*/
|
||||
static void gpio_nand_dosync(struct gpiomtd *gpiomtd)
|
||||
{
|
||||
unsigned long tmp;
|
||||
|
||||
if (gpiomtd->io_sync) {
|
||||
/*
|
||||
* Linux memory barriers don't cater for what's required here.
|
||||
* What's required is what's here - a read from a separate
|
||||
* region with a dependency on that read.
|
||||
*/
|
||||
tmp = readl(gpiomtd->io_sync);
|
||||
asm volatile("mov %1, %0\n" : "=r" (tmp) : "r" (tmp));
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void gpio_nand_dosync(struct gpiomtd *gpiomtd) {}
|
||||
#endif
|
||||
|
||||
static void gpio_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd);
|
||||
|
||||
gpio_nand_dosync(gpiomtd);
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
gpio_set_value(gpiomtd->plat.gpio_nce, !(ctrl & NAND_NCE));
|
||||
gpio_set_value(gpiomtd->plat.gpio_cle, !!(ctrl & NAND_CLE));
|
||||
gpio_set_value(gpiomtd->plat.gpio_ale, !!(ctrl & NAND_ALE));
|
||||
gpio_nand_dosync(gpiomtd);
|
||||
}
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
writeb(cmd, gpiomtd->nand_chip.IO_ADDR_W);
|
||||
gpio_nand_dosync(gpiomtd);
|
||||
}
|
||||
|
||||
static int gpio_nand_devready(struct mtd_info *mtd)
|
||||
{
|
||||
struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd);
|
||||
|
||||
return gpio_get_value(gpiomtd->plat.gpio_rdy);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id gpio_nand_id_table[] = {
|
||||
{ .compatible = "gpio-control-nand" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, gpio_nand_id_table);
|
||||
|
||||
static int gpio_nand_get_config_of(const struct device *dev,
|
||||
struct gpio_nand_platdata *plat)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (!dev->of_node)
|
||||
return -ENODEV;
|
||||
|
||||
if (!of_property_read_u32(dev->of_node, "bank-width", &val)) {
|
||||
if (val == 2) {
|
||||
plat->options |= NAND_BUSWIDTH_16;
|
||||
} else if (val != 1) {
|
||||
dev_err(dev, "invalid bank-width %u\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
plat->gpio_rdy = of_get_gpio(dev->of_node, 0);
|
||||
plat->gpio_nce = of_get_gpio(dev->of_node, 1);
|
||||
plat->gpio_ale = of_get_gpio(dev->of_node, 2);
|
||||
plat->gpio_cle = of_get_gpio(dev->of_node, 3);
|
||||
plat->gpio_nwp = of_get_gpio(dev->of_node, 4);
|
||||
|
||||
if (!of_property_read_u32(dev->of_node, "chip-delay", &val))
|
||||
plat->chip_delay = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct resource *gpio_nand_get_io_sync_of(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *r;
|
||||
u64 addr;
|
||||
|
||||
if (of_property_read_u64(pdev->dev.of_node,
|
||||
"gpio-control-nand,io-sync-reg", &addr))
|
||||
return NULL;
|
||||
|
||||
r = devm_kzalloc(&pdev->dev, sizeof(*r), GFP_KERNEL);
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
r->start = addr;
|
||||
r->end = r->start + 0x3;
|
||||
r->flags = IORESOURCE_MEM;
|
||||
|
||||
return r;
|
||||
}
|
||||
#else /* CONFIG_OF */
|
||||
static inline int gpio_nand_get_config_of(const struct device *dev,
|
||||
struct gpio_nand_platdata *plat)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static inline struct resource *
|
||||
gpio_nand_get_io_sync_of(struct platform_device *pdev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static inline int gpio_nand_get_config(const struct device *dev,
|
||||
struct gpio_nand_platdata *plat)
|
||||
{
|
||||
int ret = gpio_nand_get_config_of(dev, plat);
|
||||
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
if (dev_get_platdata(dev)) {
|
||||
memcpy(plat, dev_get_platdata(dev), sizeof(*plat));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline struct resource *
|
||||
gpio_nand_get_io_sync(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *r = gpio_nand_get_io_sync_of(pdev);
|
||||
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
return platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
}
|
||||
|
||||
static int gpio_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpiomtd *gpiomtd = platform_get_drvdata(pdev);
|
||||
|
||||
nand_release(&gpiomtd->mtd_info);
|
||||
|
||||
if (gpio_is_valid(gpiomtd->plat.gpio_nwp))
|
||||
gpio_set_value(gpiomtd->plat.gpio_nwp, 0);
|
||||
gpio_set_value(gpiomtd->plat.gpio_nce, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpiomtd *gpiomtd;
|
||||
struct nand_chip *chip;
|
||||
struct resource *res;
|
||||
struct mtd_part_parser_data ppdata = {};
|
||||
int ret = 0;
|
||||
|
||||
if (!pdev->dev.of_node && !dev_get_platdata(&pdev->dev))
|
||||
return -EINVAL;
|
||||
|
||||
gpiomtd = devm_kzalloc(&pdev->dev, sizeof(*gpiomtd), GFP_KERNEL);
|
||||
if (!gpiomtd)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = &gpiomtd->nand_chip;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
chip->IO_ADDR_R = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(chip->IO_ADDR_R))
|
||||
return PTR_ERR(chip->IO_ADDR_R);
|
||||
|
||||
res = gpio_nand_get_io_sync(pdev);
|
||||
if (res) {
|
||||
gpiomtd->io_sync = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(gpiomtd->io_sync))
|
||||
return PTR_ERR(gpiomtd->io_sync);
|
||||
}
|
||||
|
||||
ret = gpio_nand_get_config(&pdev->dev, &gpiomtd->plat);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_nce, "NAND NCE");
|
||||
if (ret)
|
||||
return ret;
|
||||
gpio_direction_output(gpiomtd->plat.gpio_nce, 1);
|
||||
|
||||
if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) {
|
||||
ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_nwp,
|
||||
"NAND NWP");
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_ale, "NAND ALE");
|
||||
if (ret)
|
||||
return ret;
|
||||
gpio_direction_output(gpiomtd->plat.gpio_ale, 0);
|
||||
|
||||
ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_cle, "NAND CLE");
|
||||
if (ret)
|
||||
return ret;
|
||||
gpio_direction_output(gpiomtd->plat.gpio_cle, 0);
|
||||
|
||||
if (gpio_is_valid(gpiomtd->plat.gpio_rdy)) {
|
||||
ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_rdy,
|
||||
"NAND RDY");
|
||||
if (ret)
|
||||
return ret;
|
||||
gpio_direction_input(gpiomtd->plat.gpio_rdy);
|
||||
chip->dev_ready = gpio_nand_devready;
|
||||
}
|
||||
|
||||
chip->IO_ADDR_W = chip->IO_ADDR_R;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
chip->options = gpiomtd->plat.options;
|
||||
chip->chip_delay = gpiomtd->plat.chip_delay;
|
||||
chip->cmd_ctrl = gpio_nand_cmd_ctrl;
|
||||
|
||||
gpiomtd->mtd_info.priv = chip;
|
||||
gpiomtd->mtd_info.owner = THIS_MODULE;
|
||||
|
||||
platform_set_drvdata(pdev, gpiomtd);
|
||||
|
||||
if (gpio_is_valid(gpiomtd->plat.gpio_nwp))
|
||||
gpio_direction_output(gpiomtd->plat.gpio_nwp, 1);
|
||||
|
||||
if (nand_scan(&gpiomtd->mtd_info, 1)) {
|
||||
ret = -ENXIO;
|
||||
goto err_wp;
|
||||
}
|
||||
|
||||
if (gpiomtd->plat.adjust_parts)
|
||||
gpiomtd->plat.adjust_parts(&gpiomtd->plat,
|
||||
gpiomtd->mtd_info.size);
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
ret = mtd_device_parse_register(&gpiomtd->mtd_info, NULL, &ppdata,
|
||||
gpiomtd->plat.parts,
|
||||
gpiomtd->plat.num_parts);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
err_wp:
|
||||
if (gpio_is_valid(gpiomtd->plat.gpio_nwp))
|
||||
gpio_set_value(gpiomtd->plat.gpio_nwp, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver gpio_nand_driver = {
|
||||
.probe = gpio_nand_probe,
|
||||
.remove = gpio_nand_remove,
|
||||
.driver = {
|
||||
.name = "gpio-nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(gpio_nand_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(gpio_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
|
||||
MODULE_DESCRIPTION("GPIO NAND Driver");
|
||||
3
drivers/mtd/nand/gpmi-nand/Makefile
Normal file
3
drivers/mtd/nand/gpmi-nand/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi_nand.o
|
||||
gpmi_nand-objs += gpmi-nand.o
|
||||
gpmi_nand-objs += gpmi-lib.o
|
||||
128
drivers/mtd/nand/gpmi-nand/bch-regs.h
Normal file
128
drivers/mtd/nand/gpmi-nand/bch-regs.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Freescale GPMI NAND Flash Driver
|
||||
*
|
||||
* Copyright 2008-2011 Freescale Semiconductor, Inc.
|
||||
* Copyright 2008 Embedded Alley Solutions, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
#ifndef __GPMI_NAND_BCH_REGS_H
|
||||
#define __GPMI_NAND_BCH_REGS_H
|
||||
|
||||
#define HW_BCH_CTRL 0x00000000
|
||||
#define HW_BCH_CTRL_SET 0x00000004
|
||||
#define HW_BCH_CTRL_CLR 0x00000008
|
||||
#define HW_BCH_CTRL_TOG 0x0000000c
|
||||
|
||||
#define BM_BCH_CTRL_COMPLETE_IRQ_EN (1 << 8)
|
||||
#define BM_BCH_CTRL_COMPLETE_IRQ (1 << 0)
|
||||
|
||||
#define HW_BCH_STATUS0 0x00000010
|
||||
#define HW_BCH_MODE 0x00000020
|
||||
#define HW_BCH_ENCODEPTR 0x00000030
|
||||
#define HW_BCH_DATAPTR 0x00000040
|
||||
#define HW_BCH_METAPTR 0x00000050
|
||||
#define HW_BCH_LAYOUTSELECT 0x00000070
|
||||
|
||||
#define HW_BCH_FLASH0LAYOUT0 0x00000080
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT0_NBLOCKS 24
|
||||
#define BM_BCH_FLASH0LAYOUT0_NBLOCKS (0xff << BP_BCH_FLASH0LAYOUT0_NBLOCKS)
|
||||
#define BF_BCH_FLASH0LAYOUT0_NBLOCKS(v) \
|
||||
(((v) << BP_BCH_FLASH0LAYOUT0_NBLOCKS) & BM_BCH_FLASH0LAYOUT0_NBLOCKS)
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT0_META_SIZE 16
|
||||
#define BM_BCH_FLASH0LAYOUT0_META_SIZE (0xff << BP_BCH_FLASH0LAYOUT0_META_SIZE)
|
||||
#define BF_BCH_FLASH0LAYOUT0_META_SIZE(v) \
|
||||
(((v) << BP_BCH_FLASH0LAYOUT0_META_SIZE)\
|
||||
& BM_BCH_FLASH0LAYOUT0_META_SIZE)
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT0_ECC0 12
|
||||
#define BM_BCH_FLASH0LAYOUT0_ECC0 (0xf << BP_BCH_FLASH0LAYOUT0_ECC0)
|
||||
#define MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0 11
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0 (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0)
|
||||
#define BF_BCH_FLASH0LAYOUT0_ECC0(v, x) \
|
||||
(GPMI_IS_MX6(x) \
|
||||
? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0) \
|
||||
& MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0) \
|
||||
: (((v) << BP_BCH_FLASH0LAYOUT0_ECC0) \
|
||||
& BM_BCH_FLASH0LAYOUT0_ECC0) \
|
||||
)
|
||||
|
||||
#define MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14 10
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14 \
|
||||
(0x1 << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14)
|
||||
#define BF_BCH_FLASH0LAYOUT0_GF(v, x) \
|
||||
((GPMI_IS_MX6(x) && ((v) == 14)) \
|
||||
? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14) \
|
||||
& MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14) \
|
||||
: 0 \
|
||||
)
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT0_DATA0_SIZE 0
|
||||
#define BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \
|
||||
(0xfff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE)
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \
|
||||
(0x3ff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE)
|
||||
#define BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(v, x) \
|
||||
(GPMI_IS_MX6(x) \
|
||||
? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \
|
||||
: ((v) & BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \
|
||||
)
|
||||
|
||||
#define HW_BCH_FLASH0LAYOUT1 0x00000090
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT1_PAGE_SIZE 16
|
||||
#define BM_BCH_FLASH0LAYOUT1_PAGE_SIZE \
|
||||
(0xffff << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE)
|
||||
#define BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(v) \
|
||||
(((v) << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) \
|
||||
& BM_BCH_FLASH0LAYOUT1_PAGE_SIZE)
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT1_ECCN 12
|
||||
#define BM_BCH_FLASH0LAYOUT1_ECCN (0xf << BP_BCH_FLASH0LAYOUT1_ECCN)
|
||||
#define MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN 11
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN)
|
||||
#define BF_BCH_FLASH0LAYOUT1_ECCN(v, x) \
|
||||
(GPMI_IS_MX6(x) \
|
||||
? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN) \
|
||||
& MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN) \
|
||||
: (((v) << BP_BCH_FLASH0LAYOUT1_ECCN) \
|
||||
& BM_BCH_FLASH0LAYOUT1_ECCN) \
|
||||
)
|
||||
|
||||
#define MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14 10
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14 \
|
||||
(0x1 << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14)
|
||||
#define BF_BCH_FLASH0LAYOUT1_GF(v, x) \
|
||||
((GPMI_IS_MX6(x) && ((v) == 14)) \
|
||||
? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14) \
|
||||
& MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14) \
|
||||
: 0 \
|
||||
)
|
||||
|
||||
#define BP_BCH_FLASH0LAYOUT1_DATAN_SIZE 0
|
||||
#define BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \
|
||||
(0xfff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE)
|
||||
#define MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \
|
||||
(0x3ff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE)
|
||||
#define BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(v, x) \
|
||||
(GPMI_IS_MX6(x) \
|
||||
? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \
|
||||
: ((v) & BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \
|
||||
)
|
||||
|
||||
#define HW_BCH_VERSION 0x00000160
|
||||
#endif
|
||||
1355
drivers/mtd/nand/gpmi-nand/gpmi-lib.c
Normal file
1355
drivers/mtd/nand/gpmi-nand/gpmi-lib.c
Normal file
File diff suppressed because it is too large
Load diff
1857
drivers/mtd/nand/gpmi-nand/gpmi-nand.c
Normal file
1857
drivers/mtd/nand/gpmi-nand/gpmi-nand.c
Normal file
File diff suppressed because it is too large
Load diff
305
drivers/mtd/nand/gpmi-nand/gpmi-nand.h
Normal file
305
drivers/mtd/nand/gpmi-nand/gpmi-nand.h
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Freescale GPMI NAND Flash Driver
|
||||
*
|
||||
* Copyright (C) 2010-2011 Freescale Semiconductor, Inc.
|
||||
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __DRIVERS_MTD_NAND_GPMI_NAND_H
|
||||
#define __DRIVERS_MTD_NAND_GPMI_NAND_H
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#define GPMI_CLK_MAX 5 /* MX6Q needs five clocks */
|
||||
struct resources {
|
||||
void __iomem *gpmi_regs;
|
||||
void __iomem *bch_regs;
|
||||
unsigned int dma_low_channel;
|
||||
unsigned int dma_high_channel;
|
||||
struct clk *clock[GPMI_CLK_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bch_geometry - BCH geometry description.
|
||||
* @gf_len: The length of Galois Field. (e.g., 13 or 14)
|
||||
* @ecc_strength: A number that describes the strength of the ECC
|
||||
* algorithm.
|
||||
* @page_size: The size, in bytes, of a physical page, including
|
||||
* both data and OOB.
|
||||
* @metadata_size: The size, in bytes, of the metadata.
|
||||
* @ecc_chunk_size: The size, in bytes, of a single ECC chunk. Note
|
||||
* the first chunk in the page includes both data and
|
||||
* metadata, so it's a bit larger than this value.
|
||||
* @ecc_chunk_count: The number of ECC chunks in the page,
|
||||
* @payload_size: The size, in bytes, of the payload buffer.
|
||||
* @auxiliary_size: The size, in bytes, of the auxiliary buffer.
|
||||
* @auxiliary_status_offset: The offset into the auxiliary buffer at which
|
||||
* the ECC status appears.
|
||||
* @block_mark_byte_offset: The byte offset in the ECC-based page view at
|
||||
* which the underlying physical block mark appears.
|
||||
* @block_mark_bit_offset: The bit offset into the ECC-based page view at
|
||||
* which the underlying physical block mark appears.
|
||||
*/
|
||||
struct bch_geometry {
|
||||
unsigned int gf_len;
|
||||
unsigned int ecc_strength;
|
||||
unsigned int page_size;
|
||||
unsigned int metadata_size;
|
||||
unsigned int ecc_chunk_size;
|
||||
unsigned int ecc_chunk_count;
|
||||
unsigned int payload_size;
|
||||
unsigned int auxiliary_size;
|
||||
unsigned int auxiliary_status_offset;
|
||||
unsigned int block_mark_byte_offset;
|
||||
unsigned int block_mark_bit_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct boot_rom_geometry - Boot ROM geometry description.
|
||||
* @stride_size_in_pages: The size of a boot block stride, in pages.
|
||||
* @search_area_stride_exponent: The logarithm to base 2 of the size of a
|
||||
* search area in boot block strides.
|
||||
*/
|
||||
struct boot_rom_geometry {
|
||||
unsigned int stride_size_in_pages;
|
||||
unsigned int search_area_stride_exponent;
|
||||
};
|
||||
|
||||
/* DMA operations types */
|
||||
enum dma_ops_type {
|
||||
DMA_FOR_COMMAND = 1,
|
||||
DMA_FOR_READ_DATA,
|
||||
DMA_FOR_WRITE_DATA,
|
||||
DMA_FOR_READ_ECC_PAGE,
|
||||
DMA_FOR_WRITE_ECC_PAGE
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_timing - Fundamental timing attributes for NAND.
|
||||
* @data_setup_in_ns: The data setup time, in nanoseconds. Usually the
|
||||
* maximum of tDS and tWP. A negative value
|
||||
* indicates this characteristic isn't known.
|
||||
* @data_hold_in_ns: The data hold time, in nanoseconds. Usually the
|
||||
* maximum of tDH, tWH and tREH. A negative value
|
||||
* indicates this characteristic isn't known.
|
||||
* @address_setup_in_ns: The address setup time, in nanoseconds. Usually
|
||||
* the maximum of tCLS, tCS and tALS. A negative
|
||||
* value indicates this characteristic isn't known.
|
||||
* @gpmi_sample_delay_in_ns: A GPMI-specific timing parameter. A negative value
|
||||
* indicates this characteristic isn't known.
|
||||
* @tREA_in_ns: tREA, in nanoseconds, from the data sheet. A
|
||||
* negative value indicates this characteristic isn't
|
||||
* known.
|
||||
* @tRLOH_in_ns: tRLOH, in nanoseconds, from the data sheet. A
|
||||
* negative value indicates this characteristic isn't
|
||||
* known.
|
||||
* @tRHOH_in_ns: tRHOH, in nanoseconds, from the data sheet. A
|
||||
* negative value indicates this characteristic isn't
|
||||
* known.
|
||||
*/
|
||||
struct nand_timing {
|
||||
int8_t data_setup_in_ns;
|
||||
int8_t data_hold_in_ns;
|
||||
int8_t address_setup_in_ns;
|
||||
int8_t gpmi_sample_delay_in_ns;
|
||||
int8_t tREA_in_ns;
|
||||
int8_t tRLOH_in_ns;
|
||||
int8_t tRHOH_in_ns;
|
||||
};
|
||||
|
||||
enum gpmi_type {
|
||||
IS_MX23,
|
||||
IS_MX28,
|
||||
IS_MX6Q,
|
||||
IS_MX6SX
|
||||
};
|
||||
|
||||
struct gpmi_devdata {
|
||||
enum gpmi_type type;
|
||||
int bch_max_ecc_strength;
|
||||
int max_chain_delay; /* See the async EDO mode */
|
||||
};
|
||||
|
||||
struct gpmi_nand_data {
|
||||
/* flags */
|
||||
#define GPMI_ASYNC_EDO_ENABLED (1 << 0)
|
||||
#define GPMI_TIMING_INIT_OK (1 << 1)
|
||||
int flags;
|
||||
const struct gpmi_devdata *devdata;
|
||||
|
||||
/* System Interface */
|
||||
struct device *dev;
|
||||
struct platform_device *pdev;
|
||||
|
||||
/* Resources */
|
||||
struct resources resources;
|
||||
|
||||
/* Flash Hardware */
|
||||
struct nand_timing timing;
|
||||
int timing_mode;
|
||||
|
||||
/* BCH */
|
||||
struct bch_geometry bch_geometry;
|
||||
struct completion bch_done;
|
||||
|
||||
/* NAND Boot issue */
|
||||
bool swap_block_mark;
|
||||
struct boot_rom_geometry rom_geometry;
|
||||
|
||||
/* MTD / NAND */
|
||||
struct nand_chip nand;
|
||||
struct mtd_info mtd;
|
||||
|
||||
/* General-use Variables */
|
||||
int current_chip;
|
||||
unsigned int command_length;
|
||||
|
||||
/* passed from upper layer */
|
||||
uint8_t *upper_buf;
|
||||
int upper_len;
|
||||
|
||||
/* for DMA operations */
|
||||
bool direct_dma_map_ok;
|
||||
|
||||
struct scatterlist cmd_sgl;
|
||||
char *cmd_buffer;
|
||||
|
||||
struct scatterlist data_sgl;
|
||||
char *data_buffer_dma;
|
||||
|
||||
void *page_buffer_virt;
|
||||
dma_addr_t page_buffer_phys;
|
||||
unsigned int page_buffer_size;
|
||||
|
||||
void *payload_virt;
|
||||
dma_addr_t payload_phys;
|
||||
|
||||
void *auxiliary_virt;
|
||||
dma_addr_t auxiliary_phys;
|
||||
|
||||
/* DMA channels */
|
||||
#define DMA_CHANS 8
|
||||
struct dma_chan *dma_chans[DMA_CHANS];
|
||||
enum dma_ops_type last_dma_type;
|
||||
enum dma_ops_type dma_type;
|
||||
struct completion dma_done;
|
||||
|
||||
/* private */
|
||||
void *private;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct gpmi_nfc_hardware_timing - GPMI hardware timing parameters.
|
||||
* @data_setup_in_cycles: The data setup time, in cycles.
|
||||
* @data_hold_in_cycles: The data hold time, in cycles.
|
||||
* @address_setup_in_cycles: The address setup time, in cycles.
|
||||
* @device_busy_timeout: The timeout waiting for NAND Ready/Busy,
|
||||
* this value is the number of cycles multiplied
|
||||
* by 4096.
|
||||
* @use_half_periods: Indicates the clock is running slowly, so the
|
||||
* NFC DLL should use half-periods.
|
||||
* @sample_delay_factor: The sample delay factor.
|
||||
* @wrn_dly_sel: The delay on the GPMI write strobe.
|
||||
*/
|
||||
struct gpmi_nfc_hardware_timing {
|
||||
/* for HW_GPMI_TIMING0 */
|
||||
uint8_t data_setup_in_cycles;
|
||||
uint8_t data_hold_in_cycles;
|
||||
uint8_t address_setup_in_cycles;
|
||||
|
||||
/* for HW_GPMI_TIMING1 */
|
||||
uint16_t device_busy_timeout;
|
||||
#define GPMI_DEFAULT_BUSY_TIMEOUT 0x500 /* default busy timeout value.*/
|
||||
|
||||
/* for HW_GPMI_CTRL1 */
|
||||
bool use_half_periods;
|
||||
uint8_t sample_delay_factor;
|
||||
uint8_t wrn_dly_sel;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct timing_threshod - Timing threshold
|
||||
* @max_data_setup_cycles: The maximum number of data setup cycles that
|
||||
* can be expressed in the hardware.
|
||||
* @internal_data_setup_in_ns: The time, in ns, that the NFC hardware requires
|
||||
* for data read internal setup. In the Reference
|
||||
* Manual, see the chapter "High-Speed NAND
|
||||
* Timing" for more details.
|
||||
* @max_sample_delay_factor: The maximum sample delay factor that can be
|
||||
* expressed in the hardware.
|
||||
* @max_dll_clock_period_in_ns: The maximum period of the GPMI clock that the
|
||||
* sample delay DLL hardware can possibly work
|
||||
* with (the DLL is unusable with longer periods).
|
||||
* If the full-cycle period is greater than HALF
|
||||
* this value, the DLL must be configured to use
|
||||
* half-periods.
|
||||
* @max_dll_delay_in_ns: The maximum amount of delay, in ns, that the
|
||||
* DLL can implement.
|
||||
* @clock_frequency_in_hz: The clock frequency, in Hz, during the current
|
||||
* I/O transaction. If no I/O transaction is in
|
||||
* progress, this is the clock frequency during
|
||||
* the most recent I/O transaction.
|
||||
*/
|
||||
struct timing_threshod {
|
||||
const unsigned int max_chip_count;
|
||||
const unsigned int max_data_setup_cycles;
|
||||
const unsigned int internal_data_setup_in_ns;
|
||||
const unsigned int max_sample_delay_factor;
|
||||
const unsigned int max_dll_clock_period_in_ns;
|
||||
const unsigned int max_dll_delay_in_ns;
|
||||
unsigned long clock_frequency_in_hz;
|
||||
|
||||
};
|
||||
|
||||
/* Common Services */
|
||||
extern int common_nfc_set_geometry(struct gpmi_nand_data *);
|
||||
extern struct dma_chan *get_dma_chan(struct gpmi_nand_data *);
|
||||
extern void prepare_data_dma(struct gpmi_nand_data *,
|
||||
enum dma_data_direction dr);
|
||||
extern int start_dma_without_bch_irq(struct gpmi_nand_data *,
|
||||
struct dma_async_tx_descriptor *);
|
||||
extern int start_dma_with_bch_irq(struct gpmi_nand_data *,
|
||||
struct dma_async_tx_descriptor *);
|
||||
|
||||
/* GPMI-NAND helper function library */
|
||||
extern int gpmi_init(struct gpmi_nand_data *);
|
||||
extern int gpmi_extra_init(struct gpmi_nand_data *);
|
||||
extern void gpmi_clear_bch(struct gpmi_nand_data *);
|
||||
extern void gpmi_dump_info(struct gpmi_nand_data *);
|
||||
extern int bch_set_geometry(struct gpmi_nand_data *);
|
||||
extern int gpmi_is_ready(struct gpmi_nand_data *, unsigned chip);
|
||||
extern int gpmi_send_command(struct gpmi_nand_data *);
|
||||
extern void gpmi_begin(struct gpmi_nand_data *);
|
||||
extern void gpmi_end(struct gpmi_nand_data *);
|
||||
extern int gpmi_read_data(struct gpmi_nand_data *);
|
||||
extern int gpmi_send_data(struct gpmi_nand_data *);
|
||||
extern int gpmi_send_page(struct gpmi_nand_data *,
|
||||
dma_addr_t payload, dma_addr_t auxiliary);
|
||||
extern int gpmi_read_page(struct gpmi_nand_data *,
|
||||
dma_addr_t payload, dma_addr_t auxiliary);
|
||||
|
||||
/* BCH : Status Block Completion Codes */
|
||||
#define STATUS_GOOD 0x00
|
||||
#define STATUS_ERASED 0xff
|
||||
#define STATUS_UNCORRECTABLE 0xfe
|
||||
|
||||
/* Use the devdata to distinguish different Archs. */
|
||||
#define GPMI_IS_MX23(x) ((x)->devdata->type == IS_MX23)
|
||||
#define GPMI_IS_MX28(x) ((x)->devdata->type == IS_MX28)
|
||||
#define GPMI_IS_MX6Q(x) ((x)->devdata->type == IS_MX6Q)
|
||||
#define GPMI_IS_MX6SX(x) ((x)->devdata->type == IS_MX6SX)
|
||||
|
||||
#define GPMI_IS_MX6(x) (GPMI_IS_MX6Q(x) || GPMI_IS_MX6SX(x))
|
||||
#endif
|
||||
187
drivers/mtd/nand/gpmi-nand/gpmi-regs.h
Normal file
187
drivers/mtd/nand/gpmi-nand/gpmi-regs.h
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Freescale GPMI NAND Flash Driver
|
||||
*
|
||||
* Copyright 2008-2011 Freescale Semiconductor, Inc.
|
||||
* Copyright 2008 Embedded Alley Solutions, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
#ifndef __GPMI_NAND_GPMI_REGS_H
|
||||
#define __GPMI_NAND_GPMI_REGS_H
|
||||
|
||||
#define HW_GPMI_CTRL0 0x00000000
|
||||
#define HW_GPMI_CTRL0_SET 0x00000004
|
||||
#define HW_GPMI_CTRL0_CLR 0x00000008
|
||||
#define HW_GPMI_CTRL0_TOG 0x0000000c
|
||||
|
||||
#define BP_GPMI_CTRL0_COMMAND_MODE 24
|
||||
#define BM_GPMI_CTRL0_COMMAND_MODE (3 << BP_GPMI_CTRL0_COMMAND_MODE)
|
||||
#define BF_GPMI_CTRL0_COMMAND_MODE(v) \
|
||||
(((v) << BP_GPMI_CTRL0_COMMAND_MODE) & BM_GPMI_CTRL0_COMMAND_MODE)
|
||||
#define BV_GPMI_CTRL0_COMMAND_MODE__WRITE 0x0
|
||||
#define BV_GPMI_CTRL0_COMMAND_MODE__READ 0x1
|
||||
#define BV_GPMI_CTRL0_COMMAND_MODE__READ_AND_COMPARE 0x2
|
||||
#define BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY 0x3
|
||||
|
||||
#define BM_GPMI_CTRL0_WORD_LENGTH (1 << 23)
|
||||
#define BV_GPMI_CTRL0_WORD_LENGTH__16_BIT 0x0
|
||||
#define BV_GPMI_CTRL0_WORD_LENGTH__8_BIT 0x1
|
||||
|
||||
/*
|
||||
* Difference in LOCK_CS between imx23 and imx28 :
|
||||
* This bit may impact the _POWER_ consumption. So some chips
|
||||
* do not set it.
|
||||
*/
|
||||
#define MX23_BP_GPMI_CTRL0_LOCK_CS 22
|
||||
#define MX28_BP_GPMI_CTRL0_LOCK_CS 27
|
||||
#define LOCK_CS_ENABLE 0x1
|
||||
#define BF_GPMI_CTRL0_LOCK_CS(v, x) 0x0
|
||||
|
||||
/* Difference in CS between imx23 and imx28 */
|
||||
#define BP_GPMI_CTRL0_CS 20
|
||||
#define MX23_BM_GPMI_CTRL0_CS (3 << BP_GPMI_CTRL0_CS)
|
||||
#define MX28_BM_GPMI_CTRL0_CS (7 << BP_GPMI_CTRL0_CS)
|
||||
#define BF_GPMI_CTRL0_CS(v, x) (((v) << BP_GPMI_CTRL0_CS) & \
|
||||
(GPMI_IS_MX23((x)) \
|
||||
? MX23_BM_GPMI_CTRL0_CS \
|
||||
: MX28_BM_GPMI_CTRL0_CS))
|
||||
|
||||
#define BP_GPMI_CTRL0_ADDRESS 17
|
||||
#define BM_GPMI_CTRL0_ADDRESS (3 << BP_GPMI_CTRL0_ADDRESS)
|
||||
#define BF_GPMI_CTRL0_ADDRESS(v) \
|
||||
(((v) << BP_GPMI_CTRL0_ADDRESS) & BM_GPMI_CTRL0_ADDRESS)
|
||||
#define BV_GPMI_CTRL0_ADDRESS__NAND_DATA 0x0
|
||||
#define BV_GPMI_CTRL0_ADDRESS__NAND_CLE 0x1
|
||||
#define BV_GPMI_CTRL0_ADDRESS__NAND_ALE 0x2
|
||||
|
||||
#define BM_GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16)
|
||||
#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__DISABLED 0x0
|
||||
#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__ENABLED 0x1
|
||||
|
||||
#define BP_GPMI_CTRL0_XFER_COUNT 0
|
||||
#define BM_GPMI_CTRL0_XFER_COUNT (0xffff << BP_GPMI_CTRL0_XFER_COUNT)
|
||||
#define BF_GPMI_CTRL0_XFER_COUNT(v) \
|
||||
(((v) << BP_GPMI_CTRL0_XFER_COUNT) & BM_GPMI_CTRL0_XFER_COUNT)
|
||||
|
||||
#define HW_GPMI_COMPARE 0x00000010
|
||||
|
||||
#define HW_GPMI_ECCCTRL 0x00000020
|
||||
#define HW_GPMI_ECCCTRL_SET 0x00000024
|
||||
#define HW_GPMI_ECCCTRL_CLR 0x00000028
|
||||
#define HW_GPMI_ECCCTRL_TOG 0x0000002c
|
||||
|
||||
#define BP_GPMI_ECCCTRL_ECC_CMD 13
|
||||
#define BM_GPMI_ECCCTRL_ECC_CMD (3 << BP_GPMI_ECCCTRL_ECC_CMD)
|
||||
#define BF_GPMI_ECCCTRL_ECC_CMD(v) \
|
||||
(((v) << BP_GPMI_ECCCTRL_ECC_CMD) & BM_GPMI_ECCCTRL_ECC_CMD)
|
||||
#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE 0x0
|
||||
#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE 0x1
|
||||
|
||||
#define BM_GPMI_ECCCTRL_ENABLE_ECC (1 << 12)
|
||||
#define BV_GPMI_ECCCTRL_ENABLE_ECC__ENABLE 0x1
|
||||
#define BV_GPMI_ECCCTRL_ENABLE_ECC__DISABLE 0x0
|
||||
|
||||
#define BP_GPMI_ECCCTRL_BUFFER_MASK 0
|
||||
#define BM_GPMI_ECCCTRL_BUFFER_MASK (0x1ff << BP_GPMI_ECCCTRL_BUFFER_MASK)
|
||||
#define BF_GPMI_ECCCTRL_BUFFER_MASK(v) \
|
||||
(((v) << BP_GPMI_ECCCTRL_BUFFER_MASK) & BM_GPMI_ECCCTRL_BUFFER_MASK)
|
||||
#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100
|
||||
#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF
|
||||
|
||||
#define HW_GPMI_ECCCOUNT 0x00000030
|
||||
#define HW_GPMI_PAYLOAD 0x00000040
|
||||
#define HW_GPMI_AUXILIARY 0x00000050
|
||||
#define HW_GPMI_CTRL1 0x00000060
|
||||
#define HW_GPMI_CTRL1_SET 0x00000064
|
||||
#define HW_GPMI_CTRL1_CLR 0x00000068
|
||||
#define HW_GPMI_CTRL1_TOG 0x0000006c
|
||||
|
||||
#define BP_GPMI_CTRL1_DECOUPLE_CS 24
|
||||
#define BM_GPMI_CTRL1_DECOUPLE_CS (1 << BP_GPMI_CTRL1_DECOUPLE_CS)
|
||||
|
||||
#define BP_GPMI_CTRL1_WRN_DLY_SEL 22
|
||||
#define BM_GPMI_CTRL1_WRN_DLY_SEL (0x3 << BP_GPMI_CTRL1_WRN_DLY_SEL)
|
||||
#define BF_GPMI_CTRL1_WRN_DLY_SEL(v) \
|
||||
(((v) << BP_GPMI_CTRL1_WRN_DLY_SEL) & BM_GPMI_CTRL1_WRN_DLY_SEL)
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS 0x0
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_6_TO_10NS 0x1
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_7_TO_12NS 0x2
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY 0x3
|
||||
|
||||
#define BM_GPMI_CTRL1_BCH_MODE (1 << 18)
|
||||
|
||||
#define BP_GPMI_CTRL1_DLL_ENABLE 17
|
||||
#define BM_GPMI_CTRL1_DLL_ENABLE (1 << BP_GPMI_CTRL1_DLL_ENABLE)
|
||||
|
||||
#define BP_GPMI_CTRL1_HALF_PERIOD 16
|
||||
#define BM_GPMI_CTRL1_HALF_PERIOD (1 << BP_GPMI_CTRL1_HALF_PERIOD)
|
||||
|
||||
#define BP_GPMI_CTRL1_RDN_DELAY 12
|
||||
#define BM_GPMI_CTRL1_RDN_DELAY (0xf << BP_GPMI_CTRL1_RDN_DELAY)
|
||||
#define BF_GPMI_CTRL1_RDN_DELAY(v) \
|
||||
(((v) << BP_GPMI_CTRL1_RDN_DELAY) & BM_GPMI_CTRL1_RDN_DELAY)
|
||||
|
||||
#define BM_GPMI_CTRL1_DEV_RESET (1 << 3)
|
||||
#define BV_GPMI_CTRL1_DEV_RESET__ENABLED 0x0
|
||||
#define BV_GPMI_CTRL1_DEV_RESET__DISABLED 0x1
|
||||
|
||||
#define BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2)
|
||||
#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVELOW 0x0
|
||||
#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVEHIGH 0x1
|
||||
|
||||
#define BM_GPMI_CTRL1_CAMERA_MODE (1 << 1)
|
||||
#define BV_GPMI_CTRL1_GPMI_MODE__NAND 0x0
|
||||
#define BV_GPMI_CTRL1_GPMI_MODE__ATA 0x1
|
||||
|
||||
#define BM_GPMI_CTRL1_GPMI_MODE (1 << 0)
|
||||
|
||||
#define HW_GPMI_TIMING0 0x00000070
|
||||
|
||||
#define BP_GPMI_TIMING0_ADDRESS_SETUP 16
|
||||
#define BM_GPMI_TIMING0_ADDRESS_SETUP (0xff << BP_GPMI_TIMING0_ADDRESS_SETUP)
|
||||
#define BF_GPMI_TIMING0_ADDRESS_SETUP(v) \
|
||||
(((v) << BP_GPMI_TIMING0_ADDRESS_SETUP) & BM_GPMI_TIMING0_ADDRESS_SETUP)
|
||||
|
||||
#define BP_GPMI_TIMING0_DATA_HOLD 8
|
||||
#define BM_GPMI_TIMING0_DATA_HOLD (0xff << BP_GPMI_TIMING0_DATA_HOLD)
|
||||
#define BF_GPMI_TIMING0_DATA_HOLD(v) \
|
||||
(((v) << BP_GPMI_TIMING0_DATA_HOLD) & BM_GPMI_TIMING0_DATA_HOLD)
|
||||
|
||||
#define BP_GPMI_TIMING0_DATA_SETUP 0
|
||||
#define BM_GPMI_TIMING0_DATA_SETUP (0xff << BP_GPMI_TIMING0_DATA_SETUP)
|
||||
#define BF_GPMI_TIMING0_DATA_SETUP(v) \
|
||||
(((v) << BP_GPMI_TIMING0_DATA_SETUP) & BM_GPMI_TIMING0_DATA_SETUP)
|
||||
|
||||
#define HW_GPMI_TIMING1 0x00000080
|
||||
#define BP_GPMI_TIMING1_BUSY_TIMEOUT 16
|
||||
#define BM_GPMI_TIMING1_BUSY_TIMEOUT (0xffff << BP_GPMI_TIMING1_BUSY_TIMEOUT)
|
||||
#define BF_GPMI_TIMING1_BUSY_TIMEOUT(v) \
|
||||
(((v) << BP_GPMI_TIMING1_BUSY_TIMEOUT) & BM_GPMI_TIMING1_BUSY_TIMEOUT)
|
||||
|
||||
#define HW_GPMI_TIMING2 0x00000090
|
||||
#define HW_GPMI_DATA 0x000000a0
|
||||
|
||||
/* MX28 uses this to detect READY. */
|
||||
#define HW_GPMI_STAT 0x000000b0
|
||||
#define MX28_BP_GPMI_STAT_READY_BUSY 24
|
||||
#define MX28_BM_GPMI_STAT_READY_BUSY (0xff << MX28_BP_GPMI_STAT_READY_BUSY)
|
||||
#define MX28_BF_GPMI_STAT_READY_BUSY(v) \
|
||||
(((v) << MX28_BP_GPMI_STAT_READY_BUSY) & MX28_BM_GPMI_STAT_READY_BUSY)
|
||||
|
||||
/* MX23 uses this to detect READY. */
|
||||
#define HW_GPMI_DEBUG 0x000000c0
|
||||
#define MX23_BP_GPMI_DEBUG_READY0 28
|
||||
#define MX23_BM_GPMI_DEBUG_READY0 (1 << MX23_BP_GPMI_DEBUG_READY0)
|
||||
#endif
|
||||
589
drivers/mtd/nand/jz4740_nand.c
Normal file
589
drivers/mtd/nand/jz4740_nand.c
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
|
||||
* JZ4740 SoC NAND controller driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include <asm/mach-jz4740/jz4740_nand.h>
|
||||
|
||||
#define JZ_REG_NAND_CTRL 0x50
|
||||
#define JZ_REG_NAND_ECC_CTRL 0x100
|
||||
#define JZ_REG_NAND_DATA 0x104
|
||||
#define JZ_REG_NAND_PAR0 0x108
|
||||
#define JZ_REG_NAND_PAR1 0x10C
|
||||
#define JZ_REG_NAND_PAR2 0x110
|
||||
#define JZ_REG_NAND_IRQ_STAT 0x114
|
||||
#define JZ_REG_NAND_IRQ_CTRL 0x118
|
||||
#define JZ_REG_NAND_ERR(x) (0x11C + ((x) << 2))
|
||||
|
||||
#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
|
||||
#define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
|
||||
#define JZ_NAND_ECC_CTRL_RS BIT(2)
|
||||
#define JZ_NAND_ECC_CTRL_RESET BIT(1)
|
||||
#define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
|
||||
|
||||
#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
|
||||
#define JZ_NAND_STATUS_PAD_FINISH BIT(4)
|
||||
#define JZ_NAND_STATUS_DEC_FINISH BIT(3)
|
||||
#define JZ_NAND_STATUS_ENC_FINISH BIT(2)
|
||||
#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
|
||||
#define JZ_NAND_STATUS_ERROR BIT(0)
|
||||
|
||||
#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1)
|
||||
#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1)
|
||||
#define JZ_NAND_CTRL_ASSERT_CHIP_MASK 0xaa
|
||||
|
||||
#define JZ_NAND_MEM_CMD_OFFSET 0x08000
|
||||
#define JZ_NAND_MEM_ADDR_OFFSET 0x10000
|
||||
|
||||
struct jz_nand {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
void __iomem *base;
|
||||
struct resource *mem;
|
||||
|
||||
unsigned char banks[JZ_NAND_NUM_BANKS];
|
||||
void __iomem *bank_base[JZ_NAND_NUM_BANKS];
|
||||
struct resource *bank_mem[JZ_NAND_NUM_BANKS];
|
||||
|
||||
int selected_bank;
|
||||
|
||||
struct jz_nand_platform_data *pdata;
|
||||
bool is_reading;
|
||||
};
|
||||
|
||||
static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd)
|
||||
{
|
||||
return container_of(mtd, struct jz_nand, mtd);
|
||||
}
|
||||
|
||||
static void jz_nand_select_chip(struct mtd_info *mtd, int chipnr)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
uint32_t ctrl;
|
||||
int banknr;
|
||||
|
||||
ctrl = readl(nand->base + JZ_REG_NAND_CTRL);
|
||||
ctrl &= ~JZ_NAND_CTRL_ASSERT_CHIP_MASK;
|
||||
|
||||
if (chipnr == -1) {
|
||||
banknr = -1;
|
||||
} else {
|
||||
banknr = nand->banks[chipnr] - 1;
|
||||
chip->IO_ADDR_R = nand->bank_base[banknr];
|
||||
chip->IO_ADDR_W = nand->bank_base[banknr];
|
||||
}
|
||||
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);
|
||||
|
||||
nand->selected_bank = banknr;
|
||||
}
|
||||
|
||||
static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
uint32_t reg;
|
||||
void __iomem *bank_base = nand->bank_base[nand->selected_bank];
|
||||
|
||||
BUG_ON(nand->selected_bank < 0);
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
|
||||
if (ctrl & NAND_ALE)
|
||||
bank_base += JZ_NAND_MEM_ADDR_OFFSET;
|
||||
else if (ctrl & NAND_CLE)
|
||||
bank_base += JZ_NAND_MEM_CMD_OFFSET;
|
||||
chip->IO_ADDR_W = bank_base;
|
||||
|
||||
reg = readl(nand->base + JZ_REG_NAND_CTRL);
|
||||
if (ctrl & NAND_NCE)
|
||||
reg |= JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank);
|
||||
else
|
||||
reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank);
|
||||
writel(reg, nand->base + JZ_REG_NAND_CTRL);
|
||||
}
|
||||
if (dat != NAND_CMD_NONE)
|
||||
writeb(dat, chip->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static int jz_nand_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
return gpio_get_value_cansleep(nand->pdata->busy_gpio);
|
||||
}
|
||||
|
||||
static void jz_nand_hwctl(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
uint32_t reg;
|
||||
|
||||
writel(0, nand->base + JZ_REG_NAND_IRQ_STAT);
|
||||
reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
|
||||
reg |= JZ_NAND_ECC_CTRL_RESET;
|
||||
reg |= JZ_NAND_ECC_CTRL_ENABLE;
|
||||
reg |= JZ_NAND_ECC_CTRL_RS;
|
||||
|
||||
switch (mode) {
|
||||
case NAND_ECC_READ:
|
||||
reg &= ~JZ_NAND_ECC_CTRL_ENCODING;
|
||||
nand->is_reading = true;
|
||||
break;
|
||||
case NAND_ECC_WRITE:
|
||||
reg |= JZ_NAND_ECC_CTRL_ENCODING;
|
||||
nand->is_reading = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
}
|
||||
|
||||
static int jz_nand_calculate_ecc_rs(struct mtd_info *mtd, const uint8_t *dat,
|
||||
uint8_t *ecc_code)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
uint32_t reg, status;
|
||||
int i;
|
||||
unsigned int timeout = 1000;
|
||||
static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4,
|
||||
0x8b, 0xff, 0xb7, 0x6f};
|
||||
|
||||
if (nand->is_reading)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
|
||||
} while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout);
|
||||
|
||||
if (timeout == 0)
|
||||
return -1;
|
||||
|
||||
reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
|
||||
writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
|
||||
for (i = 0; i < 9; ++i)
|
||||
ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i);
|
||||
|
||||
/* If the written data is completly 0xff, we also want to write 0xff as
|
||||
* ecc, otherwise we will get in trouble when doing subpage writes. */
|
||||
if (memcmp(ecc_code, empty_block_ecc, 9) == 0)
|
||||
memset(ecc_code, 0xff, 9);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jz_nand_correct_data(uint8_t *dat, int index, int mask)
|
||||
{
|
||||
int offset = index & 0x7;
|
||||
uint16_t data;
|
||||
|
||||
index += (index >> 3);
|
||||
|
||||
data = dat[index];
|
||||
data |= dat[index+1] << 8;
|
||||
|
||||
mask ^= (data >> offset) & 0x1ff;
|
||||
data &= ~(0x1ff << offset);
|
||||
data |= (mask << offset);
|
||||
|
||||
dat[index] = data & 0xff;
|
||||
dat[index+1] = (data >> 8) & 0xff;
|
||||
}
|
||||
|
||||
static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat,
|
||||
uint8_t *read_ecc, uint8_t *calc_ecc)
|
||||
{
|
||||
struct jz_nand *nand = mtd_to_jz_nand(mtd);
|
||||
int i, error_count, index;
|
||||
uint32_t reg, status, error;
|
||||
uint32_t t;
|
||||
unsigned int timeout = 1000;
|
||||
|
||||
t = read_ecc[0];
|
||||
|
||||
if (t == 0xff) {
|
||||
for (i = 1; i < 9; ++i)
|
||||
t &= read_ecc[i];
|
||||
|
||||
t &= dat[0];
|
||||
t &= dat[nand->chip.ecc.size / 2];
|
||||
t &= dat[nand->chip.ecc.size - 1];
|
||||
|
||||
if (t == 0xff) {
|
||||
for (i = 1; i < nand->chip.ecc.size - 1; ++i)
|
||||
t &= dat[i];
|
||||
if (t == 0xff)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 9; ++i)
|
||||
writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i);
|
||||
|
||||
reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
reg |= JZ_NAND_ECC_CTRL_PAR_READY;
|
||||
writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
|
||||
do {
|
||||
status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
|
||||
} while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout);
|
||||
|
||||
if (timeout == 0)
|
||||
return -1;
|
||||
|
||||
reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
|
||||
writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
|
||||
|
||||
if (status & JZ_NAND_STATUS_ERROR) {
|
||||
if (status & JZ_NAND_STATUS_UNCOR_ERROR)
|
||||
return -1;
|
||||
|
||||
error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29;
|
||||
|
||||
for (i = 0; i < error_count; ++i) {
|
||||
error = readl(nand->base + JZ_REG_NAND_ERR(i));
|
||||
index = ((error >> 16) & 0x1ff) - 1;
|
||||
if (index >= 0 && index < 512)
|
||||
jz_nand_correct_data(dat, index, error & 0x1ff);
|
||||
}
|
||||
|
||||
return error_count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jz_nand_ioremap_resource(struct platform_device *pdev,
|
||||
const char *name, struct resource **res, void *__iomem *base)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
||||
if (!*res) {
|
||||
dev_err(&pdev->dev, "Failed to get platform %s memory\n", name);
|
||||
ret = -ENXIO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*res = request_mem_region((*res)->start, resource_size(*res),
|
||||
pdev->name);
|
||||
if (!*res) {
|
||||
dev_err(&pdev->dev, "Failed to request %s memory region\n", name);
|
||||
ret = -EBUSY;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*base = ioremap((*res)->start, resource_size(*res));
|
||||
if (!*base) {
|
||||
dev_err(&pdev->dev, "Failed to ioremap %s memory region\n", name);
|
||||
ret = -EBUSY;
|
||||
goto err_release_mem;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_release_mem:
|
||||
release_mem_region((*res)->start, resource_size(*res));
|
||||
err:
|
||||
*res = NULL;
|
||||
*base = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void jz_nand_iounmap_resource(struct resource *res,
|
||||
void __iomem *base)
|
||||
{
|
||||
iounmap(base);
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
}
|
||||
|
||||
static int jz_nand_detect_bank(struct platform_device *pdev,
|
||||
struct jz_nand *nand, unsigned char bank,
|
||||
size_t chipnr, uint8_t *nand_maf_id,
|
||||
uint8_t *nand_dev_id)
|
||||
{
|
||||
int ret;
|
||||
int gpio;
|
||||
char gpio_name[9];
|
||||
char res_name[6];
|
||||
uint32_t ctrl;
|
||||
struct mtd_info *mtd = &nand->mtd;
|
||||
struct nand_chip *chip = &nand->chip;
|
||||
|
||||
/* Request GPIO port. */
|
||||
gpio = JZ_GPIO_MEM_CS0 + bank - 1;
|
||||
sprintf(gpio_name, "NAND CS%d", bank);
|
||||
ret = gpio_request(gpio, gpio_name);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to request %s gpio %d: %d\n",
|
||||
gpio_name, gpio, ret);
|
||||
goto notfound_gpio;
|
||||
}
|
||||
|
||||
/* Request I/O resource. */
|
||||
sprintf(res_name, "bank%d", bank);
|
||||
ret = jz_nand_ioremap_resource(pdev, res_name,
|
||||
&nand->bank_mem[bank - 1],
|
||||
&nand->bank_base[bank - 1]);
|
||||
if (ret)
|
||||
goto notfound_resource;
|
||||
|
||||
/* Enable chip in bank. */
|
||||
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_MEM_CS0);
|
||||
ctrl = readl(nand->base + JZ_REG_NAND_CTRL);
|
||||
ctrl |= JZ_NAND_CTRL_ENABLE_CHIP(bank - 1);
|
||||
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);
|
||||
|
||||
if (chipnr == 0) {
|
||||
/* Detect first chip. */
|
||||
ret = nand_scan_ident(mtd, 1, NULL);
|
||||
if (ret)
|
||||
goto notfound_id;
|
||||
|
||||
/* Retrieve the IDs from the first chip. */
|
||||
chip->select_chip(mtd, 0);
|
||||
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
|
||||
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
|
||||
*nand_maf_id = chip->read_byte(mtd);
|
||||
*nand_dev_id = chip->read_byte(mtd);
|
||||
} else {
|
||||
/* Detect additional chip. */
|
||||
chip->select_chip(mtd, chipnr);
|
||||
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
|
||||
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
|
||||
if (*nand_maf_id != chip->read_byte(mtd)
|
||||
|| *nand_dev_id != chip->read_byte(mtd)) {
|
||||
ret = -ENODEV;
|
||||
goto notfound_id;
|
||||
}
|
||||
|
||||
/* Update size of the MTD. */
|
||||
chip->numchips++;
|
||||
mtd->size += chip->chipsize;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Found chip %i on bank %i\n", chipnr, bank);
|
||||
return 0;
|
||||
|
||||
notfound_id:
|
||||
dev_info(&pdev->dev, "No chip found on bank %i\n", bank);
|
||||
ctrl &= ~(JZ_NAND_CTRL_ENABLE_CHIP(bank - 1));
|
||||
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);
|
||||
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE);
|
||||
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
|
||||
nand->bank_base[bank - 1]);
|
||||
notfound_resource:
|
||||
gpio_free(gpio);
|
||||
notfound_gpio:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int jz_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct jz_nand *nand;
|
||||
struct nand_chip *chip;
|
||||
struct mtd_info *mtd;
|
||||
struct jz_nand_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
size_t chipnr, bank_idx;
|
||||
uint8_t nand_maf_id = 0, nand_dev_id = 0;
|
||||
|
||||
nand = kzalloc(sizeof(*nand), GFP_KERNEL);
|
||||
if (!nand)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = jz_nand_ioremap_resource(pdev, "mmio", &nand->mem, &nand->base);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
if (pdata && gpio_is_valid(pdata->busy_gpio)) {
|
||||
ret = gpio_request(pdata->busy_gpio, "NAND busy pin");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to request busy gpio %d: %d\n",
|
||||
pdata->busy_gpio, ret);
|
||||
goto err_iounmap_mmio;
|
||||
}
|
||||
}
|
||||
|
||||
mtd = &nand->mtd;
|
||||
chip = &nand->chip;
|
||||
mtd->priv = chip;
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->name = "jz4740-nand";
|
||||
|
||||
chip->ecc.hwctl = jz_nand_hwctl;
|
||||
chip->ecc.calculate = jz_nand_calculate_ecc_rs;
|
||||
chip->ecc.correct = jz_nand_correct_ecc_rs;
|
||||
chip->ecc.mode = NAND_ECC_HW_OOB_FIRST;
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.bytes = 9;
|
||||
chip->ecc.strength = 4;
|
||||
|
||||
if (pdata)
|
||||
chip->ecc.layout = pdata->ecc_layout;
|
||||
|
||||
chip->chip_delay = 50;
|
||||
chip->cmd_ctrl = jz_nand_cmd_ctrl;
|
||||
chip->select_chip = jz_nand_select_chip;
|
||||
|
||||
if (pdata && gpio_is_valid(pdata->busy_gpio))
|
||||
chip->dev_ready = jz_nand_dev_ready;
|
||||
|
||||
nand->pdata = pdata;
|
||||
platform_set_drvdata(pdev, nand);
|
||||
|
||||
/* We are going to autodetect NAND chips in the banks specified in the
|
||||
* platform data. Although nand_scan_ident() can detect multiple chips,
|
||||
* it requires those chips to be numbered consecuitively, which is not
|
||||
* always the case for external memory banks. And a fixed chip-to-bank
|
||||
* mapping is not practical either, since for example Dingoo units
|
||||
* produced at different times have NAND chips in different banks.
|
||||
*/
|
||||
chipnr = 0;
|
||||
for (bank_idx = 0; bank_idx < JZ_NAND_NUM_BANKS; bank_idx++) {
|
||||
unsigned char bank;
|
||||
|
||||
/* If there is no platform data, look for NAND in bank 1,
|
||||
* which is the most likely bank since it is the only one
|
||||
* that can be booted from.
|
||||
*/
|
||||
bank = pdata ? pdata->banks[bank_idx] : bank_idx ^ 1;
|
||||
if (bank == 0)
|
||||
break;
|
||||
if (bank > JZ_NAND_NUM_BANKS) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Skipping non-existing bank: %d\n", bank);
|
||||
continue;
|
||||
}
|
||||
/* The detection routine will directly or indirectly call
|
||||
* jz_nand_select_chip(), so nand->banks has to contain the
|
||||
* bank we're checking.
|
||||
*/
|
||||
nand->banks[chipnr] = bank;
|
||||
if (jz_nand_detect_bank(pdev, nand, bank, chipnr,
|
||||
&nand_maf_id, &nand_dev_id) == 0)
|
||||
chipnr++;
|
||||
else
|
||||
nand->banks[chipnr] = 0;
|
||||
}
|
||||
if (chipnr == 0) {
|
||||
dev_err(&pdev->dev, "No NAND chips found\n");
|
||||
goto err_gpio_busy;
|
||||
}
|
||||
|
||||
if (pdata && pdata->ident_callback) {
|
||||
pdata->ident_callback(pdev, chip, &pdata->partitions,
|
||||
&pdata->num_partitions);
|
||||
}
|
||||
|
||||
ret = nand_scan_tail(mtd);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to scan NAND\n");
|
||||
goto err_unclaim_banks;
|
||||
}
|
||||
|
||||
ret = mtd_device_parse_register(mtd, NULL, NULL,
|
||||
pdata ? pdata->partitions : NULL,
|
||||
pdata ? pdata->num_partitions : 0);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to add mtd device\n");
|
||||
goto err_nand_release;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_nand_release:
|
||||
nand_release(mtd);
|
||||
err_unclaim_banks:
|
||||
while (chipnr--) {
|
||||
unsigned char bank = nand->banks[chipnr];
|
||||
gpio_free(JZ_GPIO_MEM_CS0 + bank - 1);
|
||||
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
|
||||
nand->bank_base[bank - 1]);
|
||||
}
|
||||
writel(0, nand->base + JZ_REG_NAND_CTRL);
|
||||
err_gpio_busy:
|
||||
if (pdata && gpio_is_valid(pdata->busy_gpio))
|
||||
gpio_free(pdata->busy_gpio);
|
||||
err_iounmap_mmio:
|
||||
jz_nand_iounmap_resource(nand->mem, nand->base);
|
||||
err_free:
|
||||
kfree(nand);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int jz_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct jz_nand *nand = platform_get_drvdata(pdev);
|
||||
struct jz_nand_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
size_t i;
|
||||
|
||||
nand_release(&nand->mtd);
|
||||
|
||||
/* Deassert and disable all chips */
|
||||
writel(0, nand->base + JZ_REG_NAND_CTRL);
|
||||
|
||||
for (i = 0; i < JZ_NAND_NUM_BANKS; ++i) {
|
||||
unsigned char bank = nand->banks[i];
|
||||
if (bank != 0) {
|
||||
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
|
||||
nand->bank_base[bank - 1]);
|
||||
gpio_free(JZ_GPIO_MEM_CS0 + bank - 1);
|
||||
}
|
||||
}
|
||||
if (pdata && gpio_is_valid(pdata->busy_gpio))
|
||||
gpio_free(pdata->busy_gpio);
|
||||
|
||||
jz_nand_iounmap_resource(nand->mem, nand->base);
|
||||
|
||||
kfree(nand);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver jz_nand_driver = {
|
||||
.probe = jz_nand_probe,
|
||||
.remove = jz_nand_remove,
|
||||
.driver = {
|
||||
.name = "jz4740-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(jz_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
|
||||
MODULE_ALIAS("platform:jz4740-nand");
|
||||
890
drivers/mtd/nand/lpc32xx_mlc.c
Normal file
890
drivers/mtd/nand/lpc32xx_mlc.c
Normal file
|
|
@ -0,0 +1,890 @@
|
|||
/*
|
||||
* Driver for NAND MLC Controller in LPC32xx
|
||||
*
|
||||
* Author: Roland Stigge <stigge@antcom.de>
|
||||
*
|
||||
* Copyright © 2011 WORK Microwave GmbH
|
||||
* Copyright © 2011, 2012 Roland Stigge
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* NAND Flash Controller Operation:
|
||||
* - Read: Auto Decode
|
||||
* - Write: Auto Encode
|
||||
* - Tested Page Sizes: 2048, 4096
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mtd.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mtd/lpc32xx_mlc.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
|
||||
#define DRV_NAME "lpc32xx_mlc"
|
||||
|
||||
/**********************************************************************
|
||||
* MLC NAND controller register offsets
|
||||
**********************************************************************/
|
||||
|
||||
#define MLC_BUFF(x) (x + 0x00000)
|
||||
#define MLC_DATA(x) (x + 0x08000)
|
||||
#define MLC_CMD(x) (x + 0x10000)
|
||||
#define MLC_ADDR(x) (x + 0x10004)
|
||||
#define MLC_ECC_ENC_REG(x) (x + 0x10008)
|
||||
#define MLC_ECC_DEC_REG(x) (x + 0x1000C)
|
||||
#define MLC_ECC_AUTO_ENC_REG(x) (x + 0x10010)
|
||||
#define MLC_ECC_AUTO_DEC_REG(x) (x + 0x10014)
|
||||
#define MLC_RPR(x) (x + 0x10018)
|
||||
#define MLC_WPR(x) (x + 0x1001C)
|
||||
#define MLC_RUBP(x) (x + 0x10020)
|
||||
#define MLC_ROBP(x) (x + 0x10024)
|
||||
#define MLC_SW_WP_ADD_LOW(x) (x + 0x10028)
|
||||
#define MLC_SW_WP_ADD_HIG(x) (x + 0x1002C)
|
||||
#define MLC_ICR(x) (x + 0x10030)
|
||||
#define MLC_TIME_REG(x) (x + 0x10034)
|
||||
#define MLC_IRQ_MR(x) (x + 0x10038)
|
||||
#define MLC_IRQ_SR(x) (x + 0x1003C)
|
||||
#define MLC_LOCK_PR(x) (x + 0x10044)
|
||||
#define MLC_ISR(x) (x + 0x10048)
|
||||
#define MLC_CEH(x) (x + 0x1004C)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_CMD bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCCMD_RESET 0xFF
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_ICR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCICR_WPROT (1 << 3)
|
||||
#define MLCICR_LARGEBLOCK (1 << 2)
|
||||
#define MLCICR_LONGADDR (1 << 1)
|
||||
#define MLCICR_16BIT (1 << 0) /* unsupported by LPC32x0! */
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_TIME_REG bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCTIMEREG_TCEA_DELAY(n) (((n) & 0x03) << 24)
|
||||
#define MLCTIMEREG_BUSY_DELAY(n) (((n) & 0x1F) << 19)
|
||||
#define MLCTIMEREG_NAND_TA(n) (((n) & 0x07) << 16)
|
||||
#define MLCTIMEREG_RD_HIGH(n) (((n) & 0x0F) << 12)
|
||||
#define MLCTIMEREG_RD_LOW(n) (((n) & 0x0F) << 8)
|
||||
#define MLCTIMEREG_WR_HIGH(n) (((n) & 0x0F) << 4)
|
||||
#define MLCTIMEREG_WR_LOW(n) (((n) & 0x0F) << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_IRQ_MR and MLC_IRQ_SR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCIRQ_NAND_READY (1 << 5)
|
||||
#define MLCIRQ_CONTROLLER_READY (1 << 4)
|
||||
#define MLCIRQ_DECODE_FAILURE (1 << 3)
|
||||
#define MLCIRQ_DECODE_ERROR (1 << 2)
|
||||
#define MLCIRQ_ECC_READY (1 << 1)
|
||||
#define MLCIRQ_WRPROT_FAULT (1 << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_LOCK_PR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCLOCKPR_MAGIC 0xA25E
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_ISR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCISR_DECODER_FAILURE (1 << 6)
|
||||
#define MLCISR_ERRORS ((1 << 4) | (1 << 5))
|
||||
#define MLCISR_ERRORS_DETECTED (1 << 3)
|
||||
#define MLCISR_ECC_READY (1 << 2)
|
||||
#define MLCISR_CONTROLLER_READY (1 << 1)
|
||||
#define MLCISR_NAND_READY (1 << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_CEH bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCCEH_NORMAL (1 << 0)
|
||||
|
||||
struct lpc32xx_nand_cfg_mlc {
|
||||
uint32_t tcea_delay;
|
||||
uint32_t busy_delay;
|
||||
uint32_t nand_ta;
|
||||
uint32_t rd_high;
|
||||
uint32_t rd_low;
|
||||
uint32_t wr_high;
|
||||
uint32_t wr_low;
|
||||
int wp_gpio;
|
||||
struct mtd_partition *parts;
|
||||
unsigned num_parts;
|
||||
};
|
||||
|
||||
static struct nand_ecclayout lpc32xx_nand_oob = {
|
||||
.eccbytes = 40,
|
||||
.eccpos = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63 },
|
||||
.oobfree = {
|
||||
{ .offset = 0,
|
||||
.length = 6, },
|
||||
{ .offset = 16,
|
||||
.length = 6, },
|
||||
{ .offset = 32,
|
||||
.length = 6, },
|
||||
{ .offset = 48,
|
||||
.length = 6, },
|
||||
},
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr lpc32xx_nand_bbt = {
|
||||
.options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB |
|
||||
NAND_BBT_WRITE,
|
||||
.pages = { 524224, 0, 0, 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = {
|
||||
.options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB |
|
||||
NAND_BBT_WRITE,
|
||||
.pages = { 524160, 0, 0, 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
struct lpc32xx_nand_host {
|
||||
struct nand_chip nand_chip;
|
||||
struct lpc32xx_mlc_platform_data *pdata;
|
||||
struct clk *clk;
|
||||
struct mtd_info mtd;
|
||||
void __iomem *io_base;
|
||||
int irq;
|
||||
struct lpc32xx_nand_cfg_mlc *ncfg;
|
||||
struct completion comp_nand;
|
||||
struct completion comp_controller;
|
||||
uint32_t llptr;
|
||||
/*
|
||||
* Physical addresses of ECC buffer, DMA data buffers, OOB data buffer
|
||||
*/
|
||||
dma_addr_t oob_buf_phy;
|
||||
/*
|
||||
* Virtual addresses of ECC buffer, DMA data buffers, OOB data buffer
|
||||
*/
|
||||
uint8_t *oob_buf;
|
||||
/* Physical address of DMA base address */
|
||||
dma_addr_t io_base_phy;
|
||||
|
||||
struct completion comp_dma;
|
||||
struct dma_chan *dma_chan;
|
||||
struct dma_slave_config dma_slave_config;
|
||||
struct scatterlist sgl;
|
||||
uint8_t *dma_buf;
|
||||
uint8_t *dummy_buf;
|
||||
int mlcsubpages; /* number of 512bytes-subpages */
|
||||
};
|
||||
|
||||
/*
|
||||
* Activate/Deactivate DMA Operation:
|
||||
*
|
||||
* Using the PL080 DMA Controller for transferring the 512 byte subpages
|
||||
* instead of doing readl() / writel() in a loop slows it down significantly.
|
||||
* Measurements via getnstimeofday() upon 512 byte subpage reads reveal:
|
||||
*
|
||||
* - readl() of 128 x 32 bits in a loop: ~20us
|
||||
* - DMA read of 512 bytes (32 bit, 4...128 words bursts): ~60us
|
||||
* - DMA read of 512 bytes (32 bit, no bursts): ~100us
|
||||
*
|
||||
* This applies to the transfer itself. In the DMA case: only the
|
||||
* wait_for_completion() (DMA setup _not_ included).
|
||||
*
|
||||
* Note that the 512 bytes subpage transfer is done directly from/to a
|
||||
* FIFO/buffer inside the NAND controller. Most of the time (~400-800us for a
|
||||
* 2048 bytes page) is spent waiting for the NAND IRQ, anyway. (The NAND
|
||||
* controller transferring data between its internal buffer to/from the NAND
|
||||
* chip.)
|
||||
*
|
||||
* Therefore, using the PL080 DMA is disabled by default, for now.
|
||||
*
|
||||
*/
|
||||
static int use_dma;
|
||||
|
||||
static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
uint32_t clkrate, tmp;
|
||||
|
||||
/* Reset MLC controller */
|
||||
writel(MLCCMD_RESET, MLC_CMD(host->io_base));
|
||||
udelay(1000);
|
||||
|
||||
/* Get base clock for MLC block */
|
||||
clkrate = clk_get_rate(host->clk);
|
||||
if (clkrate == 0)
|
||||
clkrate = 104000000;
|
||||
|
||||
/* Unlock MLC_ICR
|
||||
* (among others, will be locked again automatically) */
|
||||
writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base));
|
||||
|
||||
/* Configure MLC Controller: Large Block, 5 Byte Address */
|
||||
tmp = MLCICR_LARGEBLOCK | MLCICR_LONGADDR;
|
||||
writel(tmp, MLC_ICR(host->io_base));
|
||||
|
||||
/* Unlock MLC_TIME_REG
|
||||
* (among others, will be locked again automatically) */
|
||||
writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base));
|
||||
|
||||
/* Compute clock setup values, see LPC and NAND manual */
|
||||
tmp = 0;
|
||||
tmp |= MLCTIMEREG_TCEA_DELAY(clkrate / host->ncfg->tcea_delay + 1);
|
||||
tmp |= MLCTIMEREG_BUSY_DELAY(clkrate / host->ncfg->busy_delay + 1);
|
||||
tmp |= MLCTIMEREG_NAND_TA(clkrate / host->ncfg->nand_ta + 1);
|
||||
tmp |= MLCTIMEREG_RD_HIGH(clkrate / host->ncfg->rd_high + 1);
|
||||
tmp |= MLCTIMEREG_RD_LOW(clkrate / host->ncfg->rd_low);
|
||||
tmp |= MLCTIMEREG_WR_HIGH(clkrate / host->ncfg->wr_high + 1);
|
||||
tmp |= MLCTIMEREG_WR_LOW(clkrate / host->ncfg->wr_low);
|
||||
writel(tmp, MLC_TIME_REG(host->io_base));
|
||||
|
||||
/* Enable IRQ for CONTROLLER_READY and NAND_READY */
|
||||
writeb(MLCIRQ_CONTROLLER_READY | MLCIRQ_NAND_READY,
|
||||
MLC_IRQ_MR(host->io_base));
|
||||
|
||||
/* Normal nCE operation: nCE controlled by controller */
|
||||
writel(MLCCEH_NORMAL, MLC_CEH(host->io_base));
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware specific access to control lines
|
||||
*/
|
||||
static void lpc32xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = nand_chip->priv;
|
||||
|
||||
if (cmd != NAND_CMD_NONE) {
|
||||
if (ctrl & NAND_CLE)
|
||||
writel(cmd, MLC_CMD(host->io_base));
|
||||
else
|
||||
writel(cmd, MLC_ADDR(host->io_base));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read Device Ready (NAND device _and_ controller ready)
|
||||
*/
|
||||
static int lpc32xx_nand_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = nand_chip->priv;
|
||||
|
||||
if ((readb(MLC_ISR(host->io_base)) &
|
||||
(MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) ==
|
||||
(MLCISR_CONTROLLER_READY | MLCISR_NAND_READY))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t lpc3xxx_nand_irq(int irq, struct lpc32xx_nand_host *host)
|
||||
{
|
||||
uint8_t sr;
|
||||
|
||||
/* Clear interrupt flag by reading status */
|
||||
sr = readb(MLC_IRQ_SR(host->io_base));
|
||||
if (sr & MLCIRQ_NAND_READY)
|
||||
complete(&host->comp_nand);
|
||||
if (sr & MLCIRQ_CONTROLLER_READY)
|
||||
complete(&host->comp_controller);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc_nand(struct mtd_info *mtd, struct nand_chip *chip)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
if (readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)
|
||||
goto exit;
|
||||
|
||||
wait_for_completion(&host->comp_nand);
|
||||
|
||||
while (!(readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)) {
|
||||
/* Seems to be delayed sometimes by controller */
|
||||
dev_dbg(&mtd->dev, "Warning: NAND not ready.\n");
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
exit:
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc_controller(struct mtd_info *mtd,
|
||||
struct nand_chip *chip)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
if (readb(MLC_ISR(host->io_base)) & MLCISR_CONTROLLER_READY)
|
||||
goto exit;
|
||||
|
||||
wait_for_completion(&host->comp_controller);
|
||||
|
||||
while (!(readb(MLC_ISR(host->io_base)) &
|
||||
MLCISR_CONTROLLER_READY)) {
|
||||
dev_dbg(&mtd->dev, "Warning: Controller not ready.\n");
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
exit:
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc(struct mtd_info *mtd, struct nand_chip *chip)
|
||||
{
|
||||
lpc32xx_waitfunc_nand(mtd, chip);
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable NAND write protect
|
||||
*/
|
||||
static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio))
|
||||
gpio_set_value(host->ncfg->wp_gpio, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable NAND write protect
|
||||
*/
|
||||
static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio))
|
||||
gpio_set_value(host->ncfg->wp_gpio, 1);
|
||||
}
|
||||
|
||||
static void lpc32xx_dma_complete_func(void *completion)
|
||||
{
|
||||
complete(completion);
|
||||
}
|
||||
|
||||
static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
|
||||
enum dma_transfer_direction dir)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
|
||||
int res;
|
||||
|
||||
sg_init_one(&host->sgl, mem, len);
|
||||
|
||||
res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (res != 1) {
|
||||
dev_err(mtd->dev.parent, "Failed to map sg list\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir,
|
||||
flags);
|
||||
if (!desc) {
|
||||
dev_err(mtd->dev.parent, "Failed to prepare slave sg\n");
|
||||
goto out1;
|
||||
}
|
||||
|
||||
init_completion(&host->comp_dma);
|
||||
desc->callback = lpc32xx_dma_complete_func;
|
||||
desc->callback_param = &host->comp_dma;
|
||||
|
||||
dmaengine_submit(desc);
|
||||
dma_async_issue_pending(host->dma_chan);
|
||||
|
||||
wait_for_completion_timeout(&host->comp_dma, msecs_to_jiffies(1000));
|
||||
|
||||
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
return 0;
|
||||
out1:
|
||||
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
int i, j;
|
||||
uint8_t *oobbuf = chip->oob_poi;
|
||||
uint32_t mlc_isr;
|
||||
int res;
|
||||
uint8_t *dma_buf;
|
||||
bool dma_mapped;
|
||||
|
||||
if ((void *)buf <= high_memory) {
|
||||
dma_buf = buf;
|
||||
dma_mapped = true;
|
||||
} else {
|
||||
dma_buf = host->dma_buf;
|
||||
dma_mapped = false;
|
||||
}
|
||||
|
||||
/* Writing Command and Address */
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
||||
|
||||
/* For all sub-pages */
|
||||
for (i = 0; i < host->mlcsubpages; i++) {
|
||||
/* Start Auto Decode Command */
|
||||
writeb(0x00, MLC_ECC_AUTO_DEC_REG(host->io_base));
|
||||
|
||||
/* Wait for Controller Ready */
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
|
||||
/* Check ECC Error status */
|
||||
mlc_isr = readl(MLC_ISR(host->io_base));
|
||||
if (mlc_isr & MLCISR_DECODER_FAILURE) {
|
||||
mtd->ecc_stats.failed++;
|
||||
dev_warn(&mtd->dev, "%s: DECODER_FAILURE\n", __func__);
|
||||
} else if (mlc_isr & MLCISR_ERRORS_DETECTED) {
|
||||
mtd->ecc_stats.corrected += ((mlc_isr >> 4) & 0x3) + 1;
|
||||
}
|
||||
|
||||
/* Read 512 + 16 Bytes */
|
||||
if (use_dma) {
|
||||
res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512,
|
||||
DMA_DEV_TO_MEM);
|
||||
if (res)
|
||||
return res;
|
||||
} else {
|
||||
for (j = 0; j < (512 >> 2); j++) {
|
||||
*((uint32_t *)(buf)) =
|
||||
readl(MLC_BUFF(host->io_base));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
for (j = 0; j < (16 >> 2); j++) {
|
||||
*((uint32_t *)(oobbuf)) =
|
||||
readl(MLC_BUFF(host->io_base));
|
||||
oobbuf += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_dma && !dma_mapped)
|
||||
memcpy(buf, dma_buf, mtd->writesize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
const uint8_t *oobbuf = chip->oob_poi;
|
||||
uint8_t *dma_buf = (uint8_t *)buf;
|
||||
int res;
|
||||
int i, j;
|
||||
|
||||
if (use_dma && (void *)buf >= high_memory) {
|
||||
dma_buf = host->dma_buf;
|
||||
memcpy(dma_buf, buf, mtd->writesize);
|
||||
}
|
||||
|
||||
for (i = 0; i < host->mlcsubpages; i++) {
|
||||
/* Start Encode */
|
||||
writeb(0x00, MLC_ECC_ENC_REG(host->io_base));
|
||||
|
||||
/* Write 512 + 6 Bytes to Buffer */
|
||||
if (use_dma) {
|
||||
res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512,
|
||||
DMA_MEM_TO_DEV);
|
||||
if (res)
|
||||
return res;
|
||||
} else {
|
||||
for (j = 0; j < (512 >> 2); j++) {
|
||||
writel(*((uint32_t *)(buf)),
|
||||
MLC_BUFF(host->io_base));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
writel(*((uint32_t *)(oobbuf)), MLC_BUFF(host->io_base));
|
||||
oobbuf += 4;
|
||||
writew(*((uint16_t *)(oobbuf)), MLC_BUFF(host->io_base));
|
||||
oobbuf += 12;
|
||||
|
||||
/* Auto Encode w/ Bit 8 = 0 (see LPC MLC Controller manual) */
|
||||
writeb(0x00, MLC_ECC_AUTO_ENC_REG(host->io_base));
|
||||
|
||||
/* Wait for Controller Ready */
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int page)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
/* Read whole page - necessary with MLC controller! */
|
||||
lpc32xx_read_page(mtd, chip, host->dummy_buf, 1, page);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int page)
|
||||
{
|
||||
/* None, write_oob conflicts with the automatic LPC MLC ECC decoder! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Prepares MLC for transfers with H/W ECC enabled: always enabled anyway */
|
||||
static void lpc32xx_ecc_enable(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
/* Always enabled! */
|
||||
}
|
||||
|
||||
static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
struct mtd_info *mtd = &host->mtd;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
if (!host->pdata || !host->pdata->dma_filter) {
|
||||
dev_err(mtd->dev.parent, "no DMA platform data\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter,
|
||||
"nand-mlc");
|
||||
if (!host->dma_chan) {
|
||||
dev_err(mtd->dev.parent, "Failed to request DMA channel\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set direction to a sensible value even if the dmaengine driver
|
||||
* should ignore it. With the default (DMA_MEM_TO_MEM), the amba-pl08x
|
||||
* driver criticizes it as "alien transfer direction".
|
||||
*/
|
||||
host->dma_slave_config.direction = DMA_DEV_TO_MEM;
|
||||
host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_slave_config.src_maxburst = 128;
|
||||
host->dma_slave_config.dst_maxburst = 128;
|
||||
/* DMA controller does flow control: */
|
||||
host->dma_slave_config.device_fc = false;
|
||||
host->dma_slave_config.src_addr = MLC_BUFF(host->io_base_phy);
|
||||
host->dma_slave_config.dst_addr = MLC_BUFF(host->io_base_phy);
|
||||
if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) {
|
||||
dev_err(mtd->dev.parent, "Failed to setup DMA slave\n");
|
||||
goto out1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out1:
|
||||
dma_release_channel(host->dma_chan);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static struct lpc32xx_nand_cfg_mlc *lpc32xx_parse_dt(struct device *dev)
|
||||
{
|
||||
struct lpc32xx_nand_cfg_mlc *ncfg;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL);
|
||||
if (!ncfg)
|
||||
return NULL;
|
||||
|
||||
of_property_read_u32(np, "nxp,tcea-delay", &ncfg->tcea_delay);
|
||||
of_property_read_u32(np, "nxp,busy-delay", &ncfg->busy_delay);
|
||||
of_property_read_u32(np, "nxp,nand-ta", &ncfg->nand_ta);
|
||||
of_property_read_u32(np, "nxp,rd-high", &ncfg->rd_high);
|
||||
of_property_read_u32(np, "nxp,rd-low", &ncfg->rd_low);
|
||||
of_property_read_u32(np, "nxp,wr-high", &ncfg->wr_high);
|
||||
of_property_read_u32(np, "nxp,wr-low", &ncfg->wr_low);
|
||||
|
||||
if (!ncfg->tcea_delay || !ncfg->busy_delay || !ncfg->nand_ta ||
|
||||
!ncfg->rd_high || !ncfg->rd_low || !ncfg->wr_high ||
|
||||
!ncfg->wr_low) {
|
||||
dev_err(dev, "chip parameters not specified correctly\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncfg->wp_gpio = of_get_named_gpio(np, "gpios", 0);
|
||||
|
||||
return ncfg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for NAND controller
|
||||
*/
|
||||
static int lpc32xx_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *nand_chip;
|
||||
struct resource *rc;
|
||||
int res;
|
||||
struct mtd_part_parser_data ppdata = {};
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
host->io_base = devm_ioremap_resource(&pdev->dev, rc);
|
||||
if (IS_ERR(host->io_base))
|
||||
return PTR_ERR(host->io_base);
|
||||
|
||||
host->io_base_phy = rc->start;
|
||||
|
||||
mtd = &host->mtd;
|
||||
nand_chip = &host->nand_chip;
|
||||
if (pdev->dev.of_node)
|
||||
host->ncfg = lpc32xx_parse_dt(&pdev->dev);
|
||||
if (!host->ncfg) {
|
||||
dev_err(&pdev->dev,
|
||||
"Missing or bad NAND config from device tree\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
if (host->ncfg->wp_gpio == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio) &&
|
||||
gpio_request(host->ncfg->wp_gpio, "NAND WP")) {
|
||||
dev_err(&pdev->dev, "GPIO not available\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
lpc32xx_wp_disable(host);
|
||||
|
||||
host->pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
nand_chip->priv = host; /* link the private data structures */
|
||||
mtd->priv = nand_chip;
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
|
||||
/* Get NAND clock */
|
||||
host->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(host->clk)) {
|
||||
dev_err(&pdev->dev, "Clock initialization failure\n");
|
||||
res = -ENOENT;
|
||||
goto err_exit1;
|
||||
}
|
||||
clk_enable(host->clk);
|
||||
|
||||
nand_chip->cmd_ctrl = lpc32xx_nand_cmd_ctrl;
|
||||
nand_chip->dev_ready = lpc32xx_nand_device_ready;
|
||||
nand_chip->chip_delay = 25; /* us */
|
||||
nand_chip->IO_ADDR_R = MLC_DATA(host->io_base);
|
||||
nand_chip->IO_ADDR_W = MLC_DATA(host->io_base);
|
||||
|
||||
/* Init NAND controller */
|
||||
lpc32xx_nand_setup(host);
|
||||
|
||||
platform_set_drvdata(pdev, host);
|
||||
|
||||
/* Initialize function pointers */
|
||||
nand_chip->ecc.hwctl = lpc32xx_ecc_enable;
|
||||
nand_chip->ecc.read_page_raw = lpc32xx_read_page;
|
||||
nand_chip->ecc.read_page = lpc32xx_read_page;
|
||||
nand_chip->ecc.write_page_raw = lpc32xx_write_page_lowlevel;
|
||||
nand_chip->ecc.write_page = lpc32xx_write_page_lowlevel;
|
||||
nand_chip->ecc.write_oob = lpc32xx_write_oob;
|
||||
nand_chip->ecc.read_oob = lpc32xx_read_oob;
|
||||
nand_chip->ecc.strength = 4;
|
||||
nand_chip->waitfunc = lpc32xx_waitfunc;
|
||||
|
||||
nand_chip->options = NAND_NO_SUBPAGE_WRITE;
|
||||
nand_chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
|
||||
nand_chip->bbt_td = &lpc32xx_nand_bbt;
|
||||
nand_chip->bbt_md = &lpc32xx_nand_bbt_mirror;
|
||||
|
||||
if (use_dma) {
|
||||
res = lpc32xx_dma_setup(host);
|
||||
if (res) {
|
||||
res = -EIO;
|
||||
goto err_exit2;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan to find existance of the device and
|
||||
* Get the type of NAND device SMALL block or LARGE block
|
||||
*/
|
||||
if (nand_scan_ident(mtd, 1, NULL)) {
|
||||
res = -ENXIO;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
host->dma_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL);
|
||||
if (!host->dma_buf) {
|
||||
res = -ENOMEM;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
host->dummy_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL);
|
||||
if (!host->dummy_buf) {
|
||||
res = -ENOMEM;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
nand_chip->ecc.mode = NAND_ECC_HW;
|
||||
nand_chip->ecc.size = mtd->writesize;
|
||||
nand_chip->ecc.layout = &lpc32xx_nand_oob;
|
||||
host->mlcsubpages = mtd->writesize / 512;
|
||||
|
||||
/* initially clear interrupt status */
|
||||
readb(MLC_IRQ_SR(host->io_base));
|
||||
|
||||
init_completion(&host->comp_nand);
|
||||
init_completion(&host->comp_controller);
|
||||
|
||||
host->irq = platform_get_irq(pdev, 0);
|
||||
if ((host->irq < 0) || (host->irq >= NR_IRQS)) {
|
||||
dev_err(&pdev->dev, "failed to get platform irq\n");
|
||||
res = -EINVAL;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
if (request_irq(host->irq, (irq_handler_t)&lpc3xxx_nand_irq,
|
||||
IRQF_TRIGGER_HIGH, DRV_NAME, host)) {
|
||||
dev_err(&pdev->dev, "Error requesting NAND IRQ\n");
|
||||
res = -ENXIO;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills out all the uninitialized function pointers with the defaults
|
||||
* And scans for a bad block table if appropriate.
|
||||
*/
|
||||
if (nand_scan_tail(mtd)) {
|
||||
res = -ENXIO;
|
||||
goto err_exit4;
|
||||
}
|
||||
|
||||
mtd->name = DRV_NAME;
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
res = mtd_device_parse_register(mtd, NULL, &ppdata, host->ncfg->parts,
|
||||
host->ncfg->num_parts);
|
||||
if (!res)
|
||||
return res;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
err_exit4:
|
||||
free_irq(host->irq, host);
|
||||
err_exit3:
|
||||
if (use_dma)
|
||||
dma_release_channel(host->dma_chan);
|
||||
err_exit2:
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
err_exit1:
|
||||
lpc32xx_wp_enable(host);
|
||||
gpio_free(host->ncfg->wp_gpio);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove NAND device
|
||||
*/
|
||||
static int lpc32xx_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
struct mtd_info *mtd = &host->mtd;
|
||||
|
||||
nand_release(mtd);
|
||||
free_irq(host->irq, host);
|
||||
if (use_dma)
|
||||
dma_release_channel(host->dma_chan);
|
||||
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
|
||||
lpc32xx_wp_enable(host);
|
||||
gpio_free(host->ncfg->wp_gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int lpc32xx_nand_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
/* Re-enable NAND clock */
|
||||
clk_enable(host->clk);
|
||||
|
||||
/* Fresh init of NAND controller */
|
||||
lpc32xx_nand_setup(host);
|
||||
|
||||
/* Disable write protect */
|
||||
lpc32xx_wp_disable(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
/* Enable write protect for safety */
|
||||
lpc32xx_wp_enable(host);
|
||||
|
||||
/* Disable clock */
|
||||
clk_disable(host->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define lpc32xx_nand_resume NULL
|
||||
#define lpc32xx_nand_suspend NULL
|
||||
#endif
|
||||
|
||||
static const struct of_device_id lpc32xx_nand_match[] = {
|
||||
{ .compatible = "nxp,lpc3220-mlc" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lpc32xx_nand_match);
|
||||
|
||||
static struct platform_driver lpc32xx_nand_driver = {
|
||||
.probe = lpc32xx_nand_probe,
|
||||
.remove = lpc32xx_nand_remove,
|
||||
.resume = lpc32xx_nand_resume,
|
||||
.suspend = lpc32xx_nand_suspend,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = lpc32xx_nand_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(lpc32xx_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
|
||||
MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX MLC controller");
|
||||
1012
drivers/mtd/nand/lpc32xx_slc.c
Normal file
1012
drivers/mtd/nand/lpc32xx_slc.c
Normal file
File diff suppressed because it is too large
Load diff
859
drivers/mtd/nand/mpc5121_nfc.c
Normal file
859
drivers/mtd/nand/mpc5121_nfc.c
Normal file
|
|
@ -0,0 +1,859 @@
|
|||
/*
|
||||
* Copyright 2004-2008 Freescale Semiconductor, Inc.
|
||||
* Copyright 2009 Semihalf.
|
||||
*
|
||||
* Approved as OSADL project by a majority of OSADL members and funded
|
||||
* by OSADL membership fees in 2009; for details see www.osadl.org.
|
||||
*
|
||||
* Based on original driver from Freescale Semiconductor
|
||||
* written by John Rigby <jrigby@freescale.com> on basis
|
||||
* of drivers/mtd/nand/mxc_nand.c. Reworked and extended
|
||||
* Piotr Ziecik <kosmo@semihalf.com>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <asm/mpc5121.h>
|
||||
|
||||
/* Addresses for NFC MAIN RAM BUFFER areas */
|
||||
#define NFC_MAIN_AREA(n) ((n) * 0x200)
|
||||
|
||||
/* Addresses for NFC SPARE BUFFER areas */
|
||||
#define NFC_SPARE_BUFFERS 8
|
||||
#define NFC_SPARE_LEN 0x40
|
||||
#define NFC_SPARE_AREA(n) (0x1000 + ((n) * NFC_SPARE_LEN))
|
||||
|
||||
/* MPC5121 NFC registers */
|
||||
#define NFC_BUF_ADDR 0x1E04
|
||||
#define NFC_FLASH_ADDR 0x1E06
|
||||
#define NFC_FLASH_CMD 0x1E08
|
||||
#define NFC_CONFIG 0x1E0A
|
||||
#define NFC_ECC_STATUS1 0x1E0C
|
||||
#define NFC_ECC_STATUS2 0x1E0E
|
||||
#define NFC_SPAS 0x1E10
|
||||
#define NFC_WRPROT 0x1E12
|
||||
#define NFC_NF_WRPRST 0x1E18
|
||||
#define NFC_CONFIG1 0x1E1A
|
||||
#define NFC_CONFIG2 0x1E1C
|
||||
#define NFC_UNLOCKSTART_BLK0 0x1E20
|
||||
#define NFC_UNLOCKEND_BLK0 0x1E22
|
||||
#define NFC_UNLOCKSTART_BLK1 0x1E24
|
||||
#define NFC_UNLOCKEND_BLK1 0x1E26
|
||||
#define NFC_UNLOCKSTART_BLK2 0x1E28
|
||||
#define NFC_UNLOCKEND_BLK2 0x1E2A
|
||||
#define NFC_UNLOCKSTART_BLK3 0x1E2C
|
||||
#define NFC_UNLOCKEND_BLK3 0x1E2E
|
||||
|
||||
/* Bit Definitions: NFC_BUF_ADDR */
|
||||
#define NFC_RBA_MASK (7 << 0)
|
||||
#define NFC_ACTIVE_CS_SHIFT 5
|
||||
#define NFC_ACTIVE_CS_MASK (3 << NFC_ACTIVE_CS_SHIFT)
|
||||
|
||||
/* Bit Definitions: NFC_CONFIG */
|
||||
#define NFC_BLS_UNLOCKED (1 << 1)
|
||||
|
||||
/* Bit Definitions: NFC_CONFIG1 */
|
||||
#define NFC_ECC_4BIT (1 << 0)
|
||||
#define NFC_FULL_PAGE_DMA (1 << 1)
|
||||
#define NFC_SPARE_ONLY (1 << 2)
|
||||
#define NFC_ECC_ENABLE (1 << 3)
|
||||
#define NFC_INT_MASK (1 << 4)
|
||||
#define NFC_BIG_ENDIAN (1 << 5)
|
||||
#define NFC_RESET (1 << 6)
|
||||
#define NFC_CE (1 << 7)
|
||||
#define NFC_ONE_CYCLE (1 << 8)
|
||||
#define NFC_PPB_32 (0 << 9)
|
||||
#define NFC_PPB_64 (1 << 9)
|
||||
#define NFC_PPB_128 (2 << 9)
|
||||
#define NFC_PPB_256 (3 << 9)
|
||||
#define NFC_PPB_MASK (3 << 9)
|
||||
#define NFC_FULL_PAGE_INT (1 << 11)
|
||||
|
||||
/* Bit Definitions: NFC_CONFIG2 */
|
||||
#define NFC_COMMAND (1 << 0)
|
||||
#define NFC_ADDRESS (1 << 1)
|
||||
#define NFC_INPUT (1 << 2)
|
||||
#define NFC_OUTPUT (1 << 3)
|
||||
#define NFC_ID (1 << 4)
|
||||
#define NFC_STATUS (1 << 5)
|
||||
#define NFC_CMD_FAIL (1 << 15)
|
||||
#define NFC_INT (1 << 15)
|
||||
|
||||
/* Bit Definitions: NFC_WRPROT */
|
||||
#define NFC_WPC_LOCK_TIGHT (1 << 0)
|
||||
#define NFC_WPC_LOCK (1 << 1)
|
||||
#define NFC_WPC_UNLOCK (1 << 2)
|
||||
|
||||
#define DRV_NAME "mpc5121_nfc"
|
||||
|
||||
/* Timeouts */
|
||||
#define NFC_RESET_TIMEOUT 1000 /* 1 ms */
|
||||
#define NFC_TIMEOUT (HZ / 10) /* 1/10 s */
|
||||
|
||||
struct mpc5121_nfc_prv {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
int irq;
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
wait_queue_head_t irq_waitq;
|
||||
uint column;
|
||||
int spareonly;
|
||||
void __iomem *csreg;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static void mpc5121_nfc_done(struct mtd_info *mtd);
|
||||
|
||||
/* Read NFC register */
|
||||
static inline u16 nfc_read(struct mtd_info *mtd, uint reg)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
|
||||
return in_be16(prv->regs + reg);
|
||||
}
|
||||
|
||||
/* Write NFC register */
|
||||
static inline void nfc_write(struct mtd_info *mtd, uint reg, u16 val)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
|
||||
out_be16(prv->regs + reg, val);
|
||||
}
|
||||
|
||||
/* Set bits in NFC register */
|
||||
static inline void nfc_set(struct mtd_info *mtd, uint reg, u16 bits)
|
||||
{
|
||||
nfc_write(mtd, reg, nfc_read(mtd, reg) | bits);
|
||||
}
|
||||
|
||||
/* Clear bits in NFC register */
|
||||
static inline void nfc_clear(struct mtd_info *mtd, uint reg, u16 bits)
|
||||
{
|
||||
nfc_write(mtd, reg, nfc_read(mtd, reg) & ~bits);
|
||||
}
|
||||
|
||||
/* Invoke address cycle */
|
||||
static inline void mpc5121_nfc_send_addr(struct mtd_info *mtd, u16 addr)
|
||||
{
|
||||
nfc_write(mtd, NFC_FLASH_ADDR, addr);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_ADDRESS);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* Invoke command cycle */
|
||||
static inline void mpc5121_nfc_send_cmd(struct mtd_info *mtd, u16 cmd)
|
||||
{
|
||||
nfc_write(mtd, NFC_FLASH_CMD, cmd);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_COMMAND);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* Send data from NFC buffers to NAND flash */
|
||||
static inline void mpc5121_nfc_send_prog_page(struct mtd_info *mtd)
|
||||
{
|
||||
nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_INPUT);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* Receive data from NAND flash */
|
||||
static inline void mpc5121_nfc_send_read_page(struct mtd_info *mtd)
|
||||
{
|
||||
nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_OUTPUT);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* Receive ID from NAND flash */
|
||||
static inline void mpc5121_nfc_send_read_id(struct mtd_info *mtd)
|
||||
{
|
||||
nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_ID);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* Receive status from NAND flash */
|
||||
static inline void mpc5121_nfc_send_read_status(struct mtd_info *mtd)
|
||||
{
|
||||
nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK);
|
||||
nfc_write(mtd, NFC_CONFIG2, NFC_STATUS);
|
||||
mpc5121_nfc_done(mtd);
|
||||
}
|
||||
|
||||
/* NFC interrupt handler */
|
||||
static irqreturn_t mpc5121_nfc_irq(int irq, void *data)
|
||||
{
|
||||
struct mtd_info *mtd = data;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_INT_MASK);
|
||||
wake_up(&prv->irq_waitq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Wait for operation complete */
|
||||
static void mpc5121_nfc_done(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
int rv;
|
||||
|
||||
if ((nfc_read(mtd, NFC_CONFIG2) & NFC_INT) == 0) {
|
||||
nfc_clear(mtd, NFC_CONFIG1, NFC_INT_MASK);
|
||||
rv = wait_event_timeout(prv->irq_waitq,
|
||||
(nfc_read(mtd, NFC_CONFIG2) & NFC_INT), NFC_TIMEOUT);
|
||||
|
||||
if (!rv)
|
||||
dev_warn(prv->dev,
|
||||
"Timeout while waiting for interrupt.\n");
|
||||
}
|
||||
|
||||
nfc_clear(mtd, NFC_CONFIG2, NFC_INT);
|
||||
}
|
||||
|
||||
/* Do address cycle(s) */
|
||||
static void mpc5121_nfc_addr_cycle(struct mtd_info *mtd, int column, int page)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
u32 pagemask = chip->pagemask;
|
||||
|
||||
if (column != -1) {
|
||||
mpc5121_nfc_send_addr(mtd, column);
|
||||
if (mtd->writesize > 512)
|
||||
mpc5121_nfc_send_addr(mtd, column >> 8);
|
||||
}
|
||||
|
||||
if (page != -1) {
|
||||
do {
|
||||
mpc5121_nfc_send_addr(mtd, page & 0xFF);
|
||||
page >>= 8;
|
||||
pagemask >>= 8;
|
||||
} while (pagemask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Control chip select signals */
|
||||
static void mpc5121_nfc_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
if (chip < 0) {
|
||||
nfc_clear(mtd, NFC_CONFIG1, NFC_CE);
|
||||
return;
|
||||
}
|
||||
|
||||
nfc_clear(mtd, NFC_BUF_ADDR, NFC_ACTIVE_CS_MASK);
|
||||
nfc_set(mtd, NFC_BUF_ADDR, (chip << NFC_ACTIVE_CS_SHIFT) &
|
||||
NFC_ACTIVE_CS_MASK);
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_CE);
|
||||
}
|
||||
|
||||
/* Init external chip select logic on ADS5121 board */
|
||||
static int ads5121_chipselect_init(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
struct device_node *dn;
|
||||
|
||||
dn = of_find_compatible_node(NULL, NULL, "fsl,mpc5121ads-cpld");
|
||||
if (dn) {
|
||||
prv->csreg = of_iomap(dn, 0);
|
||||
of_node_put(dn);
|
||||
if (!prv->csreg)
|
||||
return -ENOMEM;
|
||||
|
||||
/* CPLD Register 9 controls NAND /CE Lines */
|
||||
prv->csreg += 9;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Control chips select signal on ADS5121 board */
|
||||
static void ads5121_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
struct nand_chip *nand = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = nand->priv;
|
||||
u8 v;
|
||||
|
||||
v = in_8(prv->csreg);
|
||||
v |= 0x0F;
|
||||
|
||||
if (chip >= 0) {
|
||||
mpc5121_nfc_select_chip(mtd, 0);
|
||||
v &= ~(1 << chip);
|
||||
} else
|
||||
mpc5121_nfc_select_chip(mtd, -1);
|
||||
|
||||
out_8(prv->csreg, v);
|
||||
}
|
||||
|
||||
/* Read NAND Ready/Busy signal */
|
||||
static int mpc5121_nfc_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
/*
|
||||
* NFC handles ready/busy signal internally. Therefore, this function
|
||||
* always returns status as ready.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write command to NAND flash */
|
||||
static void mpc5121_nfc_command(struct mtd_info *mtd, unsigned command,
|
||||
int column, int page)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
|
||||
prv->column = (column >= 0) ? column : 0;
|
||||
prv->spareonly = 0;
|
||||
|
||||
switch (command) {
|
||||
case NAND_CMD_PAGEPROG:
|
||||
mpc5121_nfc_send_prog_page(mtd);
|
||||
break;
|
||||
/*
|
||||
* NFC does not support sub-page reads and writes,
|
||||
* so emulate them using full page transfers.
|
||||
*/
|
||||
case NAND_CMD_READ0:
|
||||
column = 0;
|
||||
break;
|
||||
|
||||
case NAND_CMD_READ1:
|
||||
prv->column += 256;
|
||||
command = NAND_CMD_READ0;
|
||||
column = 0;
|
||||
break;
|
||||
|
||||
case NAND_CMD_READOOB:
|
||||
prv->spareonly = 1;
|
||||
command = NAND_CMD_READ0;
|
||||
column = 0;
|
||||
break;
|
||||
|
||||
case NAND_CMD_SEQIN:
|
||||
mpc5121_nfc_command(mtd, NAND_CMD_READ0, column, page);
|
||||
column = 0;
|
||||
break;
|
||||
|
||||
case NAND_CMD_ERASE1:
|
||||
case NAND_CMD_ERASE2:
|
||||
case NAND_CMD_READID:
|
||||
case NAND_CMD_STATUS:
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
mpc5121_nfc_send_cmd(mtd, command);
|
||||
mpc5121_nfc_addr_cycle(mtd, column, page);
|
||||
|
||||
switch (command) {
|
||||
case NAND_CMD_READ0:
|
||||
if (mtd->writesize > 512)
|
||||
mpc5121_nfc_send_cmd(mtd, NAND_CMD_READSTART);
|
||||
mpc5121_nfc_send_read_page(mtd);
|
||||
break;
|
||||
|
||||
case NAND_CMD_READID:
|
||||
mpc5121_nfc_send_read_id(mtd);
|
||||
break;
|
||||
|
||||
case NAND_CMD_STATUS:
|
||||
mpc5121_nfc_send_read_status(mtd);
|
||||
if (chip->options & NAND_BUSWIDTH_16)
|
||||
prv->column = 1;
|
||||
else
|
||||
prv->column = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy data from/to NFC spare buffers. */
|
||||
static void mpc5121_nfc_copy_spare(struct mtd_info *mtd, uint offset,
|
||||
u8 *buffer, uint size, int wr)
|
||||
{
|
||||
struct nand_chip *nand = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = nand->priv;
|
||||
uint o, s, sbsize, blksize;
|
||||
|
||||
/*
|
||||
* NAND spare area is available through NFC spare buffers.
|
||||
* The NFC divides spare area into (page_size / 512) chunks.
|
||||
* Each chunk is placed into separate spare memory area, using
|
||||
* first (spare_size / num_of_chunks) bytes of the buffer.
|
||||
*
|
||||
* For NAND device in which the spare area is not divided fully
|
||||
* by the number of chunks, number of used bytes in each spare
|
||||
* buffer is rounded down to the nearest even number of bytes,
|
||||
* and all remaining bytes are added to the last used spare area.
|
||||
*
|
||||
* For more information read section 26.6.10 of MPC5121e
|
||||
* Microcontroller Reference Manual, Rev. 3.
|
||||
*/
|
||||
|
||||
/* Calculate number of valid bytes in each spare buffer */
|
||||
sbsize = (mtd->oobsize / (mtd->writesize / 512)) & ~1;
|
||||
|
||||
while (size) {
|
||||
/* Calculate spare buffer number */
|
||||
s = offset / sbsize;
|
||||
if (s > NFC_SPARE_BUFFERS - 1)
|
||||
s = NFC_SPARE_BUFFERS - 1;
|
||||
|
||||
/*
|
||||
* Calculate offset to requested data block in selected spare
|
||||
* buffer and its size.
|
||||
*/
|
||||
o = offset - (s * sbsize);
|
||||
blksize = min(sbsize - o, size);
|
||||
|
||||
if (wr)
|
||||
memcpy_toio(prv->regs + NFC_SPARE_AREA(s) + o,
|
||||
buffer, blksize);
|
||||
else
|
||||
memcpy_fromio(buffer,
|
||||
prv->regs + NFC_SPARE_AREA(s) + o, blksize);
|
||||
|
||||
buffer += blksize;
|
||||
offset += blksize;
|
||||
size -= blksize;
|
||||
};
|
||||
}
|
||||
|
||||
/* Copy data from/to NFC main and spare buffers */
|
||||
static void mpc5121_nfc_buf_copy(struct mtd_info *mtd, u_char *buf, int len,
|
||||
int wr)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
uint c = prv->column;
|
||||
uint l;
|
||||
|
||||
/* Handle spare area access */
|
||||
if (prv->spareonly || c >= mtd->writesize) {
|
||||
/* Calculate offset from beginning of spare area */
|
||||
if (c >= mtd->writesize)
|
||||
c -= mtd->writesize;
|
||||
|
||||
prv->column += len;
|
||||
mpc5121_nfc_copy_spare(mtd, c, buf, len, wr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle main area access - limit copy length to prevent
|
||||
* crossing main/spare boundary.
|
||||
*/
|
||||
l = min((uint)len, mtd->writesize - c);
|
||||
prv->column += l;
|
||||
|
||||
if (wr)
|
||||
memcpy_toio(prv->regs + NFC_MAIN_AREA(0) + c, buf, l);
|
||||
else
|
||||
memcpy_fromio(buf, prv->regs + NFC_MAIN_AREA(0) + c, l);
|
||||
|
||||
/* Handle crossing main/spare boundary */
|
||||
if (l != len) {
|
||||
buf += l;
|
||||
len -= l;
|
||||
mpc5121_nfc_buf_copy(mtd, buf, len, wr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read data from NFC buffers */
|
||||
static void mpc5121_nfc_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
mpc5121_nfc_buf_copy(mtd, buf, len, 0);
|
||||
}
|
||||
|
||||
/* Write data to NFC buffers */
|
||||
static void mpc5121_nfc_write_buf(struct mtd_info *mtd,
|
||||
const u_char *buf, int len)
|
||||
{
|
||||
mpc5121_nfc_buf_copy(mtd, (u_char *)buf, len, 1);
|
||||
}
|
||||
|
||||
/* Read byte from NFC buffers */
|
||||
static u8 mpc5121_nfc_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
u8 tmp;
|
||||
|
||||
mpc5121_nfc_read_buf(mtd, &tmp, sizeof(tmp));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/* Read word from NFC buffers */
|
||||
static u16 mpc5121_nfc_read_word(struct mtd_info *mtd)
|
||||
{
|
||||
u16 tmp;
|
||||
|
||||
mpc5121_nfc_read_buf(mtd, (u_char *)&tmp, sizeof(tmp));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read NFC configuration from Reset Config Word
|
||||
*
|
||||
* NFC is configured during reset in basis of information stored
|
||||
* in Reset Config Word. There is no other way to set NAND block
|
||||
* size, spare size and bus width.
|
||||
*/
|
||||
static int mpc5121_nfc_read_hw_config(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
struct mpc512x_reset_module *rm;
|
||||
struct device_node *rmnode;
|
||||
uint rcw_pagesize = 0;
|
||||
uint rcw_sparesize = 0;
|
||||
uint rcw_width;
|
||||
uint rcwh;
|
||||
uint romloc, ps;
|
||||
int ret = 0;
|
||||
|
||||
rmnode = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-reset");
|
||||
if (!rmnode) {
|
||||
dev_err(prv->dev, "Missing 'fsl,mpc5121-reset' "
|
||||
"node in device tree!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rm = of_iomap(rmnode, 0);
|
||||
if (!rm) {
|
||||
dev_err(prv->dev, "Error mapping reset module node!\n");
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rcwh = in_be32(&rm->rcwhr);
|
||||
|
||||
/* Bit 6: NFC bus width */
|
||||
rcw_width = ((rcwh >> 6) & 0x1) ? 2 : 1;
|
||||
|
||||
/* Bit 7: NFC Page/Spare size */
|
||||
ps = (rcwh >> 7) & 0x1;
|
||||
|
||||
/* Bits [22:21]: ROM Location */
|
||||
romloc = (rcwh >> 21) & 0x3;
|
||||
|
||||
/* Decode RCW bits */
|
||||
switch ((ps << 2) | romloc) {
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
rcw_pagesize = 512;
|
||||
rcw_sparesize = 16;
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
rcw_pagesize = 4096;
|
||||
rcw_sparesize = 128;
|
||||
break;
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
rcw_pagesize = 2048;
|
||||
rcw_sparesize = 64;
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
rcw_pagesize = 4096;
|
||||
rcw_sparesize = 218;
|
||||
break;
|
||||
}
|
||||
|
||||
mtd->writesize = rcw_pagesize;
|
||||
mtd->oobsize = rcw_sparesize;
|
||||
if (rcw_width == 2)
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
dev_notice(prv->dev, "Configured for "
|
||||
"%u-bit NAND, page size %u "
|
||||
"with %u spare.\n",
|
||||
rcw_width * 8, rcw_pagesize,
|
||||
rcw_sparesize);
|
||||
iounmap(rm);
|
||||
out:
|
||||
of_node_put(rmnode);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Free driver resources */
|
||||
static void mpc5121_nfc_free(struct device *dev, struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct mpc5121_nfc_prv *prv = chip->priv;
|
||||
|
||||
if (prv->clk)
|
||||
clk_disable_unprepare(prv->clk);
|
||||
|
||||
if (prv->csreg)
|
||||
iounmap(prv->csreg);
|
||||
}
|
||||
|
||||
static int mpc5121_nfc_probe(struct platform_device *op)
|
||||
{
|
||||
struct device_node *rootnode, *dn = op->dev.of_node;
|
||||
struct clk *clk;
|
||||
struct device *dev = &op->dev;
|
||||
struct mpc5121_nfc_prv *prv;
|
||||
struct resource res;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *chip;
|
||||
unsigned long regs_paddr, regs_size;
|
||||
const __be32 *chips_no;
|
||||
int resettime = 0;
|
||||
int retval = 0;
|
||||
int rev, len;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
|
||||
/*
|
||||
* Check SoC revision. This driver supports only NFC
|
||||
* in MPC5121 revision 2 and MPC5123 revision 3.
|
||||
*/
|
||||
rev = (mfspr(SPRN_SVR) >> 4) & 0xF;
|
||||
if ((rev != 2) && (rev != 3)) {
|
||||
dev_err(dev, "SoC revision %u is not supported!\n", rev);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
prv = devm_kzalloc(dev, sizeof(*prv), GFP_KERNEL);
|
||||
if (!prv)
|
||||
return -ENOMEM;
|
||||
|
||||
mtd = &prv->mtd;
|
||||
chip = &prv->chip;
|
||||
|
||||
mtd->priv = chip;
|
||||
chip->priv = prv;
|
||||
prv->dev = dev;
|
||||
|
||||
/* Read NFC configuration from Reset Config Word */
|
||||
retval = mpc5121_nfc_read_hw_config(mtd);
|
||||
if (retval) {
|
||||
dev_err(dev, "Unable to read NFC config!\n");
|
||||
return retval;
|
||||
}
|
||||
|
||||
prv->irq = irq_of_parse_and_map(dn, 0);
|
||||
if (prv->irq == NO_IRQ) {
|
||||
dev_err(dev, "Error mapping IRQ!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
retval = of_address_to_resource(dn, 0, &res);
|
||||
if (retval) {
|
||||
dev_err(dev, "Error parsing memory region!\n");
|
||||
return retval;
|
||||
}
|
||||
|
||||
chips_no = of_get_property(dn, "chips", &len);
|
||||
if (!chips_no || len != sizeof(*chips_no)) {
|
||||
dev_err(dev, "Invalid/missing 'chips' property!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regs_paddr = res.start;
|
||||
regs_size = resource_size(&res);
|
||||
|
||||
if (!devm_request_mem_region(dev, regs_paddr, regs_size, DRV_NAME)) {
|
||||
dev_err(dev, "Error requesting memory region!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
prv->regs = devm_ioremap(dev, regs_paddr, regs_size);
|
||||
if (!prv->regs) {
|
||||
dev_err(dev, "Error mapping memory region!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mtd->name = "MPC5121 NAND";
|
||||
ppdata.of_node = dn;
|
||||
chip->dev_ready = mpc5121_nfc_dev_ready;
|
||||
chip->cmdfunc = mpc5121_nfc_command;
|
||||
chip->read_byte = mpc5121_nfc_read_byte;
|
||||
chip->read_word = mpc5121_nfc_read_word;
|
||||
chip->read_buf = mpc5121_nfc_read_buf;
|
||||
chip->write_buf = mpc5121_nfc_write_buf;
|
||||
chip->select_chip = mpc5121_nfc_select_chip;
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
/* Support external chip-select logic on ADS5121 board */
|
||||
rootnode = of_find_node_by_path("/");
|
||||
if (of_device_is_compatible(rootnode, "fsl,mpc5121ads")) {
|
||||
retval = ads5121_chipselect_init(mtd);
|
||||
if (retval) {
|
||||
dev_err(dev, "Chipselect init error!\n");
|
||||
of_node_put(rootnode);
|
||||
return retval;
|
||||
}
|
||||
|
||||
chip->select_chip = ads5121_select_chip;
|
||||
}
|
||||
of_node_put(rootnode);
|
||||
|
||||
/* Enable NFC clock */
|
||||
clk = devm_clk_get(dev, "ipg");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Unable to acquire NFC clock!\n");
|
||||
retval = PTR_ERR(clk);
|
||||
goto error;
|
||||
}
|
||||
retval = clk_prepare_enable(clk);
|
||||
if (retval) {
|
||||
dev_err(dev, "Unable to enable NFC clock!\n");
|
||||
goto error;
|
||||
}
|
||||
prv->clk = clk;
|
||||
|
||||
/* Reset NAND Flash controller */
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_RESET);
|
||||
while (nfc_read(mtd, NFC_CONFIG1) & NFC_RESET) {
|
||||
if (resettime++ >= NFC_RESET_TIMEOUT) {
|
||||
dev_err(dev, "Timeout while resetting NFC!\n");
|
||||
retval = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
/* Enable write to NFC memory */
|
||||
nfc_write(mtd, NFC_CONFIG, NFC_BLS_UNLOCKED);
|
||||
|
||||
/* Enable write to all NAND pages */
|
||||
nfc_write(mtd, NFC_UNLOCKSTART_BLK0, 0x0000);
|
||||
nfc_write(mtd, NFC_UNLOCKEND_BLK0, 0xFFFF);
|
||||
nfc_write(mtd, NFC_WRPROT, NFC_WPC_UNLOCK);
|
||||
|
||||
/*
|
||||
* Setup NFC:
|
||||
* - Big Endian transfers,
|
||||
* - Interrupt after full page read/write.
|
||||
*/
|
||||
nfc_write(mtd, NFC_CONFIG1, NFC_BIG_ENDIAN | NFC_INT_MASK |
|
||||
NFC_FULL_PAGE_INT);
|
||||
|
||||
/* Set spare area size */
|
||||
nfc_write(mtd, NFC_SPAS, mtd->oobsize >> 1);
|
||||
|
||||
init_waitqueue_head(&prv->irq_waitq);
|
||||
retval = devm_request_irq(dev, prv->irq, &mpc5121_nfc_irq, 0, DRV_NAME,
|
||||
mtd);
|
||||
if (retval) {
|
||||
dev_err(dev, "Error requesting IRQ!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Detect NAND chips */
|
||||
if (nand_scan(mtd, be32_to_cpup(chips_no))) {
|
||||
dev_err(dev, "NAND Flash not found !\n");
|
||||
retval = -ENXIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Set erase block size */
|
||||
switch (mtd->erasesize / mtd->writesize) {
|
||||
case 32:
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_PPB_32);
|
||||
break;
|
||||
|
||||
case 64:
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_PPB_64);
|
||||
break;
|
||||
|
||||
case 128:
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_PPB_128);
|
||||
break;
|
||||
|
||||
case 256:
|
||||
nfc_set(mtd, NFC_CONFIG1, NFC_PPB_256);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev, "Unsupported NAND flash!\n");
|
||||
retval = -ENXIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, mtd);
|
||||
|
||||
/* Register device in MTD */
|
||||
retval = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
|
||||
if (retval) {
|
||||
dev_err(dev, "Error adding MTD device!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
mpc5121_nfc_free(dev, mtd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int mpc5121_nfc_remove(struct platform_device *op)
|
||||
{
|
||||
struct device *dev = &op->dev;
|
||||
struct mtd_info *mtd = dev_get_drvdata(dev);
|
||||
|
||||
nand_release(mtd);
|
||||
mpc5121_nfc_free(dev, mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mpc5121_nfc_match[] = {
|
||||
{ .compatible = "fsl,mpc5121-nfc", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver mpc5121_nfc_driver = {
|
||||
.probe = mpc5121_nfc_probe,
|
||||
.remove = mpc5121_nfc_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mpc5121_nfc_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(mpc5121_nfc_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("MPC5121 NAND MTD driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
1614
drivers/mtd/nand/mxc_nand.c
Normal file
1614
drivers/mtd/nand/mxc_nand.c
Normal file
File diff suppressed because it is too large
Load diff
4256
drivers/mtd/nand/nand_base.c
Normal file
4256
drivers/mtd/nand/nand_base.c
Normal file
File diff suppressed because it is too large
Load diff
1375
drivers/mtd/nand/nand_bbt.c
Normal file
1375
drivers/mtd/nand/nand_bbt.c
Normal file
File diff suppressed because it is too large
Load diff
243
drivers/mtd/nand/nand_bch.c
Normal file
243
drivers/mtd/nand/nand_bch.c
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* This file provides ECC correction for more than 1 bit per block of data,
|
||||
* using binary BCH codes. It relies on the generic BCH library lib/bch.c.
|
||||
*
|
||||
* Copyright © 2011 Ivan Djelic <ivan.djelic@parrot.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this file; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_bch.h>
|
||||
#include <linux/bch.h>
|
||||
|
||||
/**
|
||||
* struct nand_bch_control - private NAND BCH control structure
|
||||
* @bch: BCH control structure
|
||||
* @ecclayout: private ecc layout for this BCH configuration
|
||||
* @errloc: error location array
|
||||
* @eccmask: XOR ecc mask, allows erased pages to be decoded as valid
|
||||
*/
|
||||
struct nand_bch_control {
|
||||
struct bch_control *bch;
|
||||
struct nand_ecclayout ecclayout;
|
||||
unsigned int *errloc;
|
||||
unsigned char *eccmask;
|
||||
};
|
||||
|
||||
/**
|
||||
* nand_bch_calculate_ecc - [NAND Interface] Calculate ECC for data block
|
||||
* @mtd: MTD block structure
|
||||
* @buf: input buffer with raw data
|
||||
* @code: output buffer with ECC
|
||||
*/
|
||||
int nand_bch_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf,
|
||||
unsigned char *code)
|
||||
{
|
||||
const struct nand_chip *chip = mtd->priv;
|
||||
struct nand_bch_control *nbc = chip->ecc.priv;
|
||||
unsigned int i;
|
||||
|
||||
memset(code, 0, chip->ecc.bytes);
|
||||
encode_bch(nbc->bch, buf, chip->ecc.size, code);
|
||||
|
||||
/* apply mask so that an erased page is a valid codeword */
|
||||
for (i = 0; i < chip->ecc.bytes; i++)
|
||||
code[i] ^= nbc->eccmask[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_bch_calculate_ecc);
|
||||
|
||||
/**
|
||||
* nand_bch_correct_data - [NAND Interface] Detect and correct bit error(s)
|
||||
* @mtd: MTD block structure
|
||||
* @buf: raw data read from the chip
|
||||
* @read_ecc: ECC from the chip
|
||||
* @calc_ecc: the ECC calculated from raw data
|
||||
*
|
||||
* Detect and correct bit errors for a data byte block
|
||||
*/
|
||||
int nand_bch_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||
{
|
||||
const struct nand_chip *chip = mtd->priv;
|
||||
struct nand_bch_control *nbc = chip->ecc.priv;
|
||||
unsigned int *errloc = nbc->errloc;
|
||||
int i, count;
|
||||
|
||||
count = decode_bch(nbc->bch, NULL, chip->ecc.size, read_ecc, calc_ecc,
|
||||
NULL, errloc);
|
||||
if (count > 0) {
|
||||
for (i = 0; i < count; i++) {
|
||||
if (errloc[i] < (chip->ecc.size*8))
|
||||
/* error is located in data, correct it */
|
||||
buf[errloc[i] >> 3] ^= (1 << (errloc[i] & 7));
|
||||
/* else error in ecc, no action needed */
|
||||
|
||||
pr_debug("%s: corrected bitflip %u\n", __func__,
|
||||
errloc[i]);
|
||||
}
|
||||
} else if (count < 0) {
|
||||
printk(KERN_ERR "ecc unrecoverable error\n");
|
||||
count = -1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_bch_correct_data);
|
||||
|
||||
/**
|
||||
* nand_bch_init - [NAND Interface] Initialize NAND BCH error correction
|
||||
* @mtd: MTD block structure
|
||||
* @eccsize: ecc block size in bytes
|
||||
* @eccbytes: ecc length in bytes
|
||||
* @ecclayout: output default layout
|
||||
*
|
||||
* Returns:
|
||||
* a pointer to a new NAND BCH control structure, or NULL upon failure
|
||||
*
|
||||
* Initialize NAND BCH error correction. Parameters @eccsize and @eccbytes
|
||||
* are used to compute BCH parameters m (Galois field order) and t (error
|
||||
* correction capability). @eccbytes should be equal to the number of bytes
|
||||
* required to store m*t bits, where m is such that 2^m-1 > @eccsize*8.
|
||||
*
|
||||
* Example: to configure 4 bit correction per 512 bytes, you should pass
|
||||
* @eccsize = 512 (thus, m=13 is the smallest integer such that 2^m-1 > 512*8)
|
||||
* @eccbytes = 7 (7 bytes are required to store m*t = 13*4 = 52 bits)
|
||||
*/
|
||||
struct nand_bch_control *
|
||||
nand_bch_init(struct mtd_info *mtd, unsigned int eccsize, unsigned int eccbytes,
|
||||
struct nand_ecclayout **ecclayout)
|
||||
{
|
||||
unsigned int m, t, eccsteps, i;
|
||||
struct nand_ecclayout *layout;
|
||||
struct nand_bch_control *nbc = NULL;
|
||||
unsigned char *erased_page;
|
||||
|
||||
if (!eccsize || !eccbytes) {
|
||||
printk(KERN_WARNING "ecc parameters not supplied\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m = fls(1+8*eccsize);
|
||||
t = (eccbytes*8)/m;
|
||||
|
||||
nbc = kzalloc(sizeof(*nbc), GFP_KERNEL);
|
||||
if (!nbc)
|
||||
goto fail;
|
||||
|
||||
nbc->bch = init_bch(m, t, 0);
|
||||
if (!nbc->bch)
|
||||
goto fail;
|
||||
|
||||
/* verify that eccbytes has the expected value */
|
||||
if (nbc->bch->ecc_bytes != eccbytes) {
|
||||
printk(KERN_WARNING "invalid eccbytes %u, should be %u\n",
|
||||
eccbytes, nbc->bch->ecc_bytes);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
eccsteps = mtd->writesize/eccsize;
|
||||
|
||||
/* if no ecc placement scheme was provided, build one */
|
||||
if (!*ecclayout) {
|
||||
|
||||
/* handle large page devices only */
|
||||
if (mtd->oobsize < 64) {
|
||||
printk(KERN_WARNING "must provide an oob scheme for "
|
||||
"oobsize %d\n", mtd->oobsize);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
layout = &nbc->ecclayout;
|
||||
layout->eccbytes = eccsteps*eccbytes;
|
||||
|
||||
/* reserve 2 bytes for bad block marker */
|
||||
if (layout->eccbytes+2 > mtd->oobsize) {
|
||||
printk(KERN_WARNING "no suitable oob scheme available "
|
||||
"for oobsize %d eccbytes %u\n", mtd->oobsize,
|
||||
eccbytes);
|
||||
goto fail;
|
||||
}
|
||||
/* put ecc bytes at oob tail */
|
||||
for (i = 0; i < layout->eccbytes; i++)
|
||||
layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i;
|
||||
|
||||
layout->oobfree[0].offset = 2;
|
||||
layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes;
|
||||
|
||||
*ecclayout = layout;
|
||||
}
|
||||
|
||||
/* sanity checks */
|
||||
if (8*(eccsize+eccbytes) >= (1 << m)) {
|
||||
printk(KERN_WARNING "eccsize %u is too large\n", eccsize);
|
||||
goto fail;
|
||||
}
|
||||
if ((*ecclayout)->eccbytes != (eccsteps*eccbytes)) {
|
||||
printk(KERN_WARNING "invalid ecc layout\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nbc->eccmask = kmalloc(eccbytes, GFP_KERNEL);
|
||||
nbc->errloc = kmalloc(t*sizeof(*nbc->errloc), GFP_KERNEL);
|
||||
if (!nbc->eccmask || !nbc->errloc)
|
||||
goto fail;
|
||||
/*
|
||||
* compute and store the inverted ecc of an erased ecc block
|
||||
*/
|
||||
erased_page = kmalloc(eccsize, GFP_KERNEL);
|
||||
if (!erased_page)
|
||||
goto fail;
|
||||
|
||||
memset(erased_page, 0xff, eccsize);
|
||||
memset(nbc->eccmask, 0, eccbytes);
|
||||
encode_bch(nbc->bch, erased_page, eccsize, nbc->eccmask);
|
||||
kfree(erased_page);
|
||||
|
||||
for (i = 0; i < eccbytes; i++)
|
||||
nbc->eccmask[i] ^= 0xff;
|
||||
|
||||
return nbc;
|
||||
fail:
|
||||
nand_bch_free(nbc);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_bch_init);
|
||||
|
||||
/**
|
||||
* nand_bch_free - [NAND Interface] Release NAND BCH ECC resources
|
||||
* @nbc: NAND BCH control structure
|
||||
*/
|
||||
void nand_bch_free(struct nand_bch_control *nbc)
|
||||
{
|
||||
if (nbc) {
|
||||
free_bch(nbc->bch);
|
||||
kfree(nbc->errloc);
|
||||
kfree(nbc->eccmask);
|
||||
kfree(nbc);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(nand_bch_free);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ivan Djelic <ivan.djelic@parrot.com>");
|
||||
MODULE_DESCRIPTION("NAND software BCH ECC support");
|
||||
533
drivers/mtd/nand/nand_ecc.c
Normal file
533
drivers/mtd/nand/nand_ecc.c
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
/*
|
||||
* This file contains an ECC algorithm that detects and corrects 1 bit
|
||||
* errors in a 256 byte block of data.
|
||||
*
|
||||
* drivers/mtd/nand/nand_ecc.c
|
||||
*
|
||||
* Copyright © 2008 Koninklijke Philips Electronics NV.
|
||||
* Author: Frans Meulenbroeks
|
||||
*
|
||||
* Completely replaces the previous ECC implementation which was written by:
|
||||
* Steven J. Hill (sjhill@realitydiluted.com)
|
||||
* Thomas Gleixner (tglx@linutronix.de)
|
||||
*
|
||||
* Information on how this algorithm works and how it was developed
|
||||
* can be found in Documentation/mtd/nand_ecc.txt
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this file; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* The STANDALONE macro is useful when running the code outside the kernel
|
||||
* e.g. when running the code in a testbed or a benchmark program.
|
||||
* When STANDALONE is used, the module related macros are commented out
|
||||
* as well as the linux include files.
|
||||
* Instead a private definition of mtd_info is given to satisfy the compiler
|
||||
* (the code does not use mtd_info, so the code does not care)
|
||||
*/
|
||||
#ifndef STANDALONE
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <asm/byteorder.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
struct mtd_info;
|
||||
#define EXPORT_SYMBOL(x) /* x */
|
||||
|
||||
#define MODULE_LICENSE(x) /* x */
|
||||
#define MODULE_AUTHOR(x) /* x */
|
||||
#define MODULE_DESCRIPTION(x) /* x */
|
||||
|
||||
#define pr_err printf
|
||||
#endif
|
||||
|
||||
/*
|
||||
* invparity is a 256 byte table that contains the odd parity
|
||||
* for each byte. So if the number of bits in a byte is even,
|
||||
* the array element is 1, and when the number of bits is odd
|
||||
* the array eleemnt is 0.
|
||||
*/
|
||||
static const char invparity[256] = {
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
|
||||
};
|
||||
|
||||
/*
|
||||
* bitsperbyte contains the number of bits per byte
|
||||
* this is only used for testing and repairing parity
|
||||
* (a precalculated value slightly improves performance)
|
||||
*/
|
||||
static const char bitsperbyte[256] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
|
||||
};
|
||||
|
||||
/*
|
||||
* addressbits is a lookup table to filter out the bits from the xor-ed
|
||||
* ECC data that identify the faulty location.
|
||||
* this is only used for repairing parity
|
||||
* see the comments in nand_correct_data for more details
|
||||
*/
|
||||
static const char addressbits[256] = {
|
||||
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01,
|
||||
0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03,
|
||||
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01,
|
||||
0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03,
|
||||
0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05,
|
||||
0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07,
|
||||
0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05,
|
||||
0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07,
|
||||
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01,
|
||||
0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03,
|
||||
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01,
|
||||
0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03,
|
||||
0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05,
|
||||
0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07,
|
||||
0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05,
|
||||
0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07,
|
||||
0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09,
|
||||
0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b,
|
||||
0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09,
|
||||
0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b,
|
||||
0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
|
||||
0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f,
|
||||
0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
|
||||
0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f,
|
||||
0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09,
|
||||
0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b,
|
||||
0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09,
|
||||
0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b,
|
||||
0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
|
||||
0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f,
|
||||
0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
|
||||
0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f
|
||||
};
|
||||
|
||||
/**
|
||||
* __nand_calculate_ecc - [NAND Interface] Calculate 3-byte ECC for 256/512-byte
|
||||
* block
|
||||
* @buf: input buffer with raw data
|
||||
* @eccsize: data bytes per ECC step (256 or 512)
|
||||
* @code: output buffer with ECC
|
||||
*/
|
||||
void __nand_calculate_ecc(const unsigned char *buf, unsigned int eccsize,
|
||||
unsigned char *code)
|
||||
{
|
||||
int i;
|
||||
const uint32_t *bp = (uint32_t *)buf;
|
||||
/* 256 or 512 bytes/ecc */
|
||||
const uint32_t eccsize_mult = eccsize >> 8;
|
||||
uint32_t cur; /* current value in buffer */
|
||||
/* rp0..rp15..rp17 are the various accumulated parities (per byte) */
|
||||
uint32_t rp0, rp1, rp2, rp3, rp4, rp5, rp6, rp7;
|
||||
uint32_t rp8, rp9, rp10, rp11, rp12, rp13, rp14, rp15, rp16;
|
||||
uint32_t uninitialized_var(rp17); /* to make compiler happy */
|
||||
uint32_t par; /* the cumulative parity for all data */
|
||||
uint32_t tmppar; /* the cumulative parity for this iteration;
|
||||
for rp12, rp14 and rp16 at the end of the
|
||||
loop */
|
||||
|
||||
par = 0;
|
||||
rp4 = 0;
|
||||
rp6 = 0;
|
||||
rp8 = 0;
|
||||
rp10 = 0;
|
||||
rp12 = 0;
|
||||
rp14 = 0;
|
||||
rp16 = 0;
|
||||
|
||||
/*
|
||||
* The loop is unrolled a number of times;
|
||||
* This avoids if statements to decide on which rp value to update
|
||||
* Also we process the data by longwords.
|
||||
* Note: passing unaligned data might give a performance penalty.
|
||||
* It is assumed that the buffers are aligned.
|
||||
* tmppar is the cumulative sum of this iteration.
|
||||
* needed for calculating rp12, rp14, rp16 and par
|
||||
* also used as a performance improvement for rp6, rp8 and rp10
|
||||
*/
|
||||
for (i = 0; i < eccsize_mult << 2; i++) {
|
||||
cur = *bp++;
|
||||
tmppar = cur;
|
||||
rp4 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp6 ^= tmppar;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp8 ^= tmppar;
|
||||
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
rp6 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp6 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp10 ^= tmppar;
|
||||
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
rp6 ^= cur;
|
||||
rp8 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp6 ^= cur;
|
||||
rp8 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
rp8 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp8 ^= cur;
|
||||
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
rp6 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp6 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
rp4 ^= cur;
|
||||
cur = *bp++;
|
||||
tmppar ^= cur;
|
||||
|
||||
par ^= tmppar;
|
||||
if ((i & 0x1) == 0)
|
||||
rp12 ^= tmppar;
|
||||
if ((i & 0x2) == 0)
|
||||
rp14 ^= tmppar;
|
||||
if (eccsize_mult == 2 && (i & 0x4) == 0)
|
||||
rp16 ^= tmppar;
|
||||
}
|
||||
|
||||
/*
|
||||
* handle the fact that we use longword operations
|
||||
* we'll bring rp4..rp14..rp16 back to single byte entities by
|
||||
* shifting and xoring first fold the upper and lower 16 bits,
|
||||
* then the upper and lower 8 bits.
|
||||
*/
|
||||
rp4 ^= (rp4 >> 16);
|
||||
rp4 ^= (rp4 >> 8);
|
||||
rp4 &= 0xff;
|
||||
rp6 ^= (rp6 >> 16);
|
||||
rp6 ^= (rp6 >> 8);
|
||||
rp6 &= 0xff;
|
||||
rp8 ^= (rp8 >> 16);
|
||||
rp8 ^= (rp8 >> 8);
|
||||
rp8 &= 0xff;
|
||||
rp10 ^= (rp10 >> 16);
|
||||
rp10 ^= (rp10 >> 8);
|
||||
rp10 &= 0xff;
|
||||
rp12 ^= (rp12 >> 16);
|
||||
rp12 ^= (rp12 >> 8);
|
||||
rp12 &= 0xff;
|
||||
rp14 ^= (rp14 >> 16);
|
||||
rp14 ^= (rp14 >> 8);
|
||||
rp14 &= 0xff;
|
||||
if (eccsize_mult == 2) {
|
||||
rp16 ^= (rp16 >> 16);
|
||||
rp16 ^= (rp16 >> 8);
|
||||
rp16 &= 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
* we also need to calculate the row parity for rp0..rp3
|
||||
* This is present in par, because par is now
|
||||
* rp3 rp3 rp2 rp2 in little endian and
|
||||
* rp2 rp2 rp3 rp3 in big endian
|
||||
* as well as
|
||||
* rp1 rp0 rp1 rp0 in little endian and
|
||||
* rp0 rp1 rp0 rp1 in big endian
|
||||
* First calculate rp2 and rp3
|
||||
*/
|
||||
#ifdef __BIG_ENDIAN
|
||||
rp2 = (par >> 16);
|
||||
rp2 ^= (rp2 >> 8);
|
||||
rp2 &= 0xff;
|
||||
rp3 = par & 0xffff;
|
||||
rp3 ^= (rp3 >> 8);
|
||||
rp3 &= 0xff;
|
||||
#else
|
||||
rp3 = (par >> 16);
|
||||
rp3 ^= (rp3 >> 8);
|
||||
rp3 &= 0xff;
|
||||
rp2 = par & 0xffff;
|
||||
rp2 ^= (rp2 >> 8);
|
||||
rp2 &= 0xff;
|
||||
#endif
|
||||
|
||||
/* reduce par to 16 bits then calculate rp1 and rp0 */
|
||||
par ^= (par >> 16);
|
||||
#ifdef __BIG_ENDIAN
|
||||
rp0 = (par >> 8) & 0xff;
|
||||
rp1 = (par & 0xff);
|
||||
#else
|
||||
rp1 = (par >> 8) & 0xff;
|
||||
rp0 = (par & 0xff);
|
||||
#endif
|
||||
|
||||
/* finally reduce par to 8 bits */
|
||||
par ^= (par >> 8);
|
||||
par &= 0xff;
|
||||
|
||||
/*
|
||||
* and calculate rp5..rp15..rp17
|
||||
* note that par = rp4 ^ rp5 and due to the commutative property
|
||||
* of the ^ operator we can say:
|
||||
* rp5 = (par ^ rp4);
|
||||
* The & 0xff seems superfluous, but benchmarking learned that
|
||||
* leaving it out gives slightly worse results. No idea why, probably
|
||||
* it has to do with the way the pipeline in pentium is organized.
|
||||
*/
|
||||
rp5 = (par ^ rp4) & 0xff;
|
||||
rp7 = (par ^ rp6) & 0xff;
|
||||
rp9 = (par ^ rp8) & 0xff;
|
||||
rp11 = (par ^ rp10) & 0xff;
|
||||
rp13 = (par ^ rp12) & 0xff;
|
||||
rp15 = (par ^ rp14) & 0xff;
|
||||
if (eccsize_mult == 2)
|
||||
rp17 = (par ^ rp16) & 0xff;
|
||||
|
||||
/*
|
||||
* Finally calculate the ECC bits.
|
||||
* Again here it might seem that there are performance optimisations
|
||||
* possible, but benchmarks showed that on the system this is developed
|
||||
* the code below is the fastest
|
||||
*/
|
||||
#ifdef CONFIG_MTD_NAND_ECC_SMC
|
||||
code[0] =
|
||||
(invparity[rp7] << 7) |
|
||||
(invparity[rp6] << 6) |
|
||||
(invparity[rp5] << 5) |
|
||||
(invparity[rp4] << 4) |
|
||||
(invparity[rp3] << 3) |
|
||||
(invparity[rp2] << 2) |
|
||||
(invparity[rp1] << 1) |
|
||||
(invparity[rp0]);
|
||||
code[1] =
|
||||
(invparity[rp15] << 7) |
|
||||
(invparity[rp14] << 6) |
|
||||
(invparity[rp13] << 5) |
|
||||
(invparity[rp12] << 4) |
|
||||
(invparity[rp11] << 3) |
|
||||
(invparity[rp10] << 2) |
|
||||
(invparity[rp9] << 1) |
|
||||
(invparity[rp8]);
|
||||
#else
|
||||
code[1] =
|
||||
(invparity[rp7] << 7) |
|
||||
(invparity[rp6] << 6) |
|
||||
(invparity[rp5] << 5) |
|
||||
(invparity[rp4] << 4) |
|
||||
(invparity[rp3] << 3) |
|
||||
(invparity[rp2] << 2) |
|
||||
(invparity[rp1] << 1) |
|
||||
(invparity[rp0]);
|
||||
code[0] =
|
||||
(invparity[rp15] << 7) |
|
||||
(invparity[rp14] << 6) |
|
||||
(invparity[rp13] << 5) |
|
||||
(invparity[rp12] << 4) |
|
||||
(invparity[rp11] << 3) |
|
||||
(invparity[rp10] << 2) |
|
||||
(invparity[rp9] << 1) |
|
||||
(invparity[rp8]);
|
||||
#endif
|
||||
if (eccsize_mult == 1)
|
||||
code[2] =
|
||||
(invparity[par & 0xf0] << 7) |
|
||||
(invparity[par & 0x0f] << 6) |
|
||||
(invparity[par & 0xcc] << 5) |
|
||||
(invparity[par & 0x33] << 4) |
|
||||
(invparity[par & 0xaa] << 3) |
|
||||
(invparity[par & 0x55] << 2) |
|
||||
3;
|
||||
else
|
||||
code[2] =
|
||||
(invparity[par & 0xf0] << 7) |
|
||||
(invparity[par & 0x0f] << 6) |
|
||||
(invparity[par & 0xcc] << 5) |
|
||||
(invparity[par & 0x33] << 4) |
|
||||
(invparity[par & 0xaa] << 3) |
|
||||
(invparity[par & 0x55] << 2) |
|
||||
(invparity[rp17] << 1) |
|
||||
(invparity[rp16] << 0);
|
||||
}
|
||||
EXPORT_SYMBOL(__nand_calculate_ecc);
|
||||
|
||||
/**
|
||||
* nand_calculate_ecc - [NAND Interface] Calculate 3-byte ECC for 256/512-byte
|
||||
* block
|
||||
* @mtd: MTD block structure
|
||||
* @buf: input buffer with raw data
|
||||
* @code: output buffer with ECC
|
||||
*/
|
||||
int nand_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf,
|
||||
unsigned char *code)
|
||||
{
|
||||
__nand_calculate_ecc(buf,
|
||||
((struct nand_chip *)mtd->priv)->ecc.size, code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_calculate_ecc);
|
||||
|
||||
/**
|
||||
* __nand_correct_data - [NAND Interface] Detect and correct bit error(s)
|
||||
* @buf: raw data read from the chip
|
||||
* @read_ecc: ECC from the chip
|
||||
* @calc_ecc: the ECC calculated from raw data
|
||||
* @eccsize: data bytes per ECC step (256 or 512)
|
||||
*
|
||||
* Detect and correct a 1 bit error for eccsize byte block
|
||||
*/
|
||||
int __nand_correct_data(unsigned char *buf,
|
||||
unsigned char *read_ecc, unsigned char *calc_ecc,
|
||||
unsigned int eccsize)
|
||||
{
|
||||
unsigned char b0, b1, b2, bit_addr;
|
||||
unsigned int byte_addr;
|
||||
/* 256 or 512 bytes/ecc */
|
||||
const uint32_t eccsize_mult = eccsize >> 8;
|
||||
|
||||
/*
|
||||
* b0 to b2 indicate which bit is faulty (if any)
|
||||
* we might need the xor result more than once,
|
||||
* so keep them in a local var
|
||||
*/
|
||||
#ifdef CONFIG_MTD_NAND_ECC_SMC
|
||||
b0 = read_ecc[0] ^ calc_ecc[0];
|
||||
b1 = read_ecc[1] ^ calc_ecc[1];
|
||||
#else
|
||||
b0 = read_ecc[1] ^ calc_ecc[1];
|
||||
b1 = read_ecc[0] ^ calc_ecc[0];
|
||||
#endif
|
||||
b2 = read_ecc[2] ^ calc_ecc[2];
|
||||
|
||||
/* check if there are any bitfaults */
|
||||
|
||||
/* repeated if statements are slightly more efficient than switch ... */
|
||||
/* ordered in order of likelihood */
|
||||
|
||||
if ((b0 | b1 | b2) == 0)
|
||||
return 0; /* no error */
|
||||
|
||||
if ((((b0 ^ (b0 >> 1)) & 0x55) == 0x55) &&
|
||||
(((b1 ^ (b1 >> 1)) & 0x55) == 0x55) &&
|
||||
((eccsize_mult == 1 && ((b2 ^ (b2 >> 1)) & 0x54) == 0x54) ||
|
||||
(eccsize_mult == 2 && ((b2 ^ (b2 >> 1)) & 0x55) == 0x55))) {
|
||||
/* single bit error */
|
||||
/*
|
||||
* rp17/rp15/13/11/9/7/5/3/1 indicate which byte is the faulty
|
||||
* byte, cp 5/3/1 indicate the faulty bit.
|
||||
* A lookup table (called addressbits) is used to filter
|
||||
* the bits from the byte they are in.
|
||||
* A marginal optimisation is possible by having three
|
||||
* different lookup tables.
|
||||
* One as we have now (for b0), one for b2
|
||||
* (that would avoid the >> 1), and one for b1 (with all values
|
||||
* << 4). However it was felt that introducing two more tables
|
||||
* hardly justify the gain.
|
||||
*
|
||||
* The b2 shift is there to get rid of the lowest two bits.
|
||||
* We could also do addressbits[b2] >> 1 but for the
|
||||
* performance it does not make any difference
|
||||
*/
|
||||
if (eccsize_mult == 1)
|
||||
byte_addr = (addressbits[b1] << 4) + addressbits[b0];
|
||||
else
|
||||
byte_addr = (addressbits[b2 & 0x3] << 8) +
|
||||
(addressbits[b1] << 4) + addressbits[b0];
|
||||
bit_addr = addressbits[b2 >> 2];
|
||||
/* flip the bit */
|
||||
buf[byte_addr] ^= (1 << bit_addr);
|
||||
return 1;
|
||||
|
||||
}
|
||||
/* count nr of bits; use table lookup, faster than calculating it */
|
||||
if ((bitsperbyte[b0] + bitsperbyte[b1] + bitsperbyte[b2]) == 1)
|
||||
return 1; /* error in ECC data; no action needed */
|
||||
|
||||
pr_err("%s: uncorrectable ECC error\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(__nand_correct_data);
|
||||
|
||||
/**
|
||||
* nand_correct_data - [NAND Interface] Detect and correct bit error(s)
|
||||
* @mtd: MTD block structure
|
||||
* @buf: raw data read from the chip
|
||||
* @read_ecc: ECC from the chip
|
||||
* @calc_ecc: the ECC calculated from raw data
|
||||
*
|
||||
* Detect and correct a 1 bit error for 256/512 byte block
|
||||
*/
|
||||
int nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||
{
|
||||
return __nand_correct_data(buf, read_ecc, calc_ecc,
|
||||
((struct nand_chip *)mtd->priv)->ecc.size);
|
||||
}
|
||||
EXPORT_SYMBOL(nand_correct_data);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Frans Meulenbroeks <fransmeulenbroeks@gmail.com>");
|
||||
MODULE_DESCRIPTION("Generic NAND ECC support");
|
||||
189
drivers/mtd/nand/nand_ids.c
Normal file
189
drivers/mtd/nand/nand_ids.c
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* drivers/mtd/nandids.c
|
||||
*
|
||||
* Copyright (C) 2002 Thomas Gleixner (tglx@linutronix.de)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#define LP_OPTIONS NAND_SAMSUNG_LP_OPTIONS
|
||||
#define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16)
|
||||
|
||||
#define SP_OPTIONS NAND_NEED_READRDY
|
||||
#define SP_OPTIONS16 (SP_OPTIONS | NAND_BUSWIDTH_16)
|
||||
|
||||
/*
|
||||
* The chip ID list:
|
||||
* name, device ID, page size, chip size in MiB, eraseblock size, options
|
||||
*
|
||||
* If page size and eraseblock size are 0, the sizes are taken from the
|
||||
* extended chip ID.
|
||||
*/
|
||||
struct nand_flash_dev nand_flash_ids[] = {
|
||||
/*
|
||||
* Some incompatible NAND chips share device ID's and so must be
|
||||
* listed by full ID. We list them first so that we can easily identify
|
||||
* the most specific match.
|
||||
*/
|
||||
{"TC58NVG2S0F 4G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
|
||||
SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
|
||||
{"TC58NVG3S0F 8G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xd3, 0x90, 0x26, 0x76, 0x15, 0x02, 0x08} },
|
||||
SZ_4K, SZ_1K, SZ_256K, 0, 8, 232, NAND_ECC_INFO(4, SZ_512) },
|
||||
{"TC58NVG5D2 32G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xd7, 0x94, 0x32, 0x76, 0x56, 0x09, 0x00} },
|
||||
SZ_8K, SZ_4K, SZ_1M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) },
|
||||
{"TC58NVG6D2 64G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xde, 0x94, 0x82, 0x76, 0x56, 0x04, 0x20} },
|
||||
SZ_8K, SZ_8K, SZ_2M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) },
|
||||
{"SDTNRGAMA 64G 3.3V 8-bit",
|
||||
{ .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x50} },
|
||||
SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) },
|
||||
{"H27UCG8T2ATR-BC 64G 3.3V 8-bit",
|
||||
{ .id = {0xad, 0xde, 0x94, 0xda, 0x74, 0xc4} },
|
||||
SZ_8K, SZ_8K, SZ_2M, 0, 6, 640, NAND_ECC_INFO(40, SZ_1K),
|
||||
4 },
|
||||
|
||||
LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE5, 4, SZ_8K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xD6, 8, SZ_8K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xE6, 8, SZ_8K, SP_OPTIONS),
|
||||
|
||||
LEGACY_ID_NAND("NAND 16MiB 1,8V 8-bit", 0x33, 16, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 16MiB 3,3V 8-bit", 0x73, 16, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 16MiB 1,8V 16-bit", 0x43, 16, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 16MiB 3,3V 16-bit", 0x53, 16, SZ_16K, SP_OPTIONS16),
|
||||
|
||||
LEGACY_ID_NAND("NAND 32MiB 1,8V 8-bit", 0x35, 32, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 32MiB 3,3V 8-bit", 0x75, 32, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 32MiB 1,8V 16-bit", 0x45, 32, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 32MiB 3,3V 16-bit", 0x55, 32, SZ_16K, SP_OPTIONS16),
|
||||
|
||||
LEGACY_ID_NAND("NAND 64MiB 1,8V 8-bit", 0x36, 64, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 64MiB 3,3V 8-bit", 0x76, 64, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 64MiB 1,8V 16-bit", 0x46, 64, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 64MiB 3,3V 16-bit", 0x56, 64, SZ_16K, SP_OPTIONS16),
|
||||
|
||||
LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x78, 128, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x39, 128, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 128MiB 3,3V 8-bit", 0x79, 128, SZ_16K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x72, 128, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x49, 128, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x74, 128, SZ_16K, SP_OPTIONS16),
|
||||
LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x59, 128, SZ_16K, SP_OPTIONS16),
|
||||
|
||||
LEGACY_ID_NAND("NAND 256MiB 3,3V 8-bit", 0x71, 256, SZ_16K, SP_OPTIONS),
|
||||
|
||||
/*
|
||||
* These are the new chips with large page size. Their page size and
|
||||
* eraseblock size are determined from the extended ID bytes.
|
||||
*/
|
||||
|
||||
/* 512 Megabit */
|
||||
EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA2, 64, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA0, 64, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF2, 64, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xD0, 64, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF0, 64, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB2, 64, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB0, 64, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC2, 64, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC0, 64, LP_OPTIONS16),
|
||||
|
||||
/* 1 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 128MiB 1,8V 8-bit", 0xA1, 128, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xD1, 128, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xB1, 128, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 128MiB 3,3V 16-bit", 0xC1, 128, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xAD, 128, LP_OPTIONS16),
|
||||
|
||||
/* 2 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 256MiB 1,8V 8-bit", 0xAA, 256, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 256MiB 3,3V 8-bit", 0xDA, 256, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 256MiB 1,8V 16-bit", 0xBA, 256, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 256MiB 3,3V 16-bit", 0xCA, 256, LP_OPTIONS16),
|
||||
|
||||
/* 4 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 512MiB 1,8V 8-bit", 0xAC, 512, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 512MiB 3,3V 8-bit", 0xDC, 512, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 512MiB 1,8V 16-bit", 0xBC, 512, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 512MiB 3,3V 16-bit", 0xCC, 512, LP_OPTIONS16),
|
||||
|
||||
/* 8 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 1GiB 1,8V 8-bit", 0xA3, 1024, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 1GiB 3,3V 8-bit", 0xD3, 1024, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 1GiB 1,8V 16-bit", 0xB3, 1024, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 1GiB 3,3V 16-bit", 0xC3, 1024, LP_OPTIONS16),
|
||||
|
||||
/* 16 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 2GiB 1,8V 8-bit", 0xA5, 2048, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 2GiB 3,3V 8-bit", 0xD5, 2048, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 2GiB 1,8V 16-bit", 0xB5, 2048, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 2GiB 3,3V 16-bit", 0xC5, 2048, LP_OPTIONS16),
|
||||
|
||||
/* 32 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 4GiB 1,8V 8-bit", 0xA7, 4096, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 4GiB 3,3V 8-bit", 0xD7, 4096, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 4GiB 1,8V 16-bit", 0xB7, 4096, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 4GiB 3,3V 16-bit", 0xC7, 4096, LP_OPTIONS16),
|
||||
|
||||
/* 64 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 8GiB 1,8V 8-bit", 0xAE, 8192, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 8GiB 3,3V 8-bit", 0xDE, 8192, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 8GiB 1,8V 16-bit", 0xBE, 8192, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 8GiB 3,3V 16-bit", 0xCE, 8192, LP_OPTIONS16),
|
||||
|
||||
/* 128 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 16GiB 1,8V 8-bit", 0x1A, 16384, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 16GiB 3,3V 8-bit", 0x3A, 16384, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 16GiB 1,8V 16-bit", 0x2A, 16384, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 16GiB 3,3V 16-bit", 0x4A, 16384, LP_OPTIONS16),
|
||||
|
||||
/* 256 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 32GiB 1,8V 8-bit", 0x1C, 32768, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 32GiB 3,3V 8-bit", 0x3C, 32768, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 32GiB 1,8V 16-bit", 0x2C, 32768, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 32GiB 3,3V 16-bit", 0x4C, 32768, LP_OPTIONS16),
|
||||
|
||||
/* 512 Gigabit */
|
||||
EXTENDED_ID_NAND("NAND 64GiB 1,8V 8-bit", 0x1E, 65536, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64GiB 3,3V 8-bit", 0x3E, 65536, LP_OPTIONS),
|
||||
EXTENDED_ID_NAND("NAND 64GiB 1,8V 16-bit", 0x2E, 65536, LP_OPTIONS16),
|
||||
EXTENDED_ID_NAND("NAND 64GiB 3,3V 16-bit", 0x4E, 65536, LP_OPTIONS16),
|
||||
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* Manufacturer IDs */
|
||||
struct nand_manufacturers nand_manuf_ids[] = {
|
||||
{NAND_MFR_TOSHIBA, "Toshiba"},
|
||||
{NAND_MFR_SAMSUNG, "Samsung"},
|
||||
{NAND_MFR_FUJITSU, "Fujitsu"},
|
||||
{NAND_MFR_NATIONAL, "National"},
|
||||
{NAND_MFR_RENESAS, "Renesas"},
|
||||
{NAND_MFR_STMICRO, "ST Micro"},
|
||||
{NAND_MFR_HYNIX, "Hynix"},
|
||||
{NAND_MFR_MICRON, "Micron"},
|
||||
{NAND_MFR_AMD, "AMD/Spansion"},
|
||||
{NAND_MFR_MACRONIX, "Macronix"},
|
||||
{NAND_MFR_EON, "Eon"},
|
||||
{NAND_MFR_SANDISK, "SanDisk"},
|
||||
{NAND_MFR_INTEL, "Intel"},
|
||||
{0x0, "Unknown"}
|
||||
};
|
||||
|
||||
EXPORT_SYMBOL(nand_manuf_ids);
|
||||
EXPORT_SYMBOL(nand_flash_ids);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
|
||||
MODULE_DESCRIPTION("Nand device & manufacturer IDs");
|
||||
253
drivers/mtd/nand/nand_timings.c
Normal file
253
drivers/mtd/nand/nand_timings.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Free Electrons
|
||||
*
|
||||
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
static const struct nand_sdr_timings onfi_sdr_timings[] = {
|
||||
/* Mode 0 */
|
||||
{
|
||||
.tADL_min = 200000,
|
||||
.tALH_min = 20000,
|
||||
.tALS_min = 50000,
|
||||
.tAR_min = 25000,
|
||||
.tCEA_max = 100000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 20000,
|
||||
.tCHZ_max = 100000,
|
||||
.tCLH_min = 20000,
|
||||
.tCLR_min = 20000,
|
||||
.tCLS_min = 50000,
|
||||
.tCOH_min = 0,
|
||||
.tCS_min = 70000,
|
||||
.tDH_min = 20000,
|
||||
.tDS_min = 40000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 10000,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 100000,
|
||||
.tREA_max = 40000,
|
||||
.tREH_min = 30000,
|
||||
.tRHOH_min = 0,
|
||||
.tRHW_min = 200000,
|
||||
.tRHZ_max = 200000,
|
||||
.tRLOH_min = 0,
|
||||
.tRP_min = 50000,
|
||||
.tRST_max = 250000000000ULL,
|
||||
.tWB_max = 200000,
|
||||
.tRR_min = 40000,
|
||||
.tWC_min = 100000,
|
||||
.tWH_min = 30000,
|
||||
.tWHR_min = 120000,
|
||||
.tWP_min = 50000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
/* Mode 1 */
|
||||
{
|
||||
.tADL_min = 100000,
|
||||
.tALH_min = 10000,
|
||||
.tALS_min = 25000,
|
||||
.tAR_min = 10000,
|
||||
.tCEA_max = 45000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 10000,
|
||||
.tCHZ_max = 50000,
|
||||
.tCLH_min = 10000,
|
||||
.tCLR_min = 10000,
|
||||
.tCLS_min = 25000,
|
||||
.tCOH_min = 15000,
|
||||
.tCS_min = 35000,
|
||||
.tDH_min = 10000,
|
||||
.tDS_min = 20000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 0,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 50000,
|
||||
.tREA_max = 30000,
|
||||
.tREH_min = 15000,
|
||||
.tRHOH_min = 15000,
|
||||
.tRHW_min = 100000,
|
||||
.tRHZ_max = 100000,
|
||||
.tRLOH_min = 0,
|
||||
.tRP_min = 25000,
|
||||
.tRR_min = 20000,
|
||||
.tRST_max = 500000000,
|
||||
.tWB_max = 100000,
|
||||
.tWC_min = 45000,
|
||||
.tWH_min = 15000,
|
||||
.tWHR_min = 80000,
|
||||
.tWP_min = 25000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
/* Mode 2 */
|
||||
{
|
||||
.tADL_min = 100000,
|
||||
.tALH_min = 10000,
|
||||
.tALS_min = 15000,
|
||||
.tAR_min = 10000,
|
||||
.tCEA_max = 30000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 10000,
|
||||
.tCHZ_max = 50000,
|
||||
.tCLH_min = 10000,
|
||||
.tCLR_min = 10000,
|
||||
.tCLS_min = 15000,
|
||||
.tCOH_min = 15000,
|
||||
.tCS_min = 25000,
|
||||
.tDH_min = 5000,
|
||||
.tDS_min = 15000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 0,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 35000,
|
||||
.tREA_max = 25000,
|
||||
.tREH_min = 15000,
|
||||
.tRHOH_min = 15000,
|
||||
.tRHW_min = 100000,
|
||||
.tRHZ_max = 100000,
|
||||
.tRLOH_min = 0,
|
||||
.tRR_min = 20000,
|
||||
.tRST_max = 500000000,
|
||||
.tWB_max = 100000,
|
||||
.tRP_min = 17000,
|
||||
.tWC_min = 35000,
|
||||
.tWH_min = 15000,
|
||||
.tWHR_min = 80000,
|
||||
.tWP_min = 17000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
/* Mode 3 */
|
||||
{
|
||||
.tADL_min = 100000,
|
||||
.tALH_min = 5000,
|
||||
.tALS_min = 10000,
|
||||
.tAR_min = 10000,
|
||||
.tCEA_max = 25000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 5000,
|
||||
.tCHZ_max = 50000,
|
||||
.tCLH_min = 5000,
|
||||
.tCLR_min = 10000,
|
||||
.tCLS_min = 10000,
|
||||
.tCOH_min = 15000,
|
||||
.tCS_min = 25000,
|
||||
.tDH_min = 5000,
|
||||
.tDS_min = 10000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 0,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 30000,
|
||||
.tREA_max = 20000,
|
||||
.tREH_min = 10000,
|
||||
.tRHOH_min = 15000,
|
||||
.tRHW_min = 100000,
|
||||
.tRHZ_max = 100000,
|
||||
.tRLOH_min = 0,
|
||||
.tRP_min = 15000,
|
||||
.tRR_min = 20000,
|
||||
.tRST_max = 500000000,
|
||||
.tWB_max = 100000,
|
||||
.tWC_min = 30000,
|
||||
.tWH_min = 10000,
|
||||
.tWHR_min = 80000,
|
||||
.tWP_min = 15000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
/* Mode 4 */
|
||||
{
|
||||
.tADL_min = 70000,
|
||||
.tALH_min = 5000,
|
||||
.tALS_min = 10000,
|
||||
.tAR_min = 10000,
|
||||
.tCEA_max = 25000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 5000,
|
||||
.tCHZ_max = 30000,
|
||||
.tCLH_min = 5000,
|
||||
.tCLR_min = 10000,
|
||||
.tCLS_min = 10000,
|
||||
.tCOH_min = 15000,
|
||||
.tCS_min = 20000,
|
||||
.tDH_min = 5000,
|
||||
.tDS_min = 10000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 0,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 25000,
|
||||
.tREA_max = 20000,
|
||||
.tREH_min = 10000,
|
||||
.tRHOH_min = 15000,
|
||||
.tRHW_min = 100000,
|
||||
.tRHZ_max = 100000,
|
||||
.tRLOH_min = 5000,
|
||||
.tRP_min = 12000,
|
||||
.tRR_min = 20000,
|
||||
.tRST_max = 500000000,
|
||||
.tWB_max = 100000,
|
||||
.tWC_min = 25000,
|
||||
.tWH_min = 10000,
|
||||
.tWHR_min = 80000,
|
||||
.tWP_min = 12000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
/* Mode 5 */
|
||||
{
|
||||
.tADL_min = 70000,
|
||||
.tALH_min = 5000,
|
||||
.tALS_min = 10000,
|
||||
.tAR_min = 10000,
|
||||
.tCEA_max = 25000,
|
||||
.tCEH_min = 20000,
|
||||
.tCH_min = 5000,
|
||||
.tCHZ_max = 30000,
|
||||
.tCLH_min = 5000,
|
||||
.tCLR_min = 10000,
|
||||
.tCLS_min = 10000,
|
||||
.tCOH_min = 15000,
|
||||
.tCS_min = 15000,
|
||||
.tDH_min = 5000,
|
||||
.tDS_min = 7000,
|
||||
.tFEAT_max = 1000000,
|
||||
.tIR_min = 0,
|
||||
.tITC_max = 1000000,
|
||||
.tRC_min = 20000,
|
||||
.tREA_max = 16000,
|
||||
.tREH_min = 7000,
|
||||
.tRHOH_min = 15000,
|
||||
.tRHW_min = 100000,
|
||||
.tRHZ_max = 100000,
|
||||
.tRLOH_min = 5000,
|
||||
.tRP_min = 10000,
|
||||
.tRR_min = 20000,
|
||||
.tRST_max = 500000000,
|
||||
.tWB_max = 100000,
|
||||
.tWC_min = 20000,
|
||||
.tWH_min = 7000,
|
||||
.tWHR_min = 80000,
|
||||
.tWP_min = 10000,
|
||||
.tWW_min = 100000,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* onfi_async_timing_mode_to_sdr_timings - [NAND Interface] Retrieve NAND
|
||||
* timings according to the given ONFI timing mode
|
||||
* @mode: ONFI timing mode
|
||||
*/
|
||||
const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
|
||||
{
|
||||
if (mode < 0 || mode >= ARRAY_SIZE(onfi_sdr_timings))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return &onfi_sdr_timings[mode];
|
||||
}
|
||||
EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
|
||||
2424
drivers/mtd/nand/nandsim.c
Normal file
2424
drivers/mtd/nand/nandsim.c
Normal file
File diff suppressed because it is too large
Load diff
291
drivers/mtd/nand/ndfc.c
Normal file
291
drivers/mtd/nand/ndfc.c
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* drivers/mtd/ndfc.c
|
||||
*
|
||||
* Overview:
|
||||
* Platform independent driver for NDFC (NanD Flash Controller)
|
||||
* integrated into EP440 cores
|
||||
*
|
||||
* Ported to an OF platform driver by Sean MacLennan
|
||||
*
|
||||
* The NDFC supports multiple chips, but this driver only supports a
|
||||
* single chip since I do not have access to any boards with
|
||||
* multiple chips.
|
||||
*
|
||||
* Author: Thomas Gleixner
|
||||
*
|
||||
* Copyright 2006 IBM
|
||||
* Copyright 2008 PIKA Technologies
|
||||
* Sean MacLennan <smaclennan@pikatech.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/ndfc.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#define NDFC_MAX_CS 4
|
||||
|
||||
struct ndfc_controller {
|
||||
struct platform_device *ofdev;
|
||||
void __iomem *ndfcbase;
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
int chip_select;
|
||||
struct nand_hw_control ndfc_control;
|
||||
};
|
||||
|
||||
static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS];
|
||||
|
||||
static void ndfc_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
uint32_t ccr;
|
||||
struct nand_chip *nchip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = nchip->priv;
|
||||
|
||||
ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
|
||||
if (chip >= 0) {
|
||||
ccr &= ~NDFC_CCR_BS_MASK;
|
||||
ccr |= NDFC_CCR_BS(chip + ndfc->chip_select);
|
||||
} else
|
||||
ccr |= NDFC_CCR_RESET_CE;
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
}
|
||||
|
||||
static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD);
|
||||
else
|
||||
writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE);
|
||||
}
|
||||
|
||||
static int ndfc_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
|
||||
return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
|
||||
}
|
||||
|
||||
static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
uint32_t ccr;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
|
||||
ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
|
||||
ccr |= NDFC_CCR_RESET_ECC;
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
wmb();
|
||||
}
|
||||
|
||||
static int ndfc_calculate_ecc(struct mtd_info *mtd,
|
||||
const u_char *dat, u_char *ecc_code)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
uint32_t ecc;
|
||||
uint8_t *p = (uint8_t *)&ecc;
|
||||
|
||||
wmb();
|
||||
ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
|
||||
/* The NDFC uses Smart Media (SMC) bytes order */
|
||||
ecc_code[0] = p[1];
|
||||
ecc_code[1] = p[2];
|
||||
ecc_code[2] = p[3];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Speedups for buffer read/write/verify
|
||||
*
|
||||
* NDFC allows 32bit read/write of data. So we can speed up the buffer
|
||||
* functions. No further checking, as nand_base will always read/write
|
||||
* page aligned.
|
||||
*/
|
||||
static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
*p++ = in_be32(ndfc->ndfcbase + NDFC_DATA);
|
||||
}
|
||||
|
||||
static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
out_be32(ndfc->ndfcbase + NDFC_DATA, *p++);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize chip structure
|
||||
*/
|
||||
static int ndfc_chip_init(struct ndfc_controller *ndfc,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct device_node *flash_np;
|
||||
struct nand_chip *chip = &ndfc->chip;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
int ret;
|
||||
|
||||
chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA;
|
||||
chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA;
|
||||
chip->cmd_ctrl = ndfc_hwcontrol;
|
||||
chip->dev_ready = ndfc_ready;
|
||||
chip->select_chip = ndfc_select_chip;
|
||||
chip->chip_delay = 50;
|
||||
chip->controller = &ndfc->ndfc_control;
|
||||
chip->read_buf = ndfc_read_buf;
|
||||
chip->write_buf = ndfc_write_buf;
|
||||
chip->ecc.correct = nand_correct_data;
|
||||
chip->ecc.hwctl = ndfc_enable_hwecc;
|
||||
chip->ecc.calculate = ndfc_calculate_ecc;
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
chip->ecc.size = 256;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecc.strength = 1;
|
||||
chip->priv = ndfc;
|
||||
|
||||
ndfc->mtd.priv = chip;
|
||||
ndfc->mtd.owner = THIS_MODULE;
|
||||
|
||||
flash_np = of_get_next_child(node, NULL);
|
||||
if (!flash_np)
|
||||
return -ENODEV;
|
||||
|
||||
ppdata.of_node = flash_np;
|
||||
ndfc->mtd.name = kasprintf(GFP_KERNEL, "%s.%s",
|
||||
dev_name(&ndfc->ofdev->dev), flash_np->name);
|
||||
if (!ndfc->mtd.name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = nand_scan(&ndfc->mtd, 1);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = mtd_device_parse_register(&ndfc->mtd, NULL, &ppdata, NULL, 0);
|
||||
|
||||
err:
|
||||
of_node_put(flash_np);
|
||||
if (ret)
|
||||
kfree(ndfc->mtd.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ndfc_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct ndfc_controller *ndfc;
|
||||
const __be32 *reg;
|
||||
u32 ccr;
|
||||
u32 cs;
|
||||
int err, len;
|
||||
|
||||
/* Read the reg property to get the chip select */
|
||||
reg = of_get_property(ofdev->dev.of_node, "reg", &len);
|
||||
if (reg == NULL || len != 12) {
|
||||
dev_err(&ofdev->dev, "unable read reg property (%d)\n", len);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
cs = be32_to_cpu(reg[0]);
|
||||
if (cs >= NDFC_MAX_CS) {
|
||||
dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ndfc = &ndfc_ctrl[cs];
|
||||
ndfc->chip_select = cs;
|
||||
|
||||
spin_lock_init(&ndfc->ndfc_control.lock);
|
||||
init_waitqueue_head(&ndfc->ndfc_control.wq);
|
||||
ndfc->ofdev = ofdev;
|
||||
dev_set_drvdata(&ofdev->dev, ndfc);
|
||||
|
||||
ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0);
|
||||
if (!ndfc->ndfcbase) {
|
||||
dev_err(&ofdev->dev, "failed to get memory\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ccr = NDFC_CCR_BS(ndfc->chip_select);
|
||||
|
||||
/* It is ok if ccr does not exist - just default to 0 */
|
||||
reg = of_get_property(ofdev->dev.of_node, "ccr", NULL);
|
||||
if (reg)
|
||||
ccr |= be32_to_cpup(reg);
|
||||
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
|
||||
/* Set the bank settings if given */
|
||||
reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL);
|
||||
if (reg) {
|
||||
int offset = NDFC_BCFG0 + (ndfc->chip_select << 2);
|
||||
out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg));
|
||||
}
|
||||
|
||||
err = ndfc_chip_init(ndfc, ofdev->dev.of_node);
|
||||
if (err) {
|
||||
iounmap(ndfc->ndfcbase);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndfc_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev);
|
||||
|
||||
nand_release(&ndfc->mtd);
|
||||
kfree(ndfc->mtd.name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ndfc_match[] = {
|
||||
{ .compatible = "ibm,ndfc", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ndfc_match);
|
||||
|
||||
static struct platform_driver ndfc_driver = {
|
||||
.driver = {
|
||||
.name = "ndfc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ndfc_match,
|
||||
},
|
||||
.probe = ndfc_probe,
|
||||
.remove = ndfc_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ndfc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
|
||||
MODULE_DESCRIPTION("OF Platform driver for NDFC");
|
||||
312
drivers/mtd/nand/nuc900_nand.c
Normal file
312
drivers/mtd/nand/nuc900_nand.c
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright © 2009 Nuvoton technology corporation.
|
||||
*
|
||||
* Wan ZongShun <mcuos.com@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation;version 2 of the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#define REG_FMICSR 0x00
|
||||
#define REG_SMCSR 0xa0
|
||||
#define REG_SMISR 0xac
|
||||
#define REG_SMCMD 0xb0
|
||||
#define REG_SMADDR 0xb4
|
||||
#define REG_SMDATA 0xb8
|
||||
|
||||
#define RESET_FMI 0x01
|
||||
#define NAND_EN 0x08
|
||||
#define READYBUSY (0x01 << 18)
|
||||
|
||||
#define SWRST 0x01
|
||||
#define PSIZE (0x01 << 3)
|
||||
#define DMARWEN (0x03 << 1)
|
||||
#define BUSWID (0x01 << 4)
|
||||
#define ECC4EN (0x01 << 5)
|
||||
#define WP (0x01 << 24)
|
||||
#define NANDCS (0x01 << 25)
|
||||
#define ENDADDR (0x01 << 31)
|
||||
|
||||
#define read_data_reg(dev) \
|
||||
__raw_readl((dev)->reg + REG_SMDATA)
|
||||
|
||||
#define write_data_reg(dev, val) \
|
||||
__raw_writel((val), (dev)->reg + REG_SMDATA)
|
||||
|
||||
#define write_cmd_reg(dev, val) \
|
||||
__raw_writel((val), (dev)->reg + REG_SMCMD)
|
||||
|
||||
#define write_addr_reg(dev, val) \
|
||||
__raw_writel((val), (dev)->reg + REG_SMADDR)
|
||||
|
||||
struct nuc900_nand {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
void __iomem *reg;
|
||||
struct clk *clk;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static const struct mtd_partition partitions[] = {
|
||||
{
|
||||
.name = "NAND FS 0",
|
||||
.offset = 0,
|
||||
.size = 8 * 1024 * 1024
|
||||
},
|
||||
{
|
||||
.name = "NAND FS 1",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL
|
||||
}
|
||||
};
|
||||
|
||||
static unsigned char nuc900_nand_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
unsigned char ret;
|
||||
struct nuc900_nand *nand;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
ret = (unsigned char)read_data_reg(nand);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void nuc900_nand_read_buf(struct mtd_info *mtd,
|
||||
unsigned char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nuc900_nand *nand;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = (unsigned char)read_data_reg(nand);
|
||||
}
|
||||
|
||||
static void nuc900_nand_write_buf(struct mtd_info *mtd,
|
||||
const unsigned char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nuc900_nand *nand;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
write_data_reg(nand, buf[i]);
|
||||
}
|
||||
|
||||
static int nuc900_check_rb(struct nuc900_nand *nand)
|
||||
{
|
||||
unsigned int val;
|
||||
spin_lock(&nand->lock);
|
||||
val = __raw_readl(REG_SMISR);
|
||||
val &= READYBUSY;
|
||||
spin_unlock(&nand->lock);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int nuc900_nand_devready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nuc900_nand *nand;
|
||||
int ready;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
ready = (nuc900_check_rb(nand)) ? 1 : 0;
|
||||
return ready;
|
||||
}
|
||||
|
||||
static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command,
|
||||
int column, int page_addr)
|
||||
{
|
||||
register struct nand_chip *chip = mtd->priv;
|
||||
struct nuc900_nand *nand;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
if (command == NAND_CMD_READOOB) {
|
||||
column += mtd->writesize;
|
||||
command = NAND_CMD_READ0;
|
||||
}
|
||||
|
||||
write_cmd_reg(nand, command & 0xff);
|
||||
|
||||
if (column != -1 || page_addr != -1) {
|
||||
|
||||
if (column != -1) {
|
||||
if (chip->options & NAND_BUSWIDTH_16 &&
|
||||
!nand_opcode_8bits(command))
|
||||
column >>= 1;
|
||||
write_addr_reg(nand, column);
|
||||
write_addr_reg(nand, column >> 8 | ENDADDR);
|
||||
}
|
||||
if (page_addr != -1) {
|
||||
write_addr_reg(nand, page_addr);
|
||||
|
||||
if (chip->chipsize > (128 << 20)) {
|
||||
write_addr_reg(nand, page_addr >> 8);
|
||||
write_addr_reg(nand, page_addr >> 16 | ENDADDR);
|
||||
} else {
|
||||
write_addr_reg(nand, page_addr >> 8 | ENDADDR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case NAND_CMD_CACHEDPROG:
|
||||
case NAND_CMD_PAGEPROG:
|
||||
case NAND_CMD_ERASE1:
|
||||
case NAND_CMD_ERASE2:
|
||||
case NAND_CMD_SEQIN:
|
||||
case NAND_CMD_RNDIN:
|
||||
case NAND_CMD_STATUS:
|
||||
return;
|
||||
|
||||
case NAND_CMD_RESET:
|
||||
if (chip->dev_ready)
|
||||
break;
|
||||
udelay(chip->chip_delay);
|
||||
|
||||
write_cmd_reg(nand, NAND_CMD_STATUS);
|
||||
write_cmd_reg(nand, command);
|
||||
|
||||
while (!nuc900_check_rb(nand))
|
||||
;
|
||||
|
||||
return;
|
||||
|
||||
case NAND_CMD_RNDOUT:
|
||||
write_cmd_reg(nand, NAND_CMD_RNDOUTSTART);
|
||||
return;
|
||||
|
||||
case NAND_CMD_READ0:
|
||||
|
||||
write_cmd_reg(nand, NAND_CMD_READSTART);
|
||||
default:
|
||||
|
||||
if (!chip->dev_ready) {
|
||||
udelay(chip->chip_delay);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply this short delay always to ensure that we do wait tWB in
|
||||
* any case on any machine. */
|
||||
ndelay(100);
|
||||
|
||||
while (!chip->dev_ready(mtd))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
static void nuc900_nand_enable(struct nuc900_nand *nand)
|
||||
{
|
||||
unsigned int val;
|
||||
spin_lock(&nand->lock);
|
||||
__raw_writel(RESET_FMI, (nand->reg + REG_FMICSR));
|
||||
|
||||
val = __raw_readl(nand->reg + REG_FMICSR);
|
||||
|
||||
if (!(val & NAND_EN))
|
||||
__raw_writel(val | NAND_EN, nand->reg + REG_FMICSR);
|
||||
|
||||
val = __raw_readl(nand->reg + REG_SMCSR);
|
||||
|
||||
val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS);
|
||||
val |= WP;
|
||||
|
||||
__raw_writel(val, nand->reg + REG_SMCSR);
|
||||
|
||||
spin_unlock(&nand->lock);
|
||||
}
|
||||
|
||||
static int nuc900_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nuc900_nand *nuc900_nand;
|
||||
struct nand_chip *chip;
|
||||
struct resource *res;
|
||||
|
||||
nuc900_nand = devm_kzalloc(&pdev->dev, sizeof(struct nuc900_nand),
|
||||
GFP_KERNEL);
|
||||
if (!nuc900_nand)
|
||||
return -ENOMEM;
|
||||
chip = &(nuc900_nand->chip);
|
||||
|
||||
nuc900_nand->mtd.priv = chip;
|
||||
nuc900_nand->mtd.owner = THIS_MODULE;
|
||||
spin_lock_init(&nuc900_nand->lock);
|
||||
|
||||
nuc900_nand->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(nuc900_nand->clk))
|
||||
return -ENOENT;
|
||||
clk_enable(nuc900_nand->clk);
|
||||
|
||||
chip->cmdfunc = nuc900_nand_command_lp;
|
||||
chip->dev_ready = nuc900_nand_devready;
|
||||
chip->read_byte = nuc900_nand_read_byte;
|
||||
chip->write_buf = nuc900_nand_write_buf;
|
||||
chip->read_buf = nuc900_nand_read_buf;
|
||||
chip->chip_delay = 50;
|
||||
chip->options = 0;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
nuc900_nand->reg = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(nuc900_nand->reg))
|
||||
return PTR_ERR(nuc900_nand->reg);
|
||||
|
||||
nuc900_nand_enable(nuc900_nand);
|
||||
|
||||
if (nand_scan(&(nuc900_nand->mtd), 1))
|
||||
return -ENXIO;
|
||||
|
||||
mtd_device_register(&(nuc900_nand->mtd), partitions,
|
||||
ARRAY_SIZE(partitions));
|
||||
|
||||
platform_set_drvdata(pdev, nuc900_nand);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev);
|
||||
|
||||
nand_release(&nuc900_nand->mtd);
|
||||
clk_disable(nuc900_nand->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver nuc900_nand_driver = {
|
||||
.probe = nuc900_nand_probe,
|
||||
.remove = nuc900_nand_remove,
|
||||
.driver = {
|
||||
.name = "nuc900-fmi",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(nuc900_nand_driver);
|
||||
|
||||
MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
|
||||
MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:nuc900-fmi");
|
||||
2099
drivers/mtd/nand/omap2.c
Normal file
2099
drivers/mtd/nand/omap2.c
Normal file
File diff suppressed because it is too large
Load diff
579
drivers/mtd/nand/omap_elm.c
Normal file
579
drivers/mtd/nand/omap_elm.c
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
/*
|
||||
* Error Location Module
|
||||
*
|
||||
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#define DRIVER_NAME "omap-elm"
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/platform_data/elm.h>
|
||||
|
||||
#define ELM_SYSCONFIG 0x010
|
||||
#define ELM_IRQSTATUS 0x018
|
||||
#define ELM_IRQENABLE 0x01c
|
||||
#define ELM_LOCATION_CONFIG 0x020
|
||||
#define ELM_PAGE_CTRL 0x080
|
||||
#define ELM_SYNDROME_FRAGMENT_0 0x400
|
||||
#define ELM_SYNDROME_FRAGMENT_1 0x404
|
||||
#define ELM_SYNDROME_FRAGMENT_2 0x408
|
||||
#define ELM_SYNDROME_FRAGMENT_3 0x40c
|
||||
#define ELM_SYNDROME_FRAGMENT_4 0x410
|
||||
#define ELM_SYNDROME_FRAGMENT_5 0x414
|
||||
#define ELM_SYNDROME_FRAGMENT_6 0x418
|
||||
#define ELM_LOCATION_STATUS 0x800
|
||||
#define ELM_ERROR_LOCATION_0 0x880
|
||||
|
||||
/* ELM Interrupt Status Register */
|
||||
#define INTR_STATUS_PAGE_VALID BIT(8)
|
||||
|
||||
/* ELM Interrupt Enable Register */
|
||||
#define INTR_EN_PAGE_MASK BIT(8)
|
||||
|
||||
/* ELM Location Configuration Register */
|
||||
#define ECC_BCH_LEVEL_MASK 0x3
|
||||
|
||||
/* ELM syndrome */
|
||||
#define ELM_SYNDROME_VALID BIT(16)
|
||||
|
||||
/* ELM_LOCATION_STATUS Register */
|
||||
#define ECC_CORRECTABLE_MASK BIT(8)
|
||||
#define ECC_NB_ERRORS_MASK 0x1f
|
||||
|
||||
/* ELM_ERROR_LOCATION_0-15 Registers */
|
||||
#define ECC_ERROR_LOCATION_MASK 0x1fff
|
||||
|
||||
#define ELM_ECC_SIZE 0x7ff
|
||||
|
||||
#define SYNDROME_FRAGMENT_REG_SIZE 0x40
|
||||
#define ERROR_LOCATION_SIZE 0x100
|
||||
|
||||
struct elm_registers {
|
||||
u32 elm_irqenable;
|
||||
u32 elm_sysconfig;
|
||||
u32 elm_location_config;
|
||||
u32 elm_page_ctrl;
|
||||
u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX];
|
||||
u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX];
|
||||
};
|
||||
|
||||
struct elm_info {
|
||||
struct device *dev;
|
||||
void __iomem *elm_base;
|
||||
struct completion elm_completion;
|
||||
struct list_head list;
|
||||
enum bch_ecc bch_type;
|
||||
struct elm_registers elm_regs;
|
||||
int ecc_steps;
|
||||
int ecc_syndrome_size;
|
||||
};
|
||||
|
||||
static LIST_HEAD(elm_devices);
|
||||
|
||||
static void elm_write_reg(struct elm_info *info, int offset, u32 val)
|
||||
{
|
||||
writel(val, info->elm_base + offset);
|
||||
}
|
||||
|
||||
static u32 elm_read_reg(struct elm_info *info, int offset)
|
||||
{
|
||||
return readl(info->elm_base + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_config - Configure ELM module
|
||||
* @dev: ELM device
|
||||
* @bch_type: Type of BCH ecc
|
||||
*/
|
||||
int elm_config(struct device *dev, enum bch_ecc bch_type,
|
||||
int ecc_steps, int ecc_step_size, int ecc_syndrome_size)
|
||||
{
|
||||
u32 reg_val;
|
||||
struct elm_info *info = dev_get_drvdata(dev);
|
||||
|
||||
if (!info) {
|
||||
dev_err(dev, "Unable to configure elm - device not probed?\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
/* ELM cannot detect ECC errors for chunks > 1KB */
|
||||
if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) {
|
||||
dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* ELM support 8 error syndrome process */
|
||||
if (ecc_steps > ERROR_VECTOR_MAX) {
|
||||
dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16);
|
||||
elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val);
|
||||
info->bch_type = bch_type;
|
||||
info->ecc_steps = ecc_steps;
|
||||
info->ecc_syndrome_size = ecc_syndrome_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(elm_config);
|
||||
|
||||
/**
|
||||
* elm_configure_page_mode - Enable/Disable page mode
|
||||
* @info: elm info
|
||||
* @index: index number of syndrome fragment vector
|
||||
* @enable: enable/disable flag for page mode
|
||||
*
|
||||
* Enable page mode for syndrome fragment index
|
||||
*/
|
||||
static void elm_configure_page_mode(struct elm_info *info, int index,
|
||||
bool enable)
|
||||
{
|
||||
u32 reg_val;
|
||||
|
||||
reg_val = elm_read_reg(info, ELM_PAGE_CTRL);
|
||||
if (enable)
|
||||
reg_val |= BIT(index); /* enable page mode */
|
||||
else
|
||||
reg_val &= ~BIT(index); /* disable page mode */
|
||||
|
||||
elm_write_reg(info, ELM_PAGE_CTRL, reg_val);
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_load_syndrome - Load ELM syndrome reg
|
||||
* @info: elm info
|
||||
* @err_vec: elm error vectors
|
||||
* @ecc: buffer with calculated ecc
|
||||
*
|
||||
* Load syndrome fragment registers with calculated ecc in reverse order.
|
||||
*/
|
||||
static void elm_load_syndrome(struct elm_info *info,
|
||||
struct elm_errorvec *err_vec, u8 *ecc)
|
||||
{
|
||||
int i, offset;
|
||||
u32 val;
|
||||
|
||||
for (i = 0; i < info->ecc_steps; i++) {
|
||||
|
||||
/* Check error reported */
|
||||
if (err_vec[i].error_reported) {
|
||||
elm_configure_page_mode(info, i, true);
|
||||
offset = ELM_SYNDROME_FRAGMENT_0 +
|
||||
SYNDROME_FRAGMENT_REG_SIZE * i;
|
||||
switch (info->bch_type) {
|
||||
case BCH8_ECC:
|
||||
/* syndrome fragment 0 = ecc[9-12B] */
|
||||
val = cpu_to_be32(*(u32 *) &ecc[9]);
|
||||
elm_write_reg(info, offset, val);
|
||||
|
||||
/* syndrome fragment 1 = ecc[5-8B] */
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[5]);
|
||||
elm_write_reg(info, offset, val);
|
||||
|
||||
/* syndrome fragment 2 = ecc[1-4B] */
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[1]);
|
||||
elm_write_reg(info, offset, val);
|
||||
|
||||
/* syndrome fragment 3 = ecc[0B] */
|
||||
offset += 4;
|
||||
val = ecc[0];
|
||||
elm_write_reg(info, offset, val);
|
||||
break;
|
||||
case BCH4_ECC:
|
||||
/* syndrome fragment 0 = ecc[20-52b] bits */
|
||||
val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) |
|
||||
((ecc[2] & 0xf) << 28);
|
||||
elm_write_reg(info, offset, val);
|
||||
|
||||
/* syndrome fragment 1 = ecc[0-20b] bits */
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12;
|
||||
elm_write_reg(info, offset, val);
|
||||
break;
|
||||
case BCH16_ECC:
|
||||
val = cpu_to_be32(*(u32 *) &ecc[22]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[18]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[14]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[10]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[6]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[2]);
|
||||
elm_write_reg(info, offset, val);
|
||||
offset += 4;
|
||||
val = cpu_to_be32(*(u32 *) &ecc[0]) >> 16;
|
||||
elm_write_reg(info, offset, val);
|
||||
break;
|
||||
default:
|
||||
pr_err("invalid config bch_type\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Update ecc pointer with ecc byte size */
|
||||
ecc += info->ecc_syndrome_size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_start_processing - start elm syndrome processing
|
||||
* @info: elm info
|
||||
* @err_vec: elm error vectors
|
||||
*
|
||||
* Set syndrome valid bit for syndrome fragment registers for which
|
||||
* elm syndrome fragment registers are loaded. This enables elm module
|
||||
* to start processing syndrome vectors.
|
||||
*/
|
||||
static void elm_start_processing(struct elm_info *info,
|
||||
struct elm_errorvec *err_vec)
|
||||
{
|
||||
int i, offset;
|
||||
u32 reg_val;
|
||||
|
||||
/*
|
||||
* Set syndrome vector valid, so that ELM module
|
||||
* will process it for vectors error is reported
|
||||
*/
|
||||
for (i = 0; i < info->ecc_steps; i++) {
|
||||
if (err_vec[i].error_reported) {
|
||||
offset = ELM_SYNDROME_FRAGMENT_6 +
|
||||
SYNDROME_FRAGMENT_REG_SIZE * i;
|
||||
reg_val = elm_read_reg(info, offset);
|
||||
reg_val |= ELM_SYNDROME_VALID;
|
||||
elm_write_reg(info, offset, reg_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_error_correction - locate correctable error position
|
||||
* @info: elm info
|
||||
* @err_vec: elm error vectors
|
||||
*
|
||||
* On completion of processing by elm module, error location status
|
||||
* register updated with correctable/uncorrectable error information.
|
||||
* In case of correctable errors, number of errors located from
|
||||
* elm location status register & read the positions from
|
||||
* elm error location register.
|
||||
*/
|
||||
static void elm_error_correction(struct elm_info *info,
|
||||
struct elm_errorvec *err_vec)
|
||||
{
|
||||
int i, j, errors = 0;
|
||||
int offset;
|
||||
u32 reg_val;
|
||||
|
||||
for (i = 0; i < info->ecc_steps; i++) {
|
||||
|
||||
/* Check error reported */
|
||||
if (err_vec[i].error_reported) {
|
||||
offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i;
|
||||
reg_val = elm_read_reg(info, offset);
|
||||
|
||||
/* Check correctable error or not */
|
||||
if (reg_val & ECC_CORRECTABLE_MASK) {
|
||||
offset = ELM_ERROR_LOCATION_0 +
|
||||
ERROR_LOCATION_SIZE * i;
|
||||
|
||||
/* Read count of correctable errors */
|
||||
err_vec[i].error_count = reg_val &
|
||||
ECC_NB_ERRORS_MASK;
|
||||
|
||||
/* Update the error locations in error vector */
|
||||
for (j = 0; j < err_vec[i].error_count; j++) {
|
||||
|
||||
reg_val = elm_read_reg(info, offset);
|
||||
err_vec[i].error_loc[j] = reg_val &
|
||||
ECC_ERROR_LOCATION_MASK;
|
||||
|
||||
/* Update error location register */
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
errors += err_vec[i].error_count;
|
||||
} else {
|
||||
err_vec[i].error_uncorrectable = true;
|
||||
}
|
||||
|
||||
/* Clearing interrupts for processed error vectors */
|
||||
elm_write_reg(info, ELM_IRQSTATUS, BIT(i));
|
||||
|
||||
/* Disable page mode */
|
||||
elm_configure_page_mode(info, i, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_decode_bch_error_page - Locate error position
|
||||
* @dev: device pointer
|
||||
* @ecc_calc: calculated ECC bytes from GPMC
|
||||
* @err_vec: elm error vectors
|
||||
*
|
||||
* Called with one or more error reported vectors & vectors with
|
||||
* error reported is updated in err_vec[].error_reported
|
||||
*/
|
||||
void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
|
||||
struct elm_errorvec *err_vec)
|
||||
{
|
||||
struct elm_info *info = dev_get_drvdata(dev);
|
||||
u32 reg_val;
|
||||
|
||||
/* Enable page mode interrupt */
|
||||
reg_val = elm_read_reg(info, ELM_IRQSTATUS);
|
||||
elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);
|
||||
elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK);
|
||||
|
||||
/* Load valid ecc byte to syndrome fragment register */
|
||||
elm_load_syndrome(info, err_vec, ecc_calc);
|
||||
|
||||
/* Enable syndrome processing for which syndrome fragment is updated */
|
||||
elm_start_processing(info, err_vec);
|
||||
|
||||
/* Wait for ELM module to finish locating error correction */
|
||||
wait_for_completion(&info->elm_completion);
|
||||
|
||||
/* Disable page mode interrupt */
|
||||
reg_val = elm_read_reg(info, ELM_IRQENABLE);
|
||||
elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);
|
||||
elm_error_correction(info, err_vec);
|
||||
}
|
||||
EXPORT_SYMBOL(elm_decode_bch_error_page);
|
||||
|
||||
static irqreturn_t elm_isr(int this_irq, void *dev_id)
|
||||
{
|
||||
u32 reg_val;
|
||||
struct elm_info *info = dev_id;
|
||||
|
||||
reg_val = elm_read_reg(info, ELM_IRQSTATUS);
|
||||
|
||||
/* All error vectors processed */
|
||||
if (reg_val & INTR_STATUS_PAGE_VALID) {
|
||||
elm_write_reg(info, ELM_IRQSTATUS,
|
||||
reg_val & INTR_STATUS_PAGE_VALID);
|
||||
complete(&info->elm_completion);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static int elm_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct resource *res, *irq;
|
||||
struct elm_info *info;
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->dev = &pdev->dev;
|
||||
|
||||
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (!irq) {
|
||||
dev_err(&pdev->dev, "no irq resource defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
info->elm_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(info->elm_base))
|
||||
return PTR_ERR(info->elm_base);
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0,
|
||||
pdev->name, info);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failure requesting irq %i\n", irq->start);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
if (pm_runtime_get_sync(&pdev->dev) < 0) {
|
||||
ret = -EINVAL;
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
dev_err(&pdev->dev, "can't enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
init_completion(&info->elm_completion);
|
||||
INIT_LIST_HEAD(&info->list);
|
||||
list_add(&info->list, &elm_devices);
|
||||
platform_set_drvdata(pdev, info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int elm_remove(struct platform_device *pdev)
|
||||
{
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/**
|
||||
* elm_context_save
|
||||
* saves ELM configurations to preserve them across Hardware powered-down
|
||||
*/
|
||||
static int elm_context_save(struct elm_info *info)
|
||||
{
|
||||
struct elm_registers *regs = &info->elm_regs;
|
||||
enum bch_ecc bch_type = info->bch_type;
|
||||
u32 offset = 0, i;
|
||||
|
||||
regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE);
|
||||
regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG);
|
||||
regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG);
|
||||
regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL);
|
||||
for (i = 0; i < ERROR_VECTOR_MAX; i++) {
|
||||
offset = i * SYNDROME_FRAGMENT_REG_SIZE;
|
||||
switch (bch_type) {
|
||||
case BCH16_ECC:
|
||||
regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_6 + offset);
|
||||
regs->elm_syndrome_fragment_5[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_5 + offset);
|
||||
regs->elm_syndrome_fragment_4[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_4 + offset);
|
||||
case BCH8_ECC:
|
||||
regs->elm_syndrome_fragment_3[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_3 + offset);
|
||||
regs->elm_syndrome_fragment_2[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_2 + offset);
|
||||
case BCH4_ECC:
|
||||
regs->elm_syndrome_fragment_1[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_1 + offset);
|
||||
regs->elm_syndrome_fragment_0[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_0 + offset);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
/* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs
|
||||
* to be saved for all BCH schemes*/
|
||||
regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_6 + offset);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* elm_context_restore
|
||||
* writes configurations saved duing power-down back into ELM registers
|
||||
*/
|
||||
static int elm_context_restore(struct elm_info *info)
|
||||
{
|
||||
struct elm_registers *regs = &info->elm_regs;
|
||||
enum bch_ecc bch_type = info->bch_type;
|
||||
u32 offset = 0, i;
|
||||
|
||||
elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable);
|
||||
elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig);
|
||||
elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config);
|
||||
elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl);
|
||||
for (i = 0; i < ERROR_VECTOR_MAX; i++) {
|
||||
offset = i * SYNDROME_FRAGMENT_REG_SIZE;
|
||||
switch (bch_type) {
|
||||
case BCH16_ECC:
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
|
||||
regs->elm_syndrome_fragment_6[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset,
|
||||
regs->elm_syndrome_fragment_5[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset,
|
||||
regs->elm_syndrome_fragment_4[i]);
|
||||
case BCH8_ECC:
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset,
|
||||
regs->elm_syndrome_fragment_3[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset,
|
||||
regs->elm_syndrome_fragment_2[i]);
|
||||
case BCH4_ECC:
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset,
|
||||
regs->elm_syndrome_fragment_1[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset,
|
||||
regs->elm_syndrome_fragment_0[i]);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
/* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
|
||||
regs->elm_syndrome_fragment_6[i] &
|
||||
ELM_SYNDROME_VALID);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int elm_suspend(struct device *dev)
|
||||
{
|
||||
struct elm_info *info = dev_get_drvdata(dev);
|
||||
elm_context_save(info);
|
||||
pm_runtime_put_sync(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int elm_resume(struct device *dev)
|
||||
{
|
||||
struct elm_info *info = dev_get_drvdata(dev);
|
||||
pm_runtime_get_sync(dev);
|
||||
elm_context_restore(info);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id elm_of_match[] = {
|
||||
{ .compatible = "ti,am3352-elm" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, elm_of_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver elm_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(elm_of_match),
|
||||
.pm = &elm_pm_ops,
|
||||
},
|
||||
.probe = elm_probe,
|
||||
.remove = elm_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(elm_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ELM driver for BCH error correction");
|
||||
MODULE_AUTHOR("Texas Instruments");
|
||||
MODULE_ALIAS("platform: elm");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
237
drivers/mtd/nand/orion_nand.c
Normal file
237
drivers/mtd/nand/orion_nand.c
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* drivers/mtd/nand/orion_nand.c
|
||||
*
|
||||
* NAND support for Marvell Orion SoC platforms
|
||||
*
|
||||
* Tzachi Perelstein <tzachi@marvell.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
#include <linux/platform_data/mtd-orion_nand.h>
|
||||
|
||||
static void orion_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *nc = mtd->priv;
|
||||
struct orion_nand_data *board = nc->priv;
|
||||
u32 offs;
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
offs = (1 << board->cle);
|
||||
else if (ctrl & NAND_ALE)
|
||||
offs = (1 << board->ale);
|
||||
else
|
||||
return;
|
||||
|
||||
if (nc->options & NAND_BUSWIDTH_16)
|
||||
offs <<= 1;
|
||||
|
||||
writeb(cmd, nc->IO_ADDR_W + offs);
|
||||
}
|
||||
|
||||
static void orion_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
void __iomem *io_base = chip->IO_ADDR_R;
|
||||
uint64_t *buf64;
|
||||
int i = 0;
|
||||
|
||||
while (len && (unsigned long)buf & 7) {
|
||||
*buf++ = readb(io_base);
|
||||
len--;
|
||||
}
|
||||
buf64 = (uint64_t *)buf;
|
||||
while (i < len/8) {
|
||||
/*
|
||||
* Since GCC has no proper constraint (PR 43518)
|
||||
* force x variable to r2/r3 registers as ldrd instruction
|
||||
* requires first register to be even.
|
||||
*/
|
||||
register uint64_t x asm ("r2");
|
||||
|
||||
asm volatile ("ldrd\t%0, [%1]" : "=&r" (x) : "r" (io_base));
|
||||
buf64[i++] = x;
|
||||
}
|
||||
i *= 8;
|
||||
while (i < len)
|
||||
buf[i++] = readb(io_base);
|
||||
}
|
||||
|
||||
static int __init orion_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
struct mtd_part_parser_data ppdata = {};
|
||||
struct nand_chip *nc;
|
||||
struct orion_nand_data *board;
|
||||
struct resource *res;
|
||||
struct clk *clk;
|
||||
void __iomem *io_base;
|
||||
int ret = 0;
|
||||
u32 val = 0;
|
||||
|
||||
nc = kzalloc(sizeof(struct nand_chip) + sizeof(struct mtd_info), GFP_KERNEL);
|
||||
if (!nc) {
|
||||
ret = -ENOMEM;
|
||||
goto no_res;
|
||||
}
|
||||
mtd = (struct mtd_info *)(nc + 1);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
ret = -ENODEV;
|
||||
goto no_res;
|
||||
}
|
||||
|
||||
io_base = ioremap(res->start, resource_size(res));
|
||||
if (!io_base) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
ret = -EIO;
|
||||
goto no_res;
|
||||
}
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
board = devm_kzalloc(&pdev->dev, sizeof(struct orion_nand_data),
|
||||
GFP_KERNEL);
|
||||
if (!board) {
|
||||
ret = -ENOMEM;
|
||||
goto no_res;
|
||||
}
|
||||
if (!of_property_read_u32(pdev->dev.of_node, "cle", &val))
|
||||
board->cle = (u8)val;
|
||||
else
|
||||
board->cle = 0;
|
||||
if (!of_property_read_u32(pdev->dev.of_node, "ale", &val))
|
||||
board->ale = (u8)val;
|
||||
else
|
||||
board->ale = 1;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"bank-width", &val))
|
||||
board->width = (u8)val * 8;
|
||||
else
|
||||
board->width = 8;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"chip-delay", &val))
|
||||
board->chip_delay = (u8)val;
|
||||
} else {
|
||||
board = dev_get_platdata(&pdev->dev);
|
||||
}
|
||||
|
||||
mtd->priv = nc;
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
nc->priv = board;
|
||||
nc->IO_ADDR_R = nc->IO_ADDR_W = io_base;
|
||||
nc->cmd_ctrl = orion_nand_cmd_ctrl;
|
||||
nc->read_buf = orion_nand_read_buf;
|
||||
nc->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
if (board->chip_delay)
|
||||
nc->chip_delay = board->chip_delay;
|
||||
|
||||
WARN(board->width > 16,
|
||||
"%d bit bus width out of range",
|
||||
board->width);
|
||||
|
||||
if (board->width == 16)
|
||||
nc->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
if (board->dev_ready)
|
||||
nc->dev_ready = board->dev_ready;
|
||||
|
||||
platform_set_drvdata(pdev, mtd);
|
||||
|
||||
/* Not all platforms can gate the clock, so it is not
|
||||
an error if the clock does not exists. */
|
||||
clk = clk_get(&pdev->dev, NULL);
|
||||
if (!IS_ERR(clk)) {
|
||||
clk_prepare_enable(clk);
|
||||
clk_put(clk);
|
||||
}
|
||||
|
||||
if (nand_scan(mtd, 1)) {
|
||||
ret = -ENXIO;
|
||||
goto no_dev;
|
||||
}
|
||||
|
||||
mtd->name = "orion_nand";
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
ret = mtd_device_parse_register(mtd, NULL, &ppdata,
|
||||
board->parts, board->nr_parts);
|
||||
if (ret) {
|
||||
nand_release(mtd);
|
||||
goto no_dev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
no_dev:
|
||||
if (!IS_ERR(clk)) {
|
||||
clk_disable_unprepare(clk);
|
||||
clk_put(clk);
|
||||
}
|
||||
iounmap(io_base);
|
||||
no_res:
|
||||
kfree(nc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int orion_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtd_info *mtd = platform_get_drvdata(pdev);
|
||||
struct nand_chip *nc = mtd->priv;
|
||||
struct clk *clk;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
iounmap(nc->IO_ADDR_W);
|
||||
|
||||
kfree(nc);
|
||||
|
||||
clk = clk_get(&pdev->dev, NULL);
|
||||
if (!IS_ERR(clk)) {
|
||||
clk_disable_unprepare(clk);
|
||||
clk_put(clk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id orion_nand_of_match_table[] = {
|
||||
{ .compatible = "marvell,orion-nand", },
|
||||
{},
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver orion_nand_driver = {
|
||||
.remove = orion_nand_remove,
|
||||
.driver = {
|
||||
.name = "orion_nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(orion_nand_of_match_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver_probe(orion_nand_driver, orion_nand_probe);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Tzachi Perelstein");
|
||||
MODULE_DESCRIPTION("NAND glue for Orion platforms");
|
||||
MODULE_ALIAS("platform:orion_nand");
|
||||
237
drivers/mtd/nand/pasemi_nand.c
Normal file
237
drivers/mtd/nand/pasemi_nand.c
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (C) 2006-2007 PA Semi, Inc
|
||||
*
|
||||
* Author: Egor Martovetsky <egor@pasemi.com>
|
||||
* Maintained by: Olof Johansson <olof@lixom.net>
|
||||
*
|
||||
* Driver for the PWRficient onchip NAND flash interface
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
#define LBICTRL_LPCCTL_NR 0x00004000
|
||||
#define CLE_PIN_CTL 15
|
||||
#define ALE_PIN_CTL 14
|
||||
|
||||
static unsigned int lpcctl;
|
||||
static struct mtd_info *pasemi_nand_mtd;
|
||||
static const char driver_name[] = "pasemi-nand";
|
||||
|
||||
static void pasemi_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
while (len > 0x800) {
|
||||
memcpy_fromio(buf, chip->IO_ADDR_R, 0x800);
|
||||
buf += 0x800;
|
||||
len -= 0x800;
|
||||
}
|
||||
memcpy_fromio(buf, chip->IO_ADDR_R, len);
|
||||
}
|
||||
|
||||
static void pasemi_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
while (len > 0x800) {
|
||||
memcpy_toio(chip->IO_ADDR_R, buf, 0x800);
|
||||
buf += 0x800;
|
||||
len -= 0x800;
|
||||
}
|
||||
memcpy_toio(chip->IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
static void pasemi_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
out_8(chip->IO_ADDR_W + (1 << CLE_PIN_CTL), cmd);
|
||||
else
|
||||
out_8(chip->IO_ADDR_W + (1 << ALE_PIN_CTL), cmd);
|
||||
|
||||
/* Push out posted writes */
|
||||
eieio();
|
||||
inl(lpcctl);
|
||||
}
|
||||
|
||||
int pasemi_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return !!(inl(lpcctl) & LBICTRL_LPCCTL_NR);
|
||||
}
|
||||
|
||||
static int pasemi_nand_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct pci_dev *pdev;
|
||||
struct device_node *np = ofdev->dev.of_node;
|
||||
struct resource res;
|
||||
struct nand_chip *chip;
|
||||
int err = 0;
|
||||
|
||||
err = of_address_to_resource(np, 0, &res);
|
||||
|
||||
if (err)
|
||||
return -EINVAL;
|
||||
|
||||
/* We only support one device at the moment */
|
||||
if (pasemi_nand_mtd)
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug("pasemi_nand at %pR\n", &res);
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
pasemi_nand_mtd = kzalloc(sizeof(struct mtd_info) +
|
||||
sizeof(struct nand_chip), GFP_KERNEL);
|
||||
if (!pasemi_nand_mtd) {
|
||||
printk(KERN_WARNING
|
||||
"Unable to allocate PASEMI NAND MTD device structure\n");
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
chip = (struct nand_chip *)&pasemi_nand_mtd[1];
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
pasemi_nand_mtd->priv = chip;
|
||||
pasemi_nand_mtd->owner = THIS_MODULE;
|
||||
|
||||
chip->IO_ADDR_R = of_iomap(np, 0);
|
||||
chip->IO_ADDR_W = chip->IO_ADDR_R;
|
||||
|
||||
if (!chip->IO_ADDR_R) {
|
||||
err = -EIO;
|
||||
goto out_mtd;
|
||||
}
|
||||
|
||||
pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL);
|
||||
if (!pdev) {
|
||||
err = -ENODEV;
|
||||
goto out_ior;
|
||||
}
|
||||
|
||||
lpcctl = pci_resource_start(pdev, 0);
|
||||
pci_dev_put(pdev);
|
||||
|
||||
if (!request_region(lpcctl, 4, driver_name)) {
|
||||
err = -EBUSY;
|
||||
goto out_ior;
|
||||
}
|
||||
|
||||
chip->cmd_ctrl = pasemi_hwcontrol;
|
||||
chip->dev_ready = pasemi_device_ready;
|
||||
chip->read_buf = pasemi_read_buf;
|
||||
chip->write_buf = pasemi_write_buf;
|
||||
chip->chip_delay = 0;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
/* Enable the following for a flash based bad block table */
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan(pasemi_nand_mtd, 1)) {
|
||||
err = -ENXIO;
|
||||
goto out_lpc;
|
||||
}
|
||||
|
||||
if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) {
|
||||
printk(KERN_ERR "pasemi_nand: Unable to register MTD device\n");
|
||||
err = -ENODEV;
|
||||
goto out_lpc;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "PA Semi NAND flash at %08llx, control at I/O %x\n",
|
||||
res.start, lpcctl);
|
||||
|
||||
return 0;
|
||||
|
||||
out_lpc:
|
||||
release_region(lpcctl, 4);
|
||||
out_ior:
|
||||
iounmap(chip->IO_ADDR_R);
|
||||
out_mtd:
|
||||
kfree(pasemi_nand_mtd);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pasemi_nand_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct nand_chip *chip;
|
||||
|
||||
if (!pasemi_nand_mtd)
|
||||
return 0;
|
||||
|
||||
chip = pasemi_nand_mtd->priv;
|
||||
|
||||
/* Release resources, unregister device */
|
||||
nand_release(pasemi_nand_mtd);
|
||||
|
||||
release_region(lpcctl, 4);
|
||||
|
||||
iounmap(chip->IO_ADDR_R);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(pasemi_nand_mtd);
|
||||
|
||||
pasemi_nand_mtd = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id pasemi_nand_match[] =
|
||||
{
|
||||
{
|
||||
.compatible = "pasemi,localbus-nand",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, pasemi_nand_match);
|
||||
|
||||
static struct platform_driver pasemi_nand_driver =
|
||||
{
|
||||
.driver = {
|
||||
.name = driver_name,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = pasemi_nand_match,
|
||||
},
|
||||
.probe = pasemi_nand_probe,
|
||||
.remove = pasemi_nand_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(pasemi_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>");
|
||||
MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient");
|
||||
151
drivers/mtd/nand/plat_nand.c
Normal file
151
drivers/mtd/nand/plat_nand.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Generic NAND driver
|
||||
*
|
||||
* Author: Vitaly Wool <vitalywool@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
struct plat_nand_data {
|
||||
struct nand_chip chip;
|
||||
struct mtd_info mtd;
|
||||
void __iomem *io_base;
|
||||
};
|
||||
|
||||
static const char *part_probe_types[] = { "cmdlinepart", NULL };
|
||||
|
||||
/*
|
||||
* Probe for the NAND device.
|
||||
*/
|
||||
static int plat_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct mtd_part_parser_data ppdata;
|
||||
struct plat_nand_data *data;
|
||||
struct resource *res;
|
||||
const char **part_types;
|
||||
int err = 0;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform_nand_data is missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pdata->chip.nr_chips < 1) {
|
||||
dev_err(&pdev->dev, "invalid number of chips specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct plat_nand_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->io_base))
|
||||
return PTR_ERR(data->io_base);
|
||||
|
||||
data->chip.priv = &data;
|
||||
data->mtd.priv = &data->chip;
|
||||
data->mtd.owner = THIS_MODULE;
|
||||
data->mtd.name = dev_name(&pdev->dev);
|
||||
|
||||
data->chip.IO_ADDR_R = data->io_base;
|
||||
data->chip.IO_ADDR_W = data->io_base;
|
||||
data->chip.cmd_ctrl = pdata->ctrl.cmd_ctrl;
|
||||
data->chip.dev_ready = pdata->ctrl.dev_ready;
|
||||
data->chip.select_chip = pdata->ctrl.select_chip;
|
||||
data->chip.write_buf = pdata->ctrl.write_buf;
|
||||
data->chip.read_buf = pdata->ctrl.read_buf;
|
||||
data->chip.read_byte = pdata->ctrl.read_byte;
|
||||
data->chip.chip_delay = pdata->chip.chip_delay;
|
||||
data->chip.options |= pdata->chip.options;
|
||||
data->chip.bbt_options |= pdata->chip.bbt_options;
|
||||
|
||||
data->chip.ecc.hwctl = pdata->ctrl.hwcontrol;
|
||||
data->chip.ecc.layout = pdata->chip.ecclayout;
|
||||
data->chip.ecc.mode = NAND_ECC_SOFT;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
/* Handle any platform specific setup */
|
||||
if (pdata->ctrl.probe) {
|
||||
err = pdata->ctrl.probe(pdev);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan(&data->mtd, pdata->chip.nr_chips)) {
|
||||
err = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
part_types = pdata->chip.part_probe_types ? : part_probe_types;
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
err = mtd_device_parse_register(&data->mtd, part_types, &ppdata,
|
||||
pdata->chip.partitions,
|
||||
pdata->chip.nr_partitions);
|
||||
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
nand_release(&data->mtd);
|
||||
out:
|
||||
if (pdata->ctrl.remove)
|
||||
pdata->ctrl.remove(pdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a NAND device.
|
||||
*/
|
||||
static int plat_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct plat_nand_data *data = platform_get_drvdata(pdev);
|
||||
struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
nand_release(&data->mtd);
|
||||
if (pdata->ctrl.remove)
|
||||
pdata->ctrl.remove(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id plat_nand_match[] = {
|
||||
{ .compatible = "gen_nand" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, plat_nand_match);
|
||||
|
||||
static struct platform_driver plat_nand_driver = {
|
||||
.probe = plat_nand_probe,
|
||||
.remove = plat_nand_remove,
|
||||
.driver = {
|
||||
.name = "gen_nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = plat_nand_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(plat_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Vitaly Wool");
|
||||
MODULE_DESCRIPTION("Simple generic NAND driver");
|
||||
MODULE_ALIAS("platform:gen_nand");
|
||||
1924
drivers/mtd/nand/pxa3xx_nand.c
Normal file
1924
drivers/mtd/nand/pxa3xx_nand.c
Normal file
File diff suppressed because it is too large
Load diff
1085
drivers/mtd/nand/r852.c
Normal file
1085
drivers/mtd/nand/r852.c
Normal file
File diff suppressed because it is too large
Load diff
161
drivers/mtd/nand/r852.h
Normal file
161
drivers/mtd/nand/r852.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright © 2009 - Maxim Levitsky
|
||||
* driver for Ricoh xD readers
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
|
||||
/* nand interface + ecc
|
||||
byte write/read does one cycle on nand data lines.
|
||||
dword write/read does 4 cycles
|
||||
if R852_CTL_ECC_ACCESS is set in R852_CTL, then dword read reads
|
||||
results of ecc correction, if DMA read was done before.
|
||||
If write was done two dword reads read generated ecc checksums
|
||||
*/
|
||||
#define R852_DATALINE 0x00
|
||||
|
||||
/* control register */
|
||||
#define R852_CTL 0x04
|
||||
#define R852_CTL_COMMAND 0x01 /* send command (#CLE)*/
|
||||
#define R852_CTL_DATA 0x02 /* read/write data (#ALE)*/
|
||||
#define R852_CTL_ON 0x04 /* only seem to controls the hd led, */
|
||||
/* but has to be set on start...*/
|
||||
#define R852_CTL_RESET 0x08 /* unknown, set only on start once*/
|
||||
#define R852_CTL_CARDENABLE 0x10 /* probably (#CE) - always set*/
|
||||
#define R852_CTL_ECC_ENABLE 0x20 /* enable ecc engine */
|
||||
#define R852_CTL_ECC_ACCESS 0x40 /* read/write ecc via reg #0*/
|
||||
#define R852_CTL_WRITE 0x80 /* set when performing writes (#WP) */
|
||||
|
||||
/* card detection status */
|
||||
#define R852_CARD_STA 0x05
|
||||
|
||||
#define R852_CARD_STA_CD 0x01 /* state of #CD line, same as 0x04 */
|
||||
#define R852_CARD_STA_RO 0x02 /* card is readonly */
|
||||
#define R852_CARD_STA_PRESENT 0x04 /* card is present (#CD) */
|
||||
#define R852_CARD_STA_ABSENT 0x08 /* card is absent */
|
||||
#define R852_CARD_STA_BUSY 0x80 /* card is busy - (#R/B) */
|
||||
|
||||
/* card detection irq status & enable*/
|
||||
#define R852_CARD_IRQ_STA 0x06 /* IRQ status */
|
||||
#define R852_CARD_IRQ_ENABLE 0x07 /* IRQ enable */
|
||||
|
||||
#define R852_CARD_IRQ_CD 0x01 /* fire when #CD lights, same as 0x04*/
|
||||
#define R852_CARD_IRQ_REMOVE 0x04 /* detect card removal */
|
||||
#define R852_CARD_IRQ_INSERT 0x08 /* detect card insert */
|
||||
#define R852_CARD_IRQ_UNK1 0x10 /* unknown */
|
||||
#define R852_CARD_IRQ_GENABLE 0x80 /* general enable */
|
||||
#define R852_CARD_IRQ_MASK 0x1D
|
||||
|
||||
|
||||
|
||||
/* hardware enable */
|
||||
#define R852_HW 0x08
|
||||
#define R852_HW_ENABLED 0x01 /* hw enabled */
|
||||
#define R852_HW_UNKNOWN 0x80
|
||||
|
||||
|
||||
/* dma capabilities */
|
||||
#define R852_DMA_CAP 0x09
|
||||
#define R852_SMBIT 0x20 /* if set with bit #6 or bit #7, then */
|
||||
/* hw is smartmedia */
|
||||
#define R852_DMA1 0x40 /* if set w/bit #7, dma is supported */
|
||||
#define R852_DMA2 0x80 /* if set w/bit #6, dma is supported */
|
||||
|
||||
|
||||
/* physical DMA address - 32 bit value*/
|
||||
#define R852_DMA_ADDR 0x0C
|
||||
|
||||
|
||||
/* dma settings */
|
||||
#define R852_DMA_SETTINGS 0x10
|
||||
#define R852_DMA_MEMORY 0x01 /* (memory <-> internal hw buffer) */
|
||||
#define R852_DMA_READ 0x02 /* 0 = write, 1 = read */
|
||||
#define R852_DMA_INTERNAL 0x04 /* (internal hw buffer <-> card) */
|
||||
|
||||
/* dma IRQ status */
|
||||
#define R852_DMA_IRQ_STA 0x14
|
||||
|
||||
/* dma IRQ enable */
|
||||
#define R852_DMA_IRQ_ENABLE 0x18
|
||||
|
||||
#define R852_DMA_IRQ_MEMORY 0x01 /* (memory <-> internal hw buffer) */
|
||||
#define R852_DMA_IRQ_ERROR 0x02 /* error did happen */
|
||||
#define R852_DMA_IRQ_INTERNAL 0x04 /* (internal hw buffer <-> card) */
|
||||
#define R852_DMA_IRQ_MASK 0x07 /* mask of all IRQ bits */
|
||||
|
||||
|
||||
/* ECC syndrome format - read from reg #0 will return two copies of these for
|
||||
each half of the page.
|
||||
first byte is error byte location, and second, bit location + flags */
|
||||
#define R852_ECC_ERR_BIT_MSK 0x07 /* error bit location */
|
||||
#define R852_ECC_CORRECT 0x10 /* no errors - (guessed) */
|
||||
#define R852_ECC_CORRECTABLE 0x20 /* correctable error exist */
|
||||
#define R852_ECC_FAIL 0x40 /* non correctable error detected */
|
||||
|
||||
#define R852_DMA_LEN 512
|
||||
|
||||
#define DMA_INTERNAL 0
|
||||
#define DMA_MEMORY 1
|
||||
|
||||
struct r852_device {
|
||||
void __iomem *mmio; /* mmio */
|
||||
struct mtd_info *mtd; /* mtd backpointer */
|
||||
struct nand_chip *chip; /* nand chip backpointer */
|
||||
struct pci_dev *pci_dev; /* pci backpointer */
|
||||
|
||||
/* dma area */
|
||||
dma_addr_t phys_dma_addr; /* bus address of buffer*/
|
||||
struct completion dma_done; /* data transfer done */
|
||||
|
||||
dma_addr_t phys_bounce_buffer; /* bus address of bounce buffer */
|
||||
uint8_t *bounce_buffer; /* virtual address of bounce buffer */
|
||||
|
||||
int dma_dir; /* 1 = read, 0 = write */
|
||||
int dma_stage; /* 0 - idle, 1 - first step,
|
||||
2 - second step */
|
||||
|
||||
int dma_state; /* 0 = internal, 1 = memory */
|
||||
int dma_error; /* dma errors */
|
||||
int dma_usable; /* is it possible to use dma */
|
||||
|
||||
/* card status area */
|
||||
struct delayed_work card_detect_work;
|
||||
struct workqueue_struct *card_workqueue;
|
||||
int card_registred; /* card registered with mtd */
|
||||
int card_detected; /* card detected in slot */
|
||||
int card_unstable; /* whenever the card is inserted,
|
||||
is not known yet */
|
||||
int readonly; /* card is readonly */
|
||||
int sm; /* Is card smartmedia */
|
||||
|
||||
/* interrupt handling */
|
||||
spinlock_t irqlock; /* IRQ protecting lock */
|
||||
int irq; /* irq num */
|
||||
/* misc */
|
||||
void *tmp_buffer; /* temporary buffer */
|
||||
uint8_t ctlreg; /* cached contents of control reg */
|
||||
};
|
||||
|
||||
#define DRV_NAME "r852"
|
||||
|
||||
|
||||
#define dbg(format, ...) \
|
||||
if (debug) \
|
||||
printk(KERN_DEBUG DRV_NAME ": " format "\n", ## __VA_ARGS__)
|
||||
|
||||
#define dbg_verbose(format, ...) \
|
||||
if (debug > 1) \
|
||||
printk(KERN_DEBUG DRV_NAME ": " format "\n", ## __VA_ARGS__)
|
||||
|
||||
|
||||
#define message(format, ...) \
|
||||
printk(KERN_INFO DRV_NAME ": " format "\n", ## __VA_ARGS__)
|
||||
1146
drivers/mtd/nand/s3c2410.c
Normal file
1146
drivers/mtd/nand/s3c2410.c
Normal file
File diff suppressed because it is too large
Load diff
1203
drivers/mtd/nand/sh_flctl.c
Normal file
1203
drivers/mtd/nand/sh_flctl.c
Normal file
File diff suppressed because it is too large
Load diff
233
drivers/mtd/nand/sharpsl.c
Normal file
233
drivers/mtd/nand/sharpsl.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* drivers/mtd/nand/sharpsl.c
|
||||
*
|
||||
* Copyright (C) 2004 Richard Purdie
|
||||
* Copyright (C) 2008 Dmitry Baryshkov
|
||||
*
|
||||
* Based on Sharp's NAND driver sharp_sl.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/genhd.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/sharpsl.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
struct sharpsl_nand {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
|
||||
void __iomem *io;
|
||||
};
|
||||
|
||||
#define mtd_to_sharpsl(_mtd) container_of(_mtd, struct sharpsl_nand, mtd)
|
||||
|
||||
/* register offset */
|
||||
#define ECCLPLB 0x00 /* line parity 7 - 0 bit */
|
||||
#define ECCLPUB 0x04 /* line parity 15 - 8 bit */
|
||||
#define ECCCP 0x08 /* column parity 5 - 0 bit */
|
||||
#define ECCCNTR 0x0C /* ECC byte counter */
|
||||
#define ECCCLRR 0x10 /* cleare ECC */
|
||||
#define FLASHIO 0x14 /* Flash I/O */
|
||||
#define FLASHCTL 0x18 /* Flash Control */
|
||||
|
||||
/* Flash control bit */
|
||||
#define FLRYBY (1 << 5)
|
||||
#define FLCE1 (1 << 4)
|
||||
#define FLWP (1 << 3)
|
||||
#define FLALE (1 << 2)
|
||||
#define FLCLE (1 << 1)
|
||||
#define FLCE0 (1 << 0)
|
||||
|
||||
/*
|
||||
* hardware specific access to control-lines
|
||||
* ctrl:
|
||||
* NAND_CNE: bit 0 -> ! bit 0 & 4
|
||||
* NAND_CLE: bit 1 -> bit 1
|
||||
* NAND_ALE: bit 2 -> bit 2
|
||||
*
|
||||
*/
|
||||
static void sharpsl_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
unsigned char bits = ctrl & 0x07;
|
||||
|
||||
bits |= (ctrl & 0x01) << 4;
|
||||
|
||||
bits ^= 0x11;
|
||||
|
||||
writeb((readb(sharpsl->io + FLASHCTL) & ~0x17) | bits, sharpsl->io + FLASHCTL);
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
writeb(cmd, chip->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static int sharpsl_nand_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
return !((readb(sharpsl->io + FLASHCTL) & FLRYBY) == 0);
|
||||
}
|
||||
|
||||
static void sharpsl_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
writeb(0, sharpsl->io + ECCCLRR);
|
||||
}
|
||||
|
||||
static int sharpsl_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, u_char * ecc_code)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
ecc_code[0] = ~readb(sharpsl->io + ECCLPUB);
|
||||
ecc_code[1] = ~readb(sharpsl->io + ECCLPLB);
|
||||
ecc_code[2] = (~readb(sharpsl->io + ECCCP) << 2) | 0x03;
|
||||
return readb(sharpsl->io + ECCCNTR) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main initialization routine
|
||||
*/
|
||||
static int sharpsl_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
struct resource *r;
|
||||
int err = 0;
|
||||
struct sharpsl_nand *sharpsl;
|
||||
struct sharpsl_nand_platform_data *data = dev_get_platdata(&pdev->dev);
|
||||
|
||||
if (!data) {
|
||||
dev_err(&pdev->dev, "no platform data!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
sharpsl = kzalloc(sizeof(struct sharpsl_nand), GFP_KERNEL);
|
||||
if (!sharpsl)
|
||||
return -ENOMEM;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "no io memory resource defined!\n");
|
||||
err = -ENODEV;
|
||||
goto err_get_res;
|
||||
}
|
||||
|
||||
/* map physical address */
|
||||
sharpsl->io = ioremap(r->start, resource_size(r));
|
||||
if (!sharpsl->io) {
|
||||
dev_err(&pdev->dev, "ioremap to access Sharp SL NAND chip failed\n");
|
||||
err = -EIO;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *)(&sharpsl->chip);
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
sharpsl->mtd.priv = this;
|
||||
sharpsl->mtd.owner = THIS_MODULE;
|
||||
|
||||
platform_set_drvdata(pdev, sharpsl);
|
||||
|
||||
/*
|
||||
* PXA initialize
|
||||
*/
|
||||
writeb(readb(sharpsl->io + FLASHCTL) | FLWP, sharpsl->io + FLASHCTL);
|
||||
|
||||
/* Set address of NAND IO lines */
|
||||
this->IO_ADDR_R = sharpsl->io + FLASHIO;
|
||||
this->IO_ADDR_W = sharpsl->io + FLASHIO;
|
||||
/* Set address of hardware control function */
|
||||
this->cmd_ctrl = sharpsl_nand_hwcontrol;
|
||||
this->dev_ready = sharpsl_nand_dev_ready;
|
||||
/* 15 us command delay time */
|
||||
this->chip_delay = 15;
|
||||
/* set eccmode using hardware ECC */
|
||||
this->ecc.mode = NAND_ECC_HW;
|
||||
this->ecc.size = 256;
|
||||
this->ecc.bytes = 3;
|
||||
this->ecc.strength = 1;
|
||||
this->badblock_pattern = data->badblock_pattern;
|
||||
this->ecc.layout = data->ecc_layout;
|
||||
this->ecc.hwctl = sharpsl_nand_enable_hwecc;
|
||||
this->ecc.calculate = sharpsl_nand_calculate_ecc;
|
||||
this->ecc.correct = nand_correct_data;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
err = nand_scan(&sharpsl->mtd, 1);
|
||||
if (err)
|
||||
goto err_scan;
|
||||
|
||||
/* Register the partitions */
|
||||
sharpsl->mtd.name = "sharpsl-nand";
|
||||
|
||||
err = mtd_device_parse_register(&sharpsl->mtd, NULL, NULL,
|
||||
data->partitions, data->nr_partitions);
|
||||
if (err)
|
||||
goto err_add;
|
||||
|
||||
/* Return happy */
|
||||
return 0;
|
||||
|
||||
err_add:
|
||||
nand_release(&sharpsl->mtd);
|
||||
|
||||
err_scan:
|
||||
iounmap(sharpsl->io);
|
||||
err_ioremap:
|
||||
err_get_res:
|
||||
kfree(sharpsl);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up routine
|
||||
*/
|
||||
static int sharpsl_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = platform_get_drvdata(pdev);
|
||||
|
||||
/* Release resources, unregister device */
|
||||
nand_release(&sharpsl->mtd);
|
||||
|
||||
iounmap(sharpsl->io);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(sharpsl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sharpsl_nand_driver = {
|
||||
.driver = {
|
||||
.name = "sharpsl-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sharpsl_nand_probe,
|
||||
.remove = sharpsl_nand_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sharpsl_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
|
||||
MODULE_DESCRIPTION("Device specific logic for NAND flash on Sharp SL-C7xx Series");
|
||||
141
drivers/mtd/nand/sm_common.c
Normal file
141
drivers/mtd/nand/sm_common.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright © 2009 - Maxim Levitsky
|
||||
* Common routines & support for xD format
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sizes.h>
|
||||
#include "sm_common.h"
|
||||
|
||||
static struct nand_ecclayout nand_oob_sm = {
|
||||
.eccbytes = 6,
|
||||
.eccpos = {8, 9, 10, 13, 14, 15},
|
||||
.oobfree = {
|
||||
{.offset = 0 , .length = 4}, /* reserved */
|
||||
{.offset = 6 , .length = 2}, /* LBA1 */
|
||||
{.offset = 11, .length = 2} /* LBA2 */
|
||||
}
|
||||
};
|
||||
|
||||
/* NOTE: This layout is is not compatabable with SmartMedia, */
|
||||
/* because the 256 byte devices have page depenent oob layout */
|
||||
/* However it does preserve the bad block markers */
|
||||
/* If you use smftl, it will bypass this and work correctly */
|
||||
/* If you not, then you break SmartMedia compliance anyway */
|
||||
|
||||
static struct nand_ecclayout nand_oob_sm_small = {
|
||||
.eccbytes = 3,
|
||||
.eccpos = {0, 1, 2},
|
||||
.oobfree = {
|
||||
{.offset = 3 , .length = 2}, /* reserved */
|
||||
{.offset = 6 , .length = 2}, /* LBA1 */
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static int sm_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
struct sm_oob oob;
|
||||
int ret;
|
||||
|
||||
memset(&oob, -1, SM_OOB_SIZE);
|
||||
oob.block_status = 0x0F;
|
||||
|
||||
/* As long as this function is called on erase block boundaries
|
||||
it will work correctly for 256 byte nand */
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ops.ooboffs = 0;
|
||||
ops.ooblen = mtd->oobsize;
|
||||
ops.oobbuf = (void *)&oob;
|
||||
ops.datbuf = NULL;
|
||||
|
||||
|
||||
ret = mtd_write_oob(mtd, ofs, &ops);
|
||||
if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) {
|
||||
printk(KERN_NOTICE
|
||||
"sm_common: can't mark sector at %i as bad\n",
|
||||
(int)ofs);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct nand_flash_dev nand_smartmedia_flash_ids[] = {
|
||||
LEGACY_ID_NAND("SmartMedia 2MiB 3,3V ROM", 0x5d, 2, SZ_8K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 4MiB 3,3V", 0xe3, 4, SZ_8K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 4MiB 3,3/5V", 0xe5, 4, SZ_8K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 4MiB 5V", 0x6b, 4, SZ_8K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 4MiB 3,3V ROM", 0xd5, 4, SZ_8K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 8MiB 3,3V", 0xe6, 8, SZ_8K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 8MiB 3,3V ROM", 0xd6, 8, SZ_8K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 16MiB 3,3V", 0x73, 16, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 16MiB 3,3V ROM", 0x57, 16, SZ_16K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 32MiB 3,3V", 0x75, 32, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 32MiB 3,3V ROM", 0x58, 32, SZ_16K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 64MiB 3,3V", 0x76, 64, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 64MiB 3,3V ROM", 0xd9, 64, SZ_16K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 128MiB 3,3V", 0x79, 128, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 128MiB 3,3V ROM", 0xda, 128, SZ_16K, NAND_ROM),
|
||||
LEGACY_ID_NAND("SmartMedia 256MiB 3, 3V", 0x71, 256, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("SmartMedia 256MiB 3,3V ROM", 0x5b, 256, SZ_16K, NAND_ROM),
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static struct nand_flash_dev nand_xd_flash_ids[] = {
|
||||
LEGACY_ID_NAND("xD 16MiB 3,3V", 0x73, 16, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("xD 32MiB 3,3V", 0x75, 32, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("xD 64MiB 3,3V", 0x76, 64, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("xD 128MiB 3,3V", 0x79, 128, SZ_16K, 0),
|
||||
LEGACY_ID_NAND("xD 256MiB 3,3V", 0x71, 256, SZ_16K, NAND_BROKEN_XD),
|
||||
LEGACY_ID_NAND("xD 512MiB 3,3V", 0xdc, 512, SZ_16K, NAND_BROKEN_XD),
|
||||
LEGACY_ID_NAND("xD 1GiB 3,3V", 0xd3, 1024, SZ_16K, NAND_BROKEN_XD),
|
||||
LEGACY_ID_NAND("xD 2GiB 3,3V", 0xd5, 2048, SZ_16K, NAND_BROKEN_XD),
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int sm_register_device(struct mtd_info *mtd, int smartmedia)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int ret;
|
||||
|
||||
chip->options |= NAND_SKIP_BBTSCAN;
|
||||
|
||||
/* Scan for card properties */
|
||||
ret = nand_scan_ident(mtd, 1, smartmedia ?
|
||||
nand_smartmedia_flash_ids : nand_xd_flash_ids);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Bad block marker position */
|
||||
chip->badblockpos = 0x05;
|
||||
chip->badblockbits = 7;
|
||||
chip->block_markbad = sm_block_markbad;
|
||||
|
||||
/* ECC layout */
|
||||
if (mtd->writesize == SM_SECTOR_SIZE)
|
||||
chip->ecc.layout = &nand_oob_sm;
|
||||
else if (mtd->writesize == SM_SMALL_PAGE)
|
||||
chip->ecc.layout = &nand_oob_sm_small;
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
ret = nand_scan_tail(mtd);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mtd_device_register(mtd, NULL, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sm_register_device);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
|
||||
MODULE_DESCRIPTION("Common SmartMedia/xD functions");
|
||||
61
drivers/mtd/nand/sm_common.h
Normal file
61
drivers/mtd/nand/sm_common.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright © 2009 - Maxim Levitsky
|
||||
* Common routines & support for SmartMedia/xD format
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
/* Full oob structure as written on the flash */
|
||||
struct sm_oob {
|
||||
uint32_t reserved;
|
||||
uint8_t data_status;
|
||||
uint8_t block_status;
|
||||
uint8_t lba_copy1[2];
|
||||
uint8_t ecc2[3];
|
||||
uint8_t lba_copy2[2];
|
||||
uint8_t ecc1[3];
|
||||
} __packed;
|
||||
|
||||
|
||||
/* one sector is always 512 bytes, but it can consist of two nand pages */
|
||||
#define SM_SECTOR_SIZE 512
|
||||
|
||||
/* oob area is also 16 bytes, but might be from two pages */
|
||||
#define SM_OOB_SIZE 16
|
||||
|
||||
/* This is maximum zone size, and all devices that have more that one zone
|
||||
have this size */
|
||||
#define SM_MAX_ZONE_SIZE 1024
|
||||
|
||||
/* support for small page nand */
|
||||
#define SM_SMALL_PAGE 256
|
||||
#define SM_SMALL_OOB_SIZE 8
|
||||
|
||||
|
||||
extern int sm_register_device(struct mtd_info *mtd, int smartmedia);
|
||||
|
||||
|
||||
static inline int sm_sector_valid(struct sm_oob *oob)
|
||||
{
|
||||
return hweight16(oob->data_status) >= 5;
|
||||
}
|
||||
|
||||
static inline int sm_block_valid(struct sm_oob *oob)
|
||||
{
|
||||
return hweight16(oob->block_status) >= 7;
|
||||
}
|
||||
|
||||
static inline int sm_block_erased(struct sm_oob *oob)
|
||||
{
|
||||
static const uint32_t erased_pattern[4] = {
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
|
||||
|
||||
/* First test for erased block */
|
||||
if (!memcmp(oob, erased_pattern, sizeof(*oob)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
254
drivers/mtd/nand/socrates_nand.c
Normal file
254
drivers/mtd/nand/socrates_nand.c
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* drivers/mtd/nand/socrates_nand.c
|
||||
*
|
||||
* Copyright © 2008 Ilya Yanok, Emcraft Systems
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#define FPGA_NAND_CMD_MASK (0x7 << 28)
|
||||
#define FPGA_NAND_CMD_COMMAND (0x0 << 28)
|
||||
#define FPGA_NAND_CMD_ADDR (0x1 << 28)
|
||||
#define FPGA_NAND_CMD_READ (0x2 << 28)
|
||||
#define FPGA_NAND_CMD_WRITE (0x3 << 28)
|
||||
#define FPGA_NAND_BUSY (0x1 << 15)
|
||||
#define FPGA_NAND_ENABLE (0x1 << 31)
|
||||
#define FPGA_NAND_DATA_SHIFT 16
|
||||
|
||||
struct socrates_nand_host {
|
||||
struct nand_chip nand_chip;
|
||||
struct mtd_info mtd;
|
||||
void __iomem *io_base;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* socrates_nand_write_buf - write buffer to chip
|
||||
* @mtd: MTD device structure
|
||||
* @buf: data buffer
|
||||
* @len: number of bytes to write
|
||||
*/
|
||||
static void socrates_nand_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
struct socrates_nand_host *host = this->priv;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
out_be32(host->io_base, FPGA_NAND_ENABLE |
|
||||
FPGA_NAND_CMD_WRITE |
|
||||
(buf[i] << FPGA_NAND_DATA_SHIFT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* socrates_nand_read_buf - read chip data into buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer to store date
|
||||
* @len: number of bytes to read
|
||||
*/
|
||||
static void socrates_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
struct socrates_nand_host *host = this->priv;
|
||||
uint32_t val;
|
||||
|
||||
val = FPGA_NAND_ENABLE | FPGA_NAND_CMD_READ;
|
||||
|
||||
out_be32(host->io_base, val);
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[i] = (in_be32(host->io_base) >>
|
||||
FPGA_NAND_DATA_SHIFT) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* socrates_nand_read_byte - read one byte from the chip
|
||||
* @mtd: MTD device structure
|
||||
*/
|
||||
static uint8_t socrates_nand_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
uint8_t byte;
|
||||
socrates_nand_read_buf(mtd, &byte, sizeof(byte));
|
||||
return byte;
|
||||
}
|
||||
|
||||
/**
|
||||
* socrates_nand_read_word - read one word from the chip
|
||||
* @mtd: MTD device structure
|
||||
*/
|
||||
static uint16_t socrates_nand_read_word(struct mtd_info *mtd)
|
||||
{
|
||||
uint16_t word;
|
||||
socrates_nand_read_buf(mtd, (uint8_t *)&word, sizeof(word));
|
||||
return word;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware specific access to control-lines
|
||||
*/
|
||||
static void socrates_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct socrates_nand_host *host = nand_chip->priv;
|
||||
uint32_t val;
|
||||
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
val = FPGA_NAND_CMD_COMMAND;
|
||||
else
|
||||
val = FPGA_NAND_CMD_ADDR;
|
||||
|
||||
if (ctrl & NAND_NCE)
|
||||
val |= FPGA_NAND_ENABLE;
|
||||
|
||||
val |= (cmd & 0xff) << FPGA_NAND_DATA_SHIFT;
|
||||
|
||||
out_be32(host->io_base, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the Device Ready pin.
|
||||
*/
|
||||
static int socrates_nand_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct socrates_nand_host *host = nand_chip->priv;
|
||||
|
||||
if (in_be32(host->io_base) & FPGA_NAND_BUSY)
|
||||
return 0; /* busy */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for the NAND device.
|
||||
*/
|
||||
static int socrates_nand_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct socrates_nand_host *host;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *nand_chip;
|
||||
int res;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
host = devm_kzalloc(&ofdev->dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
host->io_base = of_iomap(ofdev->dev.of_node, 0);
|
||||
if (host->io_base == NULL) {
|
||||
dev_err(&ofdev->dev, "ioremap failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mtd = &host->mtd;
|
||||
nand_chip = &host->nand_chip;
|
||||
host->dev = &ofdev->dev;
|
||||
|
||||
nand_chip->priv = host; /* link the private data structures */
|
||||
mtd->priv = nand_chip;
|
||||
mtd->name = "socrates_nand";
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->dev.parent = &ofdev->dev;
|
||||
ppdata.of_node = ofdev->dev.of_node;
|
||||
|
||||
/*should never be accessed directly */
|
||||
nand_chip->IO_ADDR_R = (void *)0xdeadbeef;
|
||||
nand_chip->IO_ADDR_W = (void *)0xdeadbeef;
|
||||
|
||||
nand_chip->cmd_ctrl = socrates_nand_cmd_ctrl;
|
||||
nand_chip->read_byte = socrates_nand_read_byte;
|
||||
nand_chip->read_word = socrates_nand_read_word;
|
||||
nand_chip->write_buf = socrates_nand_write_buf;
|
||||
nand_chip->read_buf = socrates_nand_read_buf;
|
||||
nand_chip->dev_ready = socrates_nand_device_ready;
|
||||
|
||||
nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */
|
||||
|
||||
/* TODO: I have no idea what real delay is. */
|
||||
nand_chip->chip_delay = 20; /* 20us command delay time */
|
||||
|
||||
dev_set_drvdata(&ofdev->dev, host);
|
||||
|
||||
/* first scan to find the device and get the page size */
|
||||
if (nand_scan_ident(mtd, 1, NULL)) {
|
||||
res = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* second phase scan */
|
||||
if (nand_scan_tail(mtd)) {
|
||||
res = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
res = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
|
||||
if (!res)
|
||||
return res;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
out:
|
||||
iounmap(host->io_base);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a NAND device.
|
||||
*/
|
||||
static int socrates_nand_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct socrates_nand_host *host = dev_get_drvdata(&ofdev->dev);
|
||||
struct mtd_info *mtd = &host->mtd;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
iounmap(host->io_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id socrates_nand_match[] =
|
||||
{
|
||||
{
|
||||
.compatible = "abb,socrates-nand",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, socrates_nand_match);
|
||||
|
||||
static struct platform_driver socrates_nand_driver = {
|
||||
.driver = {
|
||||
.name = "socrates_nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = socrates_nand_match,
|
||||
},
|
||||
.probe = socrates_nand_probe,
|
||||
.remove = socrates_nand_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(socrates_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ilya Yanok");
|
||||
MODULE_DESCRIPTION("NAND driver for Socrates board");
|
||||
508
drivers/mtd/nand/tmio_nand.c
Normal file
508
drivers/mtd/nand/tmio_nand.c
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
/*
|
||||
* Toshiba TMIO NAND flash controller driver
|
||||
*
|
||||
* Slightly murky pre-git history of the driver:
|
||||
*
|
||||
* Copyright (c) Ian Molton 2004, 2005, 2008
|
||||
* Original work, independent of sharps code. Included hardware ECC support.
|
||||
* Hard ECC did not work for writes in the early revisions.
|
||||
* Copyright (c) Dirk Opfer 2005.
|
||||
* Modifications developed from sharps code but
|
||||
* NOT containing any, ported onto Ians base.
|
||||
* Copyright (c) Chris Humbert 2005
|
||||
* Copyright (c) Dmitry Baryshkov 2008
|
||||
* Minor fixes
|
||||
*
|
||||
* Parts copyright Sebastian Carlier
|
||||
*
|
||||
* This file is licensed under
|
||||
* the terms of the GNU General Public License version 2. This program
|
||||
* is licensed "as is" without any warranty of any kind, whether express
|
||||
* or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tmio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* NAND Flash Host Controller Configuration Register
|
||||
*/
|
||||
#define CCR_COMMAND 0x04 /* w Command */
|
||||
#define CCR_BASE 0x10 /* l NAND Flash Control Reg Base Addr */
|
||||
#define CCR_INTP 0x3d /* b Interrupt Pin */
|
||||
#define CCR_INTE 0x48 /* b Interrupt Enable */
|
||||
#define CCR_EC 0x4a /* b Event Control */
|
||||
#define CCR_ICC 0x4c /* b Internal Clock Control */
|
||||
#define CCR_ECCC 0x5b /* b ECC Control */
|
||||
#define CCR_NFTC 0x60 /* b NAND Flash Transaction Control */
|
||||
#define CCR_NFM 0x61 /* b NAND Flash Monitor */
|
||||
#define CCR_NFPSC 0x62 /* b NAND Flash Power Supply Control */
|
||||
#define CCR_NFDC 0x63 /* b NAND Flash Detect Control */
|
||||
|
||||
/*
|
||||
* NAND Flash Control Register
|
||||
*/
|
||||
#define FCR_DATA 0x00 /* bwl Data Register */
|
||||
#define FCR_MODE 0x04 /* b Mode Register */
|
||||
#define FCR_STATUS 0x05 /* b Status Register */
|
||||
#define FCR_ISR 0x06 /* b Interrupt Status Register */
|
||||
#define FCR_IMR 0x07 /* b Interrupt Mask Register */
|
||||
|
||||
/* FCR_MODE Register Command List */
|
||||
#define FCR_MODE_DATA 0x94 /* Data Data_Mode */
|
||||
#define FCR_MODE_COMMAND 0x95 /* Data Command_Mode */
|
||||
#define FCR_MODE_ADDRESS 0x96 /* Data Address_Mode */
|
||||
|
||||
#define FCR_MODE_HWECC_CALC 0xB4 /* HW-ECC Data */
|
||||
#define FCR_MODE_HWECC_RESULT 0xD4 /* HW-ECC Calc result Read_Mode */
|
||||
#define FCR_MODE_HWECC_RESET 0xF4 /* HW-ECC Reset */
|
||||
|
||||
#define FCR_MODE_POWER_ON 0x0C /* Power Supply ON to SSFDC card */
|
||||
#define FCR_MODE_POWER_OFF 0x08 /* Power Supply OFF to SSFDC card */
|
||||
|
||||
#define FCR_MODE_LED_OFF 0x00 /* LED OFF */
|
||||
#define FCR_MODE_LED_ON 0x04 /* LED ON */
|
||||
|
||||
#define FCR_MODE_EJECT_ON 0x68 /* Ejection events active */
|
||||
#define FCR_MODE_EJECT_OFF 0x08 /* Ejection events ignored */
|
||||
|
||||
#define FCR_MODE_LOCK 0x6C /* Lock_Mode. Eject Switch Invalid */
|
||||
#define FCR_MODE_UNLOCK 0x0C /* UnLock_Mode. Eject Switch is valid */
|
||||
|
||||
#define FCR_MODE_CONTROLLER_ID 0x40 /* Controller ID Read */
|
||||
#define FCR_MODE_STANDBY 0x00 /* SSFDC card Changes Standby State */
|
||||
|
||||
#define FCR_MODE_WE 0x80
|
||||
#define FCR_MODE_ECC1 0x40
|
||||
#define FCR_MODE_ECC0 0x20
|
||||
#define FCR_MODE_CE 0x10
|
||||
#define FCR_MODE_PCNT1 0x08
|
||||
#define FCR_MODE_PCNT0 0x04
|
||||
#define FCR_MODE_ALE 0x02
|
||||
#define FCR_MODE_CLE 0x01
|
||||
|
||||
#define FCR_STATUS_BUSY 0x80
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
struct tmio_nand {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
|
||||
struct platform_device *dev;
|
||||
|
||||
void __iomem *ccr;
|
||||
void __iomem *fcr;
|
||||
unsigned long fcr_base;
|
||||
|
||||
unsigned int irq;
|
||||
|
||||
/* for tmio_nand_read_byte */
|
||||
u8 read;
|
||||
unsigned read_good:1;
|
||||
};
|
||||
|
||||
#define mtd_to_tmio(m) container_of(m, struct tmio_nand, mtd)
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
static void tmio_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
u8 mode;
|
||||
|
||||
if (ctrl & NAND_NCE) {
|
||||
mode = FCR_MODE_DATA;
|
||||
|
||||
if (ctrl & NAND_CLE)
|
||||
mode |= FCR_MODE_CLE;
|
||||
else
|
||||
mode &= ~FCR_MODE_CLE;
|
||||
|
||||
if (ctrl & NAND_ALE)
|
||||
mode |= FCR_MODE_ALE;
|
||||
else
|
||||
mode &= ~FCR_MODE_ALE;
|
||||
} else {
|
||||
mode = FCR_MODE_STANDBY;
|
||||
}
|
||||
|
||||
tmio_iowrite8(mode, tmio->fcr + FCR_MODE);
|
||||
tmio->read_good = 0;
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
tmio_iowrite8(cmd, chip->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static int tmio_nand_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
|
||||
return !(tmio_ioread8(tmio->fcr + FCR_STATUS) & FCR_STATUS_BUSY);
|
||||
}
|
||||
|
||||
static irqreturn_t tmio_irq(int irq, void *__tmio)
|
||||
{
|
||||
struct tmio_nand *tmio = __tmio;
|
||||
struct nand_chip *nand_chip = &tmio->chip;
|
||||
|
||||
/* disable RDYREQ interrupt */
|
||||
tmio_iowrite8(0x00, tmio->fcr + FCR_IMR);
|
||||
|
||||
if (unlikely(!waitqueue_active(&nand_chip->controller->wq)))
|
||||
dev_warn(&tmio->dev->dev, "spurious interrupt\n");
|
||||
|
||||
wake_up(&nand_chip->controller->wq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
*The TMIO core has a RDYREQ interrupt on the posedge of #SMRB.
|
||||
*This interrupt is normally disabled, but for long operations like
|
||||
*erase and write, we enable it to wake us up. The irq handler
|
||||
*disables the interrupt.
|
||||
*/
|
||||
static int
|
||||
tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
long timeout;
|
||||
|
||||
/* enable RDYREQ interrupt */
|
||||
tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR);
|
||||
tmio_iowrite8(0x81, tmio->fcr + FCR_IMR);
|
||||
|
||||
timeout = wait_event_timeout(nand_chip->controller->wq,
|
||||
tmio_nand_dev_ready(mtd),
|
||||
msecs_to_jiffies(nand_chip->state == FL_ERASING ? 400 : 20));
|
||||
|
||||
if (unlikely(!tmio_nand_dev_ready(mtd))) {
|
||||
tmio_iowrite8(0x00, tmio->fcr + FCR_IMR);
|
||||
dev_warn(&tmio->dev->dev, "still busy with %s after %d ms\n",
|
||||
nand_chip->state == FL_ERASING ? "erase" : "program",
|
||||
nand_chip->state == FL_ERASING ? 400 : 20);
|
||||
|
||||
} else if (unlikely(!timeout)) {
|
||||
tmio_iowrite8(0x00, tmio->fcr + FCR_IMR);
|
||||
dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n");
|
||||
}
|
||||
|
||||
nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
||||
return nand_chip->read_byte(mtd);
|
||||
}
|
||||
|
||||
/*
|
||||
*The TMIO controller combines two 8-bit data bytes into one 16-bit
|
||||
*word. This function separates them so nand_base.c works as expected,
|
||||
*especially its NAND_CMD_READID routines.
|
||||
*
|
||||
*To prevent stale data from being read, tmio_nand_hwcontrol() clears
|
||||
*tmio->read_good.
|
||||
*/
|
||||
static u_char tmio_nand_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
unsigned int data;
|
||||
|
||||
if (tmio->read_good--)
|
||||
return tmio->read;
|
||||
|
||||
data = tmio_ioread16(tmio->fcr + FCR_DATA);
|
||||
tmio->read = data >> 8;
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
*The TMIO controller converts an 8-bit NAND interface to a 16-bit
|
||||
*bus interface, so all data reads and writes must be 16-bit wide.
|
||||
*Thus, we implement 16-bit versions of the read, write, and verify
|
||||
*buffer functions.
|
||||
*/
|
||||
static void
|
||||
tmio_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
|
||||
tmio_iowrite16_rep(tmio->fcr + FCR_DATA, buf, len >> 1);
|
||||
}
|
||||
|
||||
static void tmio_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
|
||||
tmio_ioread16_rep(tmio->fcr + FCR_DATA, buf, len >> 1);
|
||||
}
|
||||
|
||||
static void tmio_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
|
||||
tmio_iowrite8(FCR_MODE_HWECC_RESET, tmio->fcr + FCR_MODE);
|
||||
tmio_ioread8(tmio->fcr + FCR_DATA); /* dummy read */
|
||||
tmio_iowrite8(FCR_MODE_HWECC_CALC, tmio->fcr + FCR_MODE);
|
||||
}
|
||||
|
||||
static int tmio_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
|
||||
u_char *ecc_code)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
unsigned int ecc;
|
||||
|
||||
tmio_iowrite8(FCR_MODE_HWECC_RESULT, tmio->fcr + FCR_MODE);
|
||||
|
||||
ecc = tmio_ioread16(tmio->fcr + FCR_DATA);
|
||||
ecc_code[1] = ecc; /* 000-255 LP7-0 */
|
||||
ecc_code[0] = ecc >> 8; /* 000-255 LP15-8 */
|
||||
ecc = tmio_ioread16(tmio->fcr + FCR_DATA);
|
||||
ecc_code[2] = ecc; /* 000-255 CP5-0,11b */
|
||||
ecc_code[4] = ecc >> 8; /* 256-511 LP7-0 */
|
||||
ecc = tmio_ioread16(tmio->fcr + FCR_DATA);
|
||||
ecc_code[3] = ecc; /* 256-511 LP15-8 */
|
||||
ecc_code[5] = ecc >> 8; /* 256-511 CP5-0,11b */
|
||||
|
||||
tmio_iowrite8(FCR_MODE_DATA, tmio->fcr + FCR_MODE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmio_nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||
{
|
||||
int r0, r1;
|
||||
|
||||
/* assume ecc.size = 512 and ecc.bytes = 6 */
|
||||
r0 = __nand_correct_data(buf, read_ecc, calc_ecc, 256);
|
||||
if (r0 < 0)
|
||||
return r0;
|
||||
r1 = __nand_correct_data(buf + 256, read_ecc + 3, calc_ecc + 3, 256);
|
||||
if (r1 < 0)
|
||||
return r1;
|
||||
return r0 + r1;
|
||||
}
|
||||
|
||||
static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
int ret;
|
||||
|
||||
if (cell->enable) {
|
||||
ret = cell->enable(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* (4Ch) CLKRUN Enable 1st spcrunc */
|
||||
tmio_iowrite8(0x81, tmio->ccr + CCR_ICC);
|
||||
|
||||
/* (10h)BaseAddress 0x1000 spba.spba2 */
|
||||
tmio_iowrite16(tmio->fcr_base, tmio->ccr + CCR_BASE);
|
||||
tmio_iowrite16(tmio->fcr_base >> 16, tmio->ccr + CCR_BASE + 2);
|
||||
|
||||
/* (04h)Command Register I/O spcmd */
|
||||
tmio_iowrite8(0x02, tmio->ccr + CCR_COMMAND);
|
||||
|
||||
/* (62h) Power Supply Control ssmpwc */
|
||||
/* HardPowerOFF - SuspendOFF - PowerSupplyWait_4MS */
|
||||
tmio_iowrite8(0x02, tmio->ccr + CCR_NFPSC);
|
||||
|
||||
/* (63h) Detect Control ssmdtc */
|
||||
tmio_iowrite8(0x02, tmio->ccr + CCR_NFDC);
|
||||
|
||||
/* Interrupt status register clear sintst */
|
||||
tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR);
|
||||
|
||||
/* After power supply, Media are reset smode */
|
||||
tmio_iowrite8(FCR_MODE_POWER_ON, tmio->fcr + FCR_MODE);
|
||||
tmio_iowrite8(FCR_MODE_COMMAND, tmio->fcr + FCR_MODE);
|
||||
tmio_iowrite8(NAND_CMD_RESET, tmio->fcr + FCR_DATA);
|
||||
|
||||
/* Standby Mode smode */
|
||||
tmio_iowrite8(FCR_MODE_STANDBY, tmio->fcr + FCR_MODE);
|
||||
|
||||
mdelay(5);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmio_hw_stop(struct platform_device *dev, struct tmio_nand *tmio)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
|
||||
tmio_iowrite8(FCR_MODE_POWER_OFF, tmio->fcr + FCR_MODE);
|
||||
if (cell->disable)
|
||||
cell->disable(dev);
|
||||
}
|
||||
|
||||
static int tmio_probe(struct platform_device *dev)
|
||||
{
|
||||
struct tmio_nand_data *data = dev_get_platdata(&dev->dev);
|
||||
struct resource *fcr = platform_get_resource(dev,
|
||||
IORESOURCE_MEM, 0);
|
||||
struct resource *ccr = platform_get_resource(dev,
|
||||
IORESOURCE_MEM, 1);
|
||||
int irq = platform_get_irq(dev, 0);
|
||||
struct tmio_nand *tmio;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *nand_chip;
|
||||
int retval;
|
||||
|
||||
if (data == NULL)
|
||||
dev_warn(&dev->dev, "NULL platform data!\n");
|
||||
|
||||
tmio = devm_kzalloc(&dev->dev, sizeof(*tmio), GFP_KERNEL);
|
||||
if (!tmio)
|
||||
return -ENOMEM;
|
||||
|
||||
tmio->dev = dev;
|
||||
|
||||
platform_set_drvdata(dev, tmio);
|
||||
mtd = &tmio->mtd;
|
||||
nand_chip = &tmio->chip;
|
||||
mtd->priv = nand_chip;
|
||||
mtd->name = "tmio-nand";
|
||||
|
||||
tmio->ccr = devm_ioremap(&dev->dev, ccr->start, resource_size(ccr));
|
||||
if (!tmio->ccr)
|
||||
return -EIO;
|
||||
|
||||
tmio->fcr_base = fcr->start & 0xfffff;
|
||||
tmio->fcr = devm_ioremap(&dev->dev, fcr->start, resource_size(fcr));
|
||||
if (!tmio->fcr)
|
||||
return -EIO;
|
||||
|
||||
retval = tmio_hw_init(dev, tmio);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/* Set address of NAND IO lines */
|
||||
nand_chip->IO_ADDR_R = tmio->fcr;
|
||||
nand_chip->IO_ADDR_W = tmio->fcr;
|
||||
|
||||
/* Set address of hardware control function */
|
||||
nand_chip->cmd_ctrl = tmio_nand_hwcontrol;
|
||||
nand_chip->dev_ready = tmio_nand_dev_ready;
|
||||
nand_chip->read_byte = tmio_nand_read_byte;
|
||||
nand_chip->write_buf = tmio_nand_write_buf;
|
||||
nand_chip->read_buf = tmio_nand_read_buf;
|
||||
|
||||
/* set eccmode using hardware ECC */
|
||||
nand_chip->ecc.mode = NAND_ECC_HW;
|
||||
nand_chip->ecc.size = 512;
|
||||
nand_chip->ecc.bytes = 6;
|
||||
nand_chip->ecc.strength = 2;
|
||||
nand_chip->ecc.hwctl = tmio_nand_enable_hwecc;
|
||||
nand_chip->ecc.calculate = tmio_nand_calculate_ecc;
|
||||
nand_chip->ecc.correct = tmio_nand_correct_data;
|
||||
|
||||
if (data)
|
||||
nand_chip->badblock_pattern = data->badblock_pattern;
|
||||
|
||||
/* 15 us command delay time */
|
||||
nand_chip->chip_delay = 15;
|
||||
|
||||
retval = devm_request_irq(&dev->dev, irq, &tmio_irq, 0,
|
||||
dev_name(&dev->dev), tmio);
|
||||
if (retval) {
|
||||
dev_err(&dev->dev, "request_irq error %d\n", retval);
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
tmio->irq = irq;
|
||||
nand_chip->waitfunc = tmio_nand_wait;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan(mtd, 1)) {
|
||||
retval = -ENODEV;
|
||||
goto err_irq;
|
||||
}
|
||||
/* Register the partitions */
|
||||
retval = mtd_device_parse_register(mtd, NULL, NULL,
|
||||
data ? data->partition : NULL,
|
||||
data ? data->num_partitions : 0);
|
||||
if (!retval)
|
||||
return retval;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
err_irq:
|
||||
tmio_hw_stop(dev, tmio);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int tmio_remove(struct platform_device *dev)
|
||||
{
|
||||
struct tmio_nand *tmio = platform_get_drvdata(dev);
|
||||
|
||||
nand_release(&tmio->mtd);
|
||||
tmio_hw_stop(dev, tmio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tmio_suspend(struct platform_device *dev, pm_message_t state)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
|
||||
if (cell->suspend)
|
||||
cell->suspend(dev);
|
||||
|
||||
tmio_hw_stop(dev, platform_get_drvdata(dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmio_resume(struct platform_device *dev)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
|
||||
/* FIXME - is this required or merely another attack of the broken
|
||||
* SHARP platform? Looks suspicious.
|
||||
*/
|
||||
tmio_hw_init(dev, platform_get_drvdata(dev));
|
||||
|
||||
if (cell->resume)
|
||||
cell->resume(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define tmio_suspend NULL
|
||||
#define tmio_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver tmio_driver = {
|
||||
.driver.name = "tmio-nand",
|
||||
.driver.owner = THIS_MODULE,
|
||||
.probe = tmio_probe,
|
||||
.remove = tmio_remove,
|
||||
.suspend = tmio_suspend,
|
||||
.resume = tmio_resume,
|
||||
};
|
||||
|
||||
module_platform_driver(tmio_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Ian Molton, Dirk Opfer, Chris Humbert, Dmitry Baryshkov");
|
||||
MODULE_DESCRIPTION("NAND flash driver on Toshiba Mobile IO controller");
|
||||
MODULE_ALIAS("platform:tmio-nand");
|
||||
428
drivers/mtd/nand/txx9ndfmc.c
Normal file
428
drivers/mtd/nand/txx9ndfmc.c
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
* TXx9 NAND flash memory controller driver
|
||||
* Based on RBTX49xx patch from CELF patch archive.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* (C) Copyright TOSHIBA CORPORATION 2004-2007
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/txx9/ndfmc.h>
|
||||
|
||||
/* TXX9 NDFMC Registers */
|
||||
#define TXX9_NDFDTR 0x00
|
||||
#define TXX9_NDFMCR 0x04
|
||||
#define TXX9_NDFSR 0x08
|
||||
#define TXX9_NDFISR 0x0c
|
||||
#define TXX9_NDFIMR 0x10
|
||||
#define TXX9_NDFSPR 0x14
|
||||
#define TXX9_NDFRSTR 0x18 /* not TX4939 */
|
||||
|
||||
/* NDFMCR : NDFMC Mode Control */
|
||||
#define TXX9_NDFMCR_WE 0x80
|
||||
#define TXX9_NDFMCR_ECC_ALL 0x60
|
||||
#define TXX9_NDFMCR_ECC_RESET 0x60
|
||||
#define TXX9_NDFMCR_ECC_READ 0x40
|
||||
#define TXX9_NDFMCR_ECC_ON 0x20
|
||||
#define TXX9_NDFMCR_ECC_OFF 0x00
|
||||
#define TXX9_NDFMCR_CE 0x10
|
||||
#define TXX9_NDFMCR_BSPRT 0x04 /* TX4925/TX4926 only */
|
||||
#define TXX9_NDFMCR_ALE 0x02
|
||||
#define TXX9_NDFMCR_CLE 0x01
|
||||
/* TX4939 only */
|
||||
#define TXX9_NDFMCR_X16 0x0400
|
||||
#define TXX9_NDFMCR_DMAREQ_MASK 0x0300
|
||||
#define TXX9_NDFMCR_DMAREQ_NODMA 0x0000
|
||||
#define TXX9_NDFMCR_DMAREQ_128 0x0100
|
||||
#define TXX9_NDFMCR_DMAREQ_256 0x0200
|
||||
#define TXX9_NDFMCR_DMAREQ_512 0x0300
|
||||
#define TXX9_NDFMCR_CS_MASK 0x0c
|
||||
#define TXX9_NDFMCR_CS(ch) ((ch) << 2)
|
||||
|
||||
/* NDFMCR : NDFMC Status */
|
||||
#define TXX9_NDFSR_BUSY 0x80
|
||||
/* TX4939 only */
|
||||
#define TXX9_NDFSR_DMARUN 0x40
|
||||
|
||||
/* NDFMCR : NDFMC Reset */
|
||||
#define TXX9_NDFRSTR_RST 0x01
|
||||
|
||||
struct txx9ndfmc_priv {
|
||||
struct platform_device *dev;
|
||||
struct nand_chip chip;
|
||||
struct mtd_info mtd;
|
||||
int cs;
|
||||
const char *mtdname;
|
||||
};
|
||||
|
||||
#define MAX_TXX9NDFMC_DEV 4
|
||||
struct txx9ndfmc_drvdata {
|
||||
struct mtd_info *mtds[MAX_TXX9NDFMC_DEV];
|
||||
void __iomem *base;
|
||||
unsigned char hold; /* in gbusclock */
|
||||
unsigned char spw; /* in gbusclock */
|
||||
struct nand_hw_control hw_control;
|
||||
};
|
||||
|
||||
static struct platform_device *mtd_to_platdev(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct txx9ndfmc_priv *txx9_priv = chip->priv;
|
||||
return txx9_priv->dev;
|
||||
}
|
||||
|
||||
static void __iomem *ndregaddr(struct platform_device *dev, unsigned int reg)
|
||||
{
|
||||
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
||||
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
|
||||
return drvdata->base + (reg << plat->shift);
|
||||
}
|
||||
|
||||
static u32 txx9ndfmc_read(struct platform_device *dev, unsigned int reg)
|
||||
{
|
||||
return __raw_readl(ndregaddr(dev, reg));
|
||||
}
|
||||
|
||||
static void txx9ndfmc_write(struct platform_device *dev,
|
||||
u32 val, unsigned int reg)
|
||||
{
|
||||
__raw_writel(val, ndregaddr(dev, reg));
|
||||
}
|
||||
|
||||
static uint8_t txx9ndfmc_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
|
||||
return txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||
}
|
||||
|
||||
static void txx9ndfmc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
|
||||
int len)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
|
||||
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
||||
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_WE, TXX9_NDFMCR);
|
||||
while (len--)
|
||||
__raw_writel(*buf++, ndfdtr);
|
||||
txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
|
||||
}
|
||||
|
||||
static void txx9ndfmc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
|
||||
|
||||
while (len--)
|
||||
*buf++ = __raw_readl(ndfdtr);
|
||||
}
|
||||
|
||||
static void txx9ndfmc_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct txx9ndfmc_priv *txx9_priv = chip->priv;
|
||||
struct platform_device *dev = txx9_priv->dev;
|
||||
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
||||
|
||||
mcr &= ~(TXX9_NDFMCR_CLE | TXX9_NDFMCR_ALE | TXX9_NDFMCR_CE);
|
||||
mcr |= ctrl & NAND_CLE ? TXX9_NDFMCR_CLE : 0;
|
||||
mcr |= ctrl & NAND_ALE ? TXX9_NDFMCR_ALE : 0;
|
||||
/* TXX9_NDFMCR_CE bit is 0:high 1:low */
|
||||
mcr |= ctrl & NAND_NCE ? TXX9_NDFMCR_CE : 0;
|
||||
if (txx9_priv->cs >= 0 && (ctrl & NAND_NCE)) {
|
||||
mcr &= ~TXX9_NDFMCR_CS_MASK;
|
||||
mcr |= TXX9_NDFMCR_CS(txx9_priv->cs);
|
||||
}
|
||||
txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
|
||||
}
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
txx9ndfmc_write(dev, cmd & 0xff, TXX9_NDFDTR);
|
||||
if (plat->flags & NDFMC_PLAT_FLAG_DUMMYWRITE) {
|
||||
/* dummy write to update external latch */
|
||||
if ((ctrl & NAND_CTRL_CHANGE) && cmd == NAND_CMD_NONE)
|
||||
txx9ndfmc_write(dev, 0, TXX9_NDFDTR);
|
||||
}
|
||||
mmiowb();
|
||||
}
|
||||
|
||||
static int txx9ndfmc_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
|
||||
return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY);
|
||||
}
|
||||
|
||||
static int txx9ndfmc_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
|
||||
uint8_t *ecc_code)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int eccbytes;
|
||||
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
||||
|
||||
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR);
|
||||
for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) {
|
||||
ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||
ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||
ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||
ecc_code += 3;
|
||||
}
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int txx9ndfmc_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int eccsize;
|
||||
int corrected = 0;
|
||||
int stat;
|
||||
|
||||
for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) {
|
||||
stat = __nand_correct_data(buf, read_ecc, calc_ecc, 256);
|
||||
if (stat < 0)
|
||||
return stat;
|
||||
corrected += stat;
|
||||
buf += 256;
|
||||
read_ecc += 3;
|
||||
calc_ecc += 3;
|
||||
}
|
||||
return corrected;
|
||||
}
|
||||
|
||||
static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
||||
|
||||
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_RESET, TXX9_NDFMCR);
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_ON, TXX9_NDFMCR);
|
||||
}
|
||||
|
||||
static void txx9ndfmc_initialize(struct platform_device *dev)
|
||||
{
|
||||
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
||||
int tmout = 100;
|
||||
|
||||
if (plat->flags & NDFMC_PLAT_FLAG_NO_RSTR)
|
||||
; /* no NDFRSTR. Write to NDFSPR resets the NDFMC. */
|
||||
else {
|
||||
/* reset NDFMC */
|
||||
txx9ndfmc_write(dev,
|
||||
txx9ndfmc_read(dev, TXX9_NDFRSTR) |
|
||||
TXX9_NDFRSTR_RST,
|
||||
TXX9_NDFRSTR);
|
||||
while (txx9ndfmc_read(dev, TXX9_NDFRSTR) & TXX9_NDFRSTR_RST) {
|
||||
if (--tmout == 0) {
|
||||
dev_err(&dev->dev, "reset failed.\n");
|
||||
break;
|
||||
}
|
||||
udelay(1);
|
||||
}
|
||||
}
|
||||
/* setup Hold Time, Strobe Pulse Width */
|
||||
txx9ndfmc_write(dev, (drvdata->hold << 4) | drvdata->spw, TXX9_NDFSPR);
|
||||
txx9ndfmc_write(dev,
|
||||
(plat->flags & NDFMC_PLAT_FLAG_USE_BSPRT) ?
|
||||
TXX9_NDFMCR_BSPRT : 0, TXX9_NDFMCR);
|
||||
}
|
||||
|
||||
#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \
|
||||
DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000)
|
||||
|
||||
static int txx9ndfmc_nand_scan(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int ret;
|
||||
|
||||
ret = nand_scan_ident(mtd, 1, NULL);
|
||||
if (!ret) {
|
||||
if (mtd->writesize >= 512) {
|
||||
/* Hardware ECC 6 byte ECC per 512 Byte data */
|
||||
chip->ecc.size = 512;
|
||||
chip->ecc.bytes = 6;
|
||||
}
|
||||
ret = nand_scan_tail(mtd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||
{
|
||||
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
int hold, spw;
|
||||
int i;
|
||||
struct txx9ndfmc_drvdata *drvdata;
|
||||
unsigned long gbusclk = plat->gbus_clock;
|
||||
struct resource *res;
|
||||
|
||||
drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
||||
drvdata->base = devm_ioremap_resource(&dev->dev, res);
|
||||
if (IS_ERR(drvdata->base))
|
||||
return PTR_ERR(drvdata->base);
|
||||
|
||||
hold = plat->hold ?: 20; /* tDH */
|
||||
spw = plat->spw ?: 90; /* max(tREADID, tWP, tRP) */
|
||||
|
||||
hold = TXX9NDFMC_NS_TO_CYC(gbusclk, hold);
|
||||
spw = TXX9NDFMC_NS_TO_CYC(gbusclk, spw);
|
||||
if (plat->flags & NDFMC_PLAT_FLAG_HOLDADD)
|
||||
hold -= 2; /* actual hold time : (HOLD + 2) BUSCLK */
|
||||
spw -= 1; /* actual wait time : (SPW + 1) BUSCLK */
|
||||
hold = clamp(hold, 1, 15);
|
||||
drvdata->hold = hold;
|
||||
spw = clamp(spw, 1, 15);
|
||||
drvdata->spw = spw;
|
||||
dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n",
|
||||
(gbusclk + 500000) / 1000000, hold, spw);
|
||||
|
||||
spin_lock_init(&drvdata->hw_control.lock);
|
||||
init_waitqueue_head(&drvdata->hw_control.wq);
|
||||
|
||||
platform_set_drvdata(dev, drvdata);
|
||||
txx9ndfmc_initialize(dev);
|
||||
|
||||
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
|
||||
struct txx9ndfmc_priv *txx9_priv;
|
||||
struct nand_chip *chip;
|
||||
struct mtd_info *mtd;
|
||||
|
||||
if (!(plat->ch_mask & (1 << i)))
|
||||
continue;
|
||||
txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv),
|
||||
GFP_KERNEL);
|
||||
if (!txx9_priv)
|
||||
continue;
|
||||
chip = &txx9_priv->chip;
|
||||
mtd = &txx9_priv->mtd;
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
mtd->priv = chip;
|
||||
|
||||
chip->read_byte = txx9ndfmc_read_byte;
|
||||
chip->read_buf = txx9ndfmc_read_buf;
|
||||
chip->write_buf = txx9ndfmc_write_buf;
|
||||
chip->cmd_ctrl = txx9ndfmc_cmd_ctrl;
|
||||
chip->dev_ready = txx9ndfmc_dev_ready;
|
||||
chip->ecc.calculate = txx9ndfmc_calculate_ecc;
|
||||
chip->ecc.correct = txx9ndfmc_correct_data;
|
||||
chip->ecc.hwctl = txx9ndfmc_enable_hwecc;
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
/* txx9ndfmc_nand_scan will overwrite ecc.size and ecc.bytes */
|
||||
chip->ecc.size = 256;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecc.strength = 1;
|
||||
chip->chip_delay = 100;
|
||||
chip->controller = &drvdata->hw_control;
|
||||
|
||||
chip->priv = txx9_priv;
|
||||
txx9_priv->dev = dev;
|
||||
|
||||
if (plat->ch_mask != 1) {
|
||||
txx9_priv->cs = i;
|
||||
txx9_priv->mtdname = kasprintf(GFP_KERNEL, "%s.%u",
|
||||
dev_name(&dev->dev), i);
|
||||
} else {
|
||||
txx9_priv->cs = -1;
|
||||
txx9_priv->mtdname = kstrdup(dev_name(&dev->dev),
|
||||
GFP_KERNEL);
|
||||
}
|
||||
if (!txx9_priv->mtdname) {
|
||||
kfree(txx9_priv);
|
||||
dev_err(&dev->dev, "Unable to allocate MTD name.\n");
|
||||
continue;
|
||||
}
|
||||
if (plat->wide_mask & (1 << i))
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
if (txx9ndfmc_nand_scan(mtd)) {
|
||||
kfree(txx9_priv->mtdname);
|
||||
kfree(txx9_priv);
|
||||
continue;
|
||||
}
|
||||
mtd->name = txx9_priv->mtdname;
|
||||
|
||||
mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
|
||||
drvdata->mtds[i] = mtd;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __exit txx9ndfmc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
if (!drvdata)
|
||||
return 0;
|
||||
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
|
||||
struct mtd_info *mtd = drvdata->mtds[i];
|
||||
struct nand_chip *chip;
|
||||
struct txx9ndfmc_priv *txx9_priv;
|
||||
|
||||
if (!mtd)
|
||||
continue;
|
||||
chip = mtd->priv;
|
||||
txx9_priv = chip->priv;
|
||||
|
||||
nand_release(mtd);
|
||||
kfree(txx9_priv->mtdname);
|
||||
kfree(txx9_priv);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int txx9ndfmc_resume(struct platform_device *dev)
|
||||
{
|
||||
if (platform_get_drvdata(dev))
|
||||
txx9ndfmc_initialize(dev);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define txx9ndfmc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver txx9ndfmc_driver = {
|
||||
.remove = __exit_p(txx9ndfmc_remove),
|
||||
.resume = txx9ndfmc_resume,
|
||||
.driver = {
|
||||
.name = "txx9ndfmc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver_probe(txx9ndfmc_driver, txx9ndfmc_probe);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver");
|
||||
MODULE_ALIAS("platform:txx9ndfmc");
|
||||
201
drivers/mtd/nand/xway_nand.c
Normal file
201
drivers/mtd/nand/xway_nand.c
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* Copyright © 2012 John Crispin <blogic@openwrt.org>
|
||||
*/
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <lantiq_soc.h>
|
||||
|
||||
/* nand registers */
|
||||
#define EBU_ADDSEL1 0x24
|
||||
#define EBU_NAND_CON 0xB0
|
||||
#define EBU_NAND_WAIT 0xB4
|
||||
#define EBU_NAND_ECC0 0xB8
|
||||
#define EBU_NAND_ECC_AC 0xBC
|
||||
|
||||
/* nand commands */
|
||||
#define NAND_CMD_ALE (1 << 2)
|
||||
#define NAND_CMD_CLE (1 << 3)
|
||||
#define NAND_CMD_CS (1 << 4)
|
||||
#define NAND_WRITE_CMD_RESET 0xff
|
||||
#define NAND_WRITE_CMD (NAND_CMD_CS | NAND_CMD_CLE)
|
||||
#define NAND_WRITE_ADDR (NAND_CMD_CS | NAND_CMD_ALE)
|
||||
#define NAND_WRITE_DATA (NAND_CMD_CS)
|
||||
#define NAND_READ_DATA (NAND_CMD_CS)
|
||||
#define NAND_WAIT_WR_C (1 << 3)
|
||||
#define NAND_WAIT_RD (0x1)
|
||||
|
||||
/* we need to tel the ebu which addr we mapped the nand to */
|
||||
#define ADDSEL1_MASK(x) (x << 4)
|
||||
#define ADDSEL1_REGEN 1
|
||||
|
||||
/* we need to tell the EBU that we have nand attached and set it up properly */
|
||||
#define BUSCON1_SETUP (1 << 22)
|
||||
#define BUSCON1_BCGEN_RES (0x3 << 12)
|
||||
#define BUSCON1_WAITWRC2 (2 << 8)
|
||||
#define BUSCON1_WAITRDC2 (2 << 6)
|
||||
#define BUSCON1_HOLDC1 (1 << 4)
|
||||
#define BUSCON1_RECOVC1 (1 << 2)
|
||||
#define BUSCON1_CMULT4 1
|
||||
|
||||
#define NAND_CON_CE (1 << 20)
|
||||
#define NAND_CON_OUT_CS1 (1 << 10)
|
||||
#define NAND_CON_IN_CS1 (1 << 8)
|
||||
#define NAND_CON_PRE_P (1 << 7)
|
||||
#define NAND_CON_WP_P (1 << 6)
|
||||
#define NAND_CON_SE_P (1 << 5)
|
||||
#define NAND_CON_CS_P (1 << 4)
|
||||
#define NAND_CON_CSMUX (1 << 1)
|
||||
#define NAND_CON_NANDM 1
|
||||
|
||||
static void xway_reset_chip(struct nand_chip *chip)
|
||||
{
|
||||
unsigned long nandaddr = (unsigned long) chip->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
|
||||
nandaddr &= ~NAND_WRITE_ADDR;
|
||||
nandaddr |= NAND_WRITE_CMD;
|
||||
|
||||
/* finish with a reset */
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
writeb(NAND_WRITE_CMD_RESET, (void __iomem *) nandaddr);
|
||||
while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0)
|
||||
;
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
}
|
||||
|
||||
static void xway_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
|
||||
switch (chip) {
|
||||
case -1:
|
||||
ltq_ebu_w32_mask(NAND_CON_CE, 0, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(NAND_CON_NANDM, 0, EBU_NAND_CON);
|
||||
break;
|
||||
case 0:
|
||||
ltq_ebu_w32_mask(0, NAND_CON_NANDM, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(0, NAND_CON_CE, EBU_NAND_CON);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static void xway_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
nandaddr &= ~(NAND_WRITE_CMD | NAND_WRITE_ADDR);
|
||||
if (ctrl & NAND_CLE)
|
||||
nandaddr |= NAND_WRITE_CMD;
|
||||
else
|
||||
nandaddr |= NAND_WRITE_ADDR;
|
||||
this->IO_ADDR_W = (void __iomem *) nandaddr;
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE) {
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
writeb(cmd, this->IO_ADDR_W);
|
||||
while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0)
|
||||
;
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int xway_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_RD;
|
||||
}
|
||||
|
||||
static unsigned char xway_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_R;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
ret = ltq_r8((void __iomem *)(nandaddr + NAND_READ_DATA));
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int xway_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this = platform_get_drvdata(pdev);
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;
|
||||
const __be32 *cs = of_get_property(pdev->dev.of_node,
|
||||
"lantiq,cs", NULL);
|
||||
u32 cs_flag = 0;
|
||||
|
||||
/* load our CS from the DT. Either we find a valid 1 or default to 0 */
|
||||
if (cs && (*cs == 1))
|
||||
cs_flag = NAND_CON_IN_CS1 | NAND_CON_OUT_CS1;
|
||||
|
||||
/* setup the EBU to run in NAND mode on our base addr */
|
||||
ltq_ebu_w32(CPHYSADDR(nandaddr)
|
||||
| ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1);
|
||||
|
||||
ltq_ebu_w32(BUSCON1_SETUP | BUSCON1_BCGEN_RES | BUSCON1_WAITWRC2
|
||||
| BUSCON1_WAITRDC2 | BUSCON1_HOLDC1 | BUSCON1_RECOVC1
|
||||
| BUSCON1_CMULT4, LTQ_EBU_BUSCON1);
|
||||
|
||||
ltq_ebu_w32(NAND_CON_NANDM | NAND_CON_CSMUX | NAND_CON_CS_P
|
||||
| NAND_CON_SE_P | NAND_CON_WP_P | NAND_CON_PRE_P
|
||||
| cs_flag, EBU_NAND_CON);
|
||||
|
||||
/* finish with a reset */
|
||||
xway_reset_chip(this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allow users to override the partition in DT using the cmdline */
|
||||
static const char *part_probes[] = { "cmdlinepart", "ofpart", NULL };
|
||||
|
||||
static struct platform_nand_data xway_nand_data = {
|
||||
.chip = {
|
||||
.nr_chips = 1,
|
||||
.chip_delay = 30,
|
||||
.part_probe_types = part_probes,
|
||||
},
|
||||
.ctrl = {
|
||||
.probe = xway_nand_probe,
|
||||
.cmd_ctrl = xway_cmd_ctrl,
|
||||
.dev_ready = xway_dev_ready,
|
||||
.select_chip = xway_select_chip,
|
||||
.read_byte = xway_read_byte,
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Try to find the node inside the DT. If it is available attach out
|
||||
* platform_nand_data
|
||||
*/
|
||||
static int __init xway_register_nand(void)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct platform_device *pdev;
|
||||
|
||||
node = of_find_compatible_node(NULL, NULL, "lantiq,nand-xway");
|
||||
if (!node)
|
||||
return -ENOENT;
|
||||
pdev = of_find_device_by_node(node);
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
pdev->dev.platform_data = &xway_nand_data;
|
||||
of_node_put(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
subsys_initcall(xway_register_nand);
|
||||
Loading…
Add table
Add a link
Reference in a new issue