mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-08 15:04:45 +00:00
This pull request contains only one notable change:
* Addition of the MTK NAND controller driver And a bunch of specific NAND driver improvements/fixes. Here are the changes that are worth mentioning: * A few fixes/improvements for the xway NAND controller driver * A few fixes for the sunxi NAND controller driver * Support for DMA in the sunxi NAND driver * Support for the sunxi NAND controller IP embedded in A23/A33 SoCs * Addition for bitflips detection in erased pages to the brcmnand driver * Support for new brcmnand IPs * Update of the OMAP-GPMC binding to support DMA channel description -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJXg0MnAAoJEGXtNgF+CLcABPcP/jSXHzYUk0vEPNLV6ToaZPgW +Ct9oQEt6A96M/JOekAo5EyO04wD/CJncXao4ARy7tfvb7MG9bXA7FXsx2ihk22s npmMV7zOSJisTpYKtX9gI4hJ8Jv8fusm+UIE78NiTJdhbQ/dQrReY7XAyUQin8lV JIpWRfv2paQeBHn6MLj3MxvNCFxkQhHQOG6ohc2zCkfrQw2rKDWYLnhGLbbTHQV9 kxem+lf5FLk5TxN41NgnVcCfEnxawnNTmbs/i0tj6IqKwFR5fOO6Tcvyi/ZeL75c 3/78hxo3NMVuR9AUqbypbIJZayZiDNQCPpQUcgFbvrToRJoVA8u21+0C/IcKfzZc mRYtMjU7+PzfbnKOo3IW/6Rbf2kc2q+6p0tMMbjPquWs8oPPRL8HMQu0h+PXXtyw mz7qVGFjvUUA/vA9ebaUWwXYeHpQ2490wVOhvp523686IZv9kJhhxQWTqTYqCnZK Ul2+UEYSpNH4ebnOeGbqyYVqee0+zokGMlEQZLyTjsm3rox13MCpOmhr1Qwl480y fPIb2BLAsXdNqWSo6k7Bg/pTzDoq+i+w3vlKuQnKhOySEnPoV/D4MsAcwx4wkuiD xxq/bu14vhnxF7Dbmjb3cylhQAz4xJomwixNjGfOg/+AyQQYh1iu0CY026N3F1VV FmnnhJgQUi3PFGAupMOb =8Aeb -----END PGP SIGNATURE----- Merge tag 'nand/for-4.8' of github.com:linux-nand/linux into mtd Pull NAND changes from Boris Brezillon: """ This pull request contains only one notable change: * Addition of the MTK NAND controller driver And a bunch of specific NAND driver improvements/fixes. Here are the changes that are worth mentioning: * A few fixes/improvements for the xway NAND controller driver * A few fixes for the sunxi NAND controller driver * Support for DMA in the sunxi NAND driver * Support for the sunxi NAND controller IP embedded in A23/A33 SoCs * Addition for bitflips detection in erased pages to the brcmnand driver * Support for new brcmnand IPs * Update of the OMAP-GPMC binding to support DMA channel description """
This commit is contained in:
commit
1ed106914a
@ -46,6 +46,10 @@ Required properties:
|
||||
0 maps to GPMC_WAIT0 pin.
|
||||
- gpio-cells: Must be set to 2
|
||||
|
||||
Required properties when using NAND prefetch dma:
|
||||
- dmas GPMC NAND prefetch dma channel
|
||||
- dma-names Must be set to "rxtx"
|
||||
|
||||
Timing properties for child nodes. All are optional and default to 0.
|
||||
|
||||
- gpmc,sync-clk-ps: Minimum clock period for synchronous mode, in picoseconds
|
||||
@ -137,7 +141,8 @@ Example for an AM33xx board:
|
||||
ti,hwmods = "gpmc";
|
||||
reg = <0x50000000 0x2000>;
|
||||
interrupts = <100>;
|
||||
|
||||
dmas = <&edma 52 0>;
|
||||
dma-names = "rxtx";
|
||||
gpmc,num-cs = <8>;
|
||||
gpmc,num-waitpins = <2>;
|
||||
#address-cells = <2>;
|
||||
|
@ -27,6 +27,7 @@ Required properties:
|
||||
brcm,brcmnand-v6.2
|
||||
brcm,brcmnand-v7.0
|
||||
brcm,brcmnand-v7.1
|
||||
brcm,brcmnand-v7.2
|
||||
brcm,brcmnand
|
||||
- reg : the register start and length for NAND register region.
|
||||
(optional) Flash DMA register range (if present)
|
||||
|
@ -39,7 +39,7 @@ Optional properties:
|
||||
|
||||
"prefetch-polled" Prefetch polled mode (default)
|
||||
"polled" Polled mode, without prefetch
|
||||
"prefetch-dma" Prefetch enabled sDMA mode
|
||||
"prefetch-dma" Prefetch enabled DMA mode
|
||||
"prefetch-irq" Prefetch enabled irq mode
|
||||
|
||||
- elm_id: <deprecated> use "ti,elm-id" instead
|
||||
|
160
Documentation/devicetree/bindings/mtd/mtk-nand.txt
Normal file
160
Documentation/devicetree/bindings/mtd/mtk-nand.txt
Normal file
@ -0,0 +1,160 @@
|
||||
MTK SoCs NAND FLASH controller (NFC) DT binding
|
||||
|
||||
This file documents the device tree bindings for MTK SoCs NAND controllers.
|
||||
The functional split of the controller requires two drivers to operate:
|
||||
the nand controller interface driver and the ECC engine driver.
|
||||
|
||||
The hardware description for both devices must be captured as device
|
||||
tree nodes.
|
||||
|
||||
1) NFC NAND Controller Interface (NFI):
|
||||
=======================================
|
||||
|
||||
The first part of NFC is NAND Controller Interface (NFI) HW.
|
||||
Required NFI properties:
|
||||
- compatible: Should be "mediatek,mtxxxx-nfc".
|
||||
- reg: Base physical address and size of NFI.
|
||||
- interrupts: Interrupts of NFI.
|
||||
- clocks: NFI required clocks.
|
||||
- clock-names: NFI clocks internal name.
|
||||
- status: Disabled default. Then set "okay" by platform.
|
||||
- ecc-engine: Required ECC Engine node.
|
||||
- #address-cells: NAND chip index, should be 1.
|
||||
- #size-cells: Should be 0.
|
||||
|
||||
Example:
|
||||
|
||||
nandc: nfi@1100d000 {
|
||||
compatible = "mediatek,mt2701-nfc";
|
||||
reg = <0 0x1100d000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_LOW>;
|
||||
clocks = <&pericfg CLK_PERI_NFI>,
|
||||
<&pericfg CLK_PERI_NFI_PAD>;
|
||||
clock-names = "nfi_clk", "pad_clk";
|
||||
status = "disabled";
|
||||
ecc-engine = <&bch>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
Platform related properties, should be set in {platform_name}.dts:
|
||||
- children nodes: NAND chips.
|
||||
|
||||
Children nodes properties:
|
||||
- reg: Chip Select Signal, default 0.
|
||||
Set as reg = <0>, <1> when need 2 CS.
|
||||
Optional:
|
||||
- nand-on-flash-bbt: Store BBT on NAND Flash.
|
||||
- nand-ecc-mode: the NAND ecc mode (check driver for supported modes)
|
||||
- nand-ecc-step-size: Number of data bytes covered by a single ECC step.
|
||||
valid values: 512 and 1024.
|
||||
1024 is recommended for large page NANDs.
|
||||
- nand-ecc-strength: Number of bits to correct per ECC step.
|
||||
The valid values that the controller supports are: 4, 6,
|
||||
8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 40, 44,
|
||||
48, 52, 56, 60.
|
||||
The strength should be calculated as follows:
|
||||
E = (S - F) * 8 / 14
|
||||
S = O / (P / Q)
|
||||
E : nand-ecc-strength.
|
||||
S : spare size per sector.
|
||||
F : FDM size, should be in the range [1,8].
|
||||
It is used to store free oob data.
|
||||
O : oob size.
|
||||
P : page size.
|
||||
Q : nand-ecc-step-size.
|
||||
If the result does not match any one of the listed
|
||||
choices above, please select the smaller valid value from
|
||||
the list.
|
||||
(otherwise the driver will do the adjustment at runtime)
|
||||
- pinctrl-names: Default NAND pin GPIO setting name.
|
||||
- pinctrl-0: GPIO setting node.
|
||||
|
||||
Example:
|
||||
&pio {
|
||||
nand_pins_default: nanddefault {
|
||||
pins_dat {
|
||||
pinmux = <MT2701_PIN_111_MSDC0_DAT7__FUNC_NLD7>,
|
||||
<MT2701_PIN_112_MSDC0_DAT6__FUNC_NLD6>,
|
||||
<MT2701_PIN_114_MSDC0_DAT4__FUNC_NLD4>,
|
||||
<MT2701_PIN_118_MSDC0_DAT3__FUNC_NLD3>,
|
||||
<MT2701_PIN_121_MSDC0_DAT0__FUNC_NLD0>,
|
||||
<MT2701_PIN_120_MSDC0_DAT1__FUNC_NLD1>,
|
||||
<MT2701_PIN_113_MSDC0_DAT5__FUNC_NLD5>,
|
||||
<MT2701_PIN_115_MSDC0_RSTB__FUNC_NLD8>,
|
||||
<MT2701_PIN_119_MSDC0_DAT2__FUNC_NLD2>;
|
||||
input-enable;
|
||||
drive-strength = <MTK_DRIVE_8mA>;
|
||||
bias-pull-up;
|
||||
};
|
||||
|
||||
pins_we {
|
||||
pinmux = <MT2701_PIN_117_MSDC0_CLK__FUNC_NWEB>;
|
||||
drive-strength = <MTK_DRIVE_8mA>;
|
||||
bias-pull-up = <MTK_PUPD_SET_R1R0_10>;
|
||||
};
|
||||
|
||||
pins_ale {
|
||||
pinmux = <MT2701_PIN_116_MSDC0_CMD__FUNC_NALE>;
|
||||
drive-strength = <MTK_DRIVE_8mA>;
|
||||
bias-pull-down = <MTK_PUPD_SET_R1R0_10>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&nandc {
|
||||
status = "okay";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&nand_pins_default>;
|
||||
nand@0 {
|
||||
reg = <0>;
|
||||
nand-on-flash-bbt;
|
||||
nand-ecc-mode = "hw";
|
||||
nand-ecc-strength = <24>;
|
||||
nand-ecc-step-size = <1024>;
|
||||
};
|
||||
};
|
||||
|
||||
NAND chip optional subnodes:
|
||||
- Partitions, see Documentation/devicetree/bindings/mtd/partition.txt
|
||||
|
||||
Example:
|
||||
nand@0 {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
preloader@0 {
|
||||
label = "pl";
|
||||
read-only;
|
||||
reg = <0x00000000 0x00400000>;
|
||||
};
|
||||
android@0x00400000 {
|
||||
label = "android";
|
||||
reg = <0x00400000 0x12c00000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
2) ECC Engine:
|
||||
==============
|
||||
|
||||
Required BCH properties:
|
||||
- compatible: Should be "mediatek,mtxxxx-ecc".
|
||||
- reg: Base physical address and size of ECC.
|
||||
- interrupts: Interrupts of ECC.
|
||||
- clocks: ECC required clocks.
|
||||
- clock-names: ECC clocks internal name.
|
||||
- status: Disabled default. Then set "okay" by platform.
|
||||
|
||||
Example:
|
||||
|
||||
bch: ecc@1100e000 {
|
||||
compatible = "mediatek,mt2701-ecc";
|
||||
reg = <0 0x1100e000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_LOW>;
|
||||
clocks = <&pericfg CLK_PERI_NFI_ECC>;
|
||||
clock-names = "nfiecc_clk";
|
||||
status = "disabled";
|
||||
};
|
@ -11,10 +11,16 @@ Required properties:
|
||||
* "ahb" : AHB gating clock
|
||||
* "mod" : nand controller clock
|
||||
|
||||
Optional properties:
|
||||
- dmas : shall reference DMA channel associated to the NAND controller.
|
||||
- dma-names : shall be "rxtx".
|
||||
|
||||
Optional children nodes:
|
||||
Children nodes represent the available nand chips.
|
||||
|
||||
Optional properties:
|
||||
- reset : phandle + reset specifier pair
|
||||
- reset-names : must contain "ahb"
|
||||
- allwinner,rb : shall contain the native Ready/Busy ids.
|
||||
or
|
||||
- rb-gpios : shall contain the gpios used as R/B pins.
|
||||
|
@ -539,7 +539,6 @@ config MTD_NAND_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).
|
||||
@ -563,4 +562,11 @@ config MTD_NAND_QCOM
|
||||
Enables support for NAND flash chips on SoCs containing the EBI2 NAND
|
||||
controller. This controller is found on IPQ806x SoC.
|
||||
|
||||
config MTD_NAND_MTK
|
||||
tristate "Support for NAND controller on MTK SoCs"
|
||||
depends on HAS_DMA
|
||||
help
|
||||
Enables support for NAND controller on MTK SoCs.
|
||||
This controller is found on mt27xx, mt81xx, mt65xx SoCs.
|
||||
|
||||
endif # MTD_NAND
|
||||
|
@ -57,5 +57,6 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
|
||||
obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
|
||||
obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o mtk_ecc.o
|
||||
|
||||
nand-objs := nand_base.o nand_bbt.o nand_timings.o
|
||||
|
@ -340,6 +340,36 @@ static const u16 brcmnand_regs_v71[] = {
|
||||
[BRCMNAND_FC_BASE] = 0x400,
|
||||
};
|
||||
|
||||
/* BRCMNAND v7.2 */
|
||||
static const u16 brcmnand_regs_v72[] = {
|
||||
[BRCMNAND_CMD_START] = 0x04,
|
||||
[BRCMNAND_CMD_EXT_ADDRESS] = 0x08,
|
||||
[BRCMNAND_CMD_ADDRESS] = 0x0c,
|
||||
[BRCMNAND_INTFC_STATUS] = 0x14,
|
||||
[BRCMNAND_CS_SELECT] = 0x18,
|
||||
[BRCMNAND_CS_XOR] = 0x1c,
|
||||
[BRCMNAND_LL_OP] = 0x20,
|
||||
[BRCMNAND_CS0_BASE] = 0x50,
|
||||
[BRCMNAND_CS1_BASE] = 0,
|
||||
[BRCMNAND_CORR_THRESHOLD] = 0xdc,
|
||||
[BRCMNAND_CORR_THRESHOLD_EXT] = 0xe0,
|
||||
[BRCMNAND_UNCORR_COUNT] = 0xfc,
|
||||
[BRCMNAND_CORR_COUNT] = 0x100,
|
||||
[BRCMNAND_CORR_EXT_ADDR] = 0x10c,
|
||||
[BRCMNAND_CORR_ADDR] = 0x110,
|
||||
[BRCMNAND_UNCORR_EXT_ADDR] = 0x114,
|
||||
[BRCMNAND_UNCORR_ADDR] = 0x118,
|
||||
[BRCMNAND_SEMAPHORE] = 0x150,
|
||||
[BRCMNAND_ID] = 0x194,
|
||||
[BRCMNAND_ID_EXT] = 0x198,
|
||||
[BRCMNAND_LL_RDATA] = 0x19c,
|
||||
[BRCMNAND_OOB_READ_BASE] = 0x200,
|
||||
[BRCMNAND_OOB_READ_10_BASE] = 0,
|
||||
[BRCMNAND_OOB_WRITE_BASE] = 0x400,
|
||||
[BRCMNAND_OOB_WRITE_10_BASE] = 0,
|
||||
[BRCMNAND_FC_BASE] = 0x600,
|
||||
};
|
||||
|
||||
enum brcmnand_cs_reg {
|
||||
BRCMNAND_CS_CFG_EXT = 0,
|
||||
BRCMNAND_CS_CFG,
|
||||
@ -435,7 +465,9 @@ static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
||||
}
|
||||
|
||||
/* Register offsets */
|
||||
if (ctrl->nand_version >= 0x0701)
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
ctrl->reg_offsets = brcmnand_regs_v72;
|
||||
else if (ctrl->nand_version >= 0x0701)
|
||||
ctrl->reg_offsets = brcmnand_regs_v71;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
ctrl->reg_offsets = brcmnand_regs_v60;
|
||||
@ -480,7 +512,9 @@ static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
||||
}
|
||||
|
||||
/* Maximum spare area sector size (per 512B) */
|
||||
if (ctrl->nand_version >= 0x0600)
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
ctrl->max_oob = 128;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
ctrl->max_oob = 64;
|
||||
else if (ctrl->nand_version >= 0x0500)
|
||||
ctrl->max_oob = 32;
|
||||
@ -583,14 +617,20 @@ static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
|
||||
enum brcmnand_reg reg = BRCMNAND_CORR_THRESHOLD;
|
||||
int cs = host->cs;
|
||||
|
||||
if (ctrl->nand_version >= 0x0600)
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
bits = 7;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
bits = 6;
|
||||
else if (ctrl->nand_version >= 0x0500)
|
||||
bits = 5;
|
||||
else
|
||||
bits = 4;
|
||||
|
||||
if (ctrl->nand_version >= 0x0600) {
|
||||
if (ctrl->nand_version >= 0x0702) {
|
||||
if (cs >= 4)
|
||||
reg = BRCMNAND_CORR_THRESHOLD_EXT;
|
||||
shift = (cs % 4) * bits;
|
||||
} else if (ctrl->nand_version >= 0x0600) {
|
||||
if (cs >= 5)
|
||||
reg = BRCMNAND_CORR_THRESHOLD_EXT;
|
||||
shift = (cs % 5) * bits;
|
||||
@ -631,19 +671,28 @@ enum {
|
||||
|
||||
static inline u32 brcmnand_spare_area_mask(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
if (ctrl->nand_version >= 0x0600)
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
return GENMASK(7, 0);
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
return GENMASK(6, 0);
|
||||
else
|
||||
return GENMASK(5, 0);
|
||||
}
|
||||
|
||||
#define NAND_ACC_CONTROL_ECC_SHIFT 16
|
||||
#define NAND_ACC_CONTROL_ECC_EXT_SHIFT 13
|
||||
|
||||
static inline u32 brcmnand_ecc_level_mask(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
u32 mask = (ctrl->nand_version >= 0x0600) ? 0x1f : 0x0f;
|
||||
|
||||
return mask << NAND_ACC_CONTROL_ECC_SHIFT;
|
||||
mask <<= NAND_ACC_CONTROL_ECC_SHIFT;
|
||||
|
||||
/* v7.2 includes additional ECC levels */
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
mask |= 0x7 << NAND_ACC_CONTROL_ECC_EXT_SHIFT;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static void brcmnand_set_ecc_enabled(struct brcmnand_host *host, int en)
|
||||
@ -667,7 +716,9 @@ static void brcmnand_set_ecc_enabled(struct brcmnand_host *host, int en)
|
||||
|
||||
static inline int brcmnand_sector_1k_shift(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
if (ctrl->nand_version >= 0x0600)
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
return 9;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
return 7;
|
||||
else if (ctrl->nand_version >= 0x0500)
|
||||
return 6;
|
||||
@ -773,10 +824,16 @@ enum brcmnand_llop_type {
|
||||
* Internal support functions
|
||||
***********************************************************************/
|
||||
|
||||
static inline bool is_hamming_ecc(struct brcmnand_cfg *cfg)
|
||||
static inline bool is_hamming_ecc(struct brcmnand_controller *ctrl,
|
||||
struct brcmnand_cfg *cfg)
|
||||
{
|
||||
return cfg->sector_size_1k == 0 && cfg->spare_area_size == 16 &&
|
||||
cfg->ecc_level == 15;
|
||||
if (ctrl->nand_version <= 0x0701)
|
||||
return cfg->sector_size_1k == 0 && cfg->spare_area_size == 16 &&
|
||||
cfg->ecc_level == 15;
|
||||
else
|
||||
return cfg->sector_size_1k == 0 && ((cfg->spare_area_size == 16 &&
|
||||
cfg->ecc_level == 15) ||
|
||||
(cfg->spare_area_size == 28 && cfg->ecc_level == 16));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -931,7 +988,7 @@ static int brcmstb_choose_ecc_layout(struct brcmnand_host *host)
|
||||
if (p->sector_size_1k)
|
||||
ecc_level <<= 1;
|
||||
|
||||
if (is_hamming_ecc(p)) {
|
||||
if (is_hamming_ecc(host->ctrl, p)) {
|
||||
ecc->bytes = 3 * sectors;
|
||||
mtd_set_ooblayout(mtd, &brcmnand_hamming_ooblayout_ops);
|
||||
return 0;
|
||||
@ -1545,6 +1602,56 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check a page to see if it is erased (w/ bitflips) after an uncorrectable ECC
|
||||
* error
|
||||
*
|
||||
* Because the HW ECC signals an ECC error if an erase paged has even a single
|
||||
* bitflip, we must check each ECC error to see if it is actually an erased
|
||||
* page with bitflips, not a truly corrupted page.
|
||||
*
|
||||
* On a real error, return a negative error code (-EBADMSG for ECC error), and
|
||||
* buf will contain raw data.
|
||||
* Otherwise, buf gets filled with 0xffs and return the maximum number of
|
||||
* bitflips-per-ECC-sector to the caller.
|
||||
*
|
||||
*/
|
||||
static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, void *buf, u64 addr)
|
||||
{
|
||||
int i, sas;
|
||||
void *oob = chip->oob_poi;
|
||||
int bitflips = 0;
|
||||
int page = addr >> chip->page_shift;
|
||||
int ret;
|
||||
|
||||
if (!buf) {
|
||||
buf = chip->buffers->databuf;
|
||||
/* Invalidate page cache */
|
||||
chip->pagebuf = -1;
|
||||
}
|
||||
|
||||
sas = mtd->oobsize / chip->ecc.steps;
|
||||
|
||||
/* read without ecc for verification */
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
|
||||
ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < chip->ecc.steps; i++, oob += sas) {
|
||||
ret = nand_check_erased_ecc_chunk(buf, chip->ecc.size,
|
||||
oob, sas, NULL, 0,
|
||||
chip->ecc.strength);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bitflips = max(bitflips, ret);
|
||||
}
|
||||
|
||||
return bitflips;
|
||||
}
|
||||
|
||||
static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
u64 addr, unsigned int trans, u32 *buf, u8 *oob)
|
||||
{
|
||||
@ -1552,9 +1659,11 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
u64 err_addr = 0;
|
||||
int err;
|
||||
bool retry = true;
|
||||
|
||||
dev_dbg(ctrl->dev, "read %llx -> %p\n", (unsigned long long)addr, buf);
|
||||
|
||||
try_dmaread:
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_COUNT, 0);
|
||||
|
||||
if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
|
||||
@ -1575,6 +1684,34 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
}
|
||||
|
||||
if (mtd_is_eccerr(err)) {
|
||||
/*
|
||||
* On controller version and 7.0, 7.1 , DMA read after a
|
||||
* prior PIO read that reported uncorrectable error,
|
||||
* the DMA engine captures this error following DMA read
|
||||
* cleared only on subsequent DMA read, so just retry once
|
||||
* to clear a possible false error reported for current DMA
|
||||
* read
|
||||
*/
|
||||
if ((ctrl->nand_version == 0x0700) ||
|
||||
(ctrl->nand_version == 0x0701)) {
|
||||
if (retry) {
|
||||
retry = false;
|
||||
goto try_dmaread;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Controller version 7.2 has hw encoder to detect erased page
|
||||
* bitflips, apply sw verification for older controllers only
|
||||
*/
|
||||
if (ctrl->nand_version < 0x0702) {
|
||||
err = brcmstb_nand_verify_erased_page(mtd, chip, buf,
|
||||
addr);
|
||||
/* erased page bitflips corrected */
|
||||
if (err > 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
dev_dbg(ctrl->dev, "uncorrectable error at 0x%llx\n",
|
||||
(unsigned long long)err_addr);
|
||||
mtd->ecc_stats.failed++;
|
||||
@ -1857,7 +1994,8 @@ static int brcmnand_set_cfg(struct brcmnand_host *host,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void brcmnand_print_cfg(char *buf, struct brcmnand_cfg *cfg)
|
||||
static void brcmnand_print_cfg(struct brcmnand_host *host,
|
||||
char *buf, struct brcmnand_cfg *cfg)
|
||||
{
|
||||
buf += sprintf(buf,
|
||||
"%lluMiB total, %uKiB blocks, %u%s pages, %uB OOB, %u-bit",
|
||||
@ -1868,7 +2006,7 @@ static void brcmnand_print_cfg(char *buf, struct brcmnand_cfg *cfg)
|
||||
cfg->spare_area_size, cfg->device_width);
|
||||
|
||||
/* Account for Hamming ECC and for BCH 512B vs 1KiB sectors */
|
||||
if (is_hamming_ecc(cfg))
|
||||
if (is_hamming_ecc(host->ctrl, cfg))
|
||||
sprintf(buf, ", Hamming ECC");
|
||||
else if (cfg->sector_size_1k)
|
||||
sprintf(buf, ", BCH-%u (1KiB sector)", cfg->ecc_level << 1);
|
||||
@ -1987,7 +2125,7 @@ static int brcmnand_setup_dev(struct brcmnand_host *host)
|
||||
|
||||
brcmnand_set_ecc_enabled(host, 1);
|
||||
|
||||
brcmnand_print_cfg(msg, cfg);
|
||||
brcmnand_print_cfg(host, msg, cfg);
|
||||
dev_info(ctrl->dev, "detected %s\n", msg);
|
||||
|
||||
/* Configure ACC_CONTROL */
|
||||
@ -1995,6 +2133,10 @@ static int brcmnand_setup_dev(struct brcmnand_host *host)
|
||||
tmp = nand_readreg(ctrl, offs);
|
||||
tmp &= ~ACC_CONTROL_PARTIAL_PAGE;
|
||||
tmp &= ~ACC_CONTROL_RD_ERASED;
|
||||
|
||||
/* We need to turn on Read from erased paged protected by ECC */
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
tmp |= ACC_CONTROL_RD_ERASED;
|
||||
tmp &= ~ACC_CONTROL_FAST_PGM_RDIN;
|
||||
if (ctrl->features & BRCMNAND_HAS_PREFETCH) {
|
||||
/*
|
||||
@ -2195,6 +2337,7 @@ static const struct of_device_id brcmnand_of_match[] = {
|
||||
{ .compatible = "brcm,brcmnand-v6.2" },
|
||||
{ .compatible = "brcm,brcmnand-v7.0" },
|
||||
{ .compatible = "brcm,brcmnand-v7.1" },
|
||||
{ .compatible = "brcm,brcmnand-v7.2" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, brcmnand_of_match);
|
||||
|
@ -375,6 +375,6 @@ static struct platform_driver jz4780_bch_driver = {
|
||||
module_platform_driver(jz4780_bch_driver);
|
||||
|
||||
MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
|
||||
MODULE_AUTHOR("Harvey Hunt <harvey.hunt@imgtec.com>");
|
||||
MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
|
||||
MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
@ -412,6 +412,6 @@ static struct platform_driver jz4780_nand_driver = {
|
||||
module_platform_driver(jz4780_nand_driver);
|
||||
|
||||
MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
|
||||
MODULE_AUTHOR("Harvey Hunt <harvey.hunt@imgtec.com>");
|
||||
MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
|
||||
MODULE_DESCRIPTION("Ingenic JZ4780 NAND driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
530
drivers/mtd/nand/mtk_ecc.c
Normal file
530
drivers/mtd/nand/mtk_ecc.c
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* MTK ECC controller driver.
|
||||
* Copyright (C) 2016 MediaTek Inc.
|
||||
* Authors: Xiaolei Li <xiaolei.li@mediatek.com>
|
||||
* Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include "mtk_ecc.h"
|
||||
|
||||
#define ECC_IDLE_MASK BIT(0)
|
||||
#define ECC_IRQ_EN BIT(0)
|
||||
#define ECC_OP_ENABLE (1)
|
||||
#define ECC_OP_DISABLE (0)
|
||||
|
||||
#define ECC_ENCCON (0x00)
|
||||
#define ECC_ENCCNFG (0x04)
|
||||
#define ECC_CNFG_4BIT (0)
|
||||
#define ECC_CNFG_6BIT (1)
|
||||
#define ECC_CNFG_8BIT (2)
|
||||
#define ECC_CNFG_10BIT (3)
|
||||
#define ECC_CNFG_12BIT (4)
|
||||
#define ECC_CNFG_14BIT (5)
|
||||
#define ECC_CNFG_16BIT (6)
|
||||
#define ECC_CNFG_18BIT (7)
|
||||
#define ECC_CNFG_20BIT (8)
|
||||
#define ECC_CNFG_22BIT (9)
|
||||
#define ECC_CNFG_24BIT (0xa)
|
||||
#define ECC_CNFG_28BIT (0xb)
|
||||
#define ECC_CNFG_32BIT (0xc)
|
||||
#define ECC_CNFG_36BIT (0xd)
|
||||
#define ECC_CNFG_40BIT (0xe)
|
||||
#define ECC_CNFG_44BIT (0xf)
|
||||
#define ECC_CNFG_48BIT (0x10)
|
||||
#define ECC_CNFG_52BIT (0x11)
|
||||
#define ECC_CNFG_56BIT (0x12)
|
||||
#define ECC_CNFG_60BIT (0x13)
|
||||
#define ECC_MODE_SHIFT (5)
|
||||
#define ECC_MS_SHIFT (16)
|
||||
#define ECC_ENCDIADDR (0x08)
|
||||
#define ECC_ENCIDLE (0x0C)
|
||||
#define ECC_ENCPAR(x) (0x10 + (x) * sizeof(u32))
|
||||
#define ECC_ENCIRQ_EN (0x80)
|
||||
#define ECC_ENCIRQ_STA (0x84)
|
||||
#define ECC_DECCON (0x100)
|
||||
#define ECC_DECCNFG (0x104)
|
||||
#define DEC_EMPTY_EN BIT(31)
|
||||
#define DEC_CNFG_CORRECT (0x3 << 12)
|
||||
#define ECC_DECIDLE (0x10C)
|
||||
#define ECC_DECENUM0 (0x114)
|
||||
#define ERR_MASK (0x3f)
|
||||
#define ECC_DECDONE (0x124)
|
||||
#define ECC_DECIRQ_EN (0x200)
|
||||
#define ECC_DECIRQ_STA (0x204)
|
||||
|
||||
#define ECC_TIMEOUT (500000)
|
||||
|
||||
#define ECC_IDLE_REG(op) ((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE)
|
||||
#define ECC_CTL_REG(op) ((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON)
|
||||
#define ECC_IRQ_REG(op) ((op) == ECC_ENCODE ? \
|
||||
ECC_ENCIRQ_EN : ECC_DECIRQ_EN)
|
||||
|
||||
struct mtk_ecc {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
|
||||
struct completion done;
|
||||
struct mutex lock;
|
||||
u32 sectors;
|
||||
};
|
||||
|
||||
static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc,
|
||||
enum mtk_ecc_operation op)
|
||||
{
|
||||
struct device *dev = ecc->dev;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = readl_poll_timeout_atomic(ecc->regs + ECC_IDLE_REG(op), val,
|
||||
val & ECC_IDLE_MASK,
|
||||
10, ECC_TIMEOUT);
|
||||
if (ret)
|
||||
dev_warn(dev, "%s NOT idle\n",
|
||||
op == ECC_ENCODE ? "encoder" : "decoder");
|
||||
}
|
||||
|
||||
static irqreturn_t mtk_ecc_irq(int irq, void *id)
|
||||
{
|
||||
struct mtk_ecc *ecc = id;
|
||||
enum mtk_ecc_operation op;
|
||||
u32 dec, enc;
|
||||
|
||||
dec = readw(ecc->regs + ECC_DECIRQ_STA) & ECC_IRQ_EN;
|
||||
if (dec) {
|
||||
op = ECC_DECODE;
|
||||
dec = readw(ecc->regs + ECC_DECDONE);
|
||||
if (dec & ecc->sectors) {
|
||||
ecc->sectors = 0;
|
||||
complete(&ecc->done);
|
||||
} else {
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
} else {
|
||||
enc = readl(ecc->regs + ECC_ENCIRQ_STA) & ECC_IRQ_EN;
|
||||
if (enc) {
|
||||
op = ECC_ENCODE;
|
||||
complete(&ecc->done);
|
||||
} else {
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
writel(0, ecc->regs + ECC_IRQ_REG(op));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void mtk_ecc_config(struct mtk_ecc *ecc, struct mtk_ecc_config *config)
|
||||
{
|
||||
u32 ecc_bit = ECC_CNFG_4BIT, dec_sz, enc_sz;
|
||||
u32 reg;
|
||||
|
||||
switch (config->strength) {
|
||||
case 4:
|
||||
ecc_bit = ECC_CNFG_4BIT;
|
||||
break;
|
||||
case 6:
|
||||
ecc_bit = ECC_CNFG_6BIT;
|
||||
break;
|
||||
case 8:
|
||||
ecc_bit = ECC_CNFG_8BIT;
|
||||
break;
|
||||
case 10:
|
||||
ecc_bit = ECC_CNFG_10BIT;
|
||||
break;
|
||||
case 12:
|
||||
ecc_bit = ECC_CNFG_12BIT;
|
||||
break;
|
||||
case 14:
|
||||
ecc_bit = ECC_CNFG_14BIT;
|
||||
break;
|
||||
case 16:
|
||||
ecc_bit = ECC_CNFG_16BIT;
|
||||
break;
|
||||
case 18:
|
||||
ecc_bit = ECC_CNFG_18BIT;
|
||||
break;
|
||||
case 20:
|
||||
ecc_bit = ECC_CNFG_20BIT;
|
||||
break;
|
||||
case 22:
|
||||
ecc_bit = ECC_CNFG_22BIT;
|
||||
break;
|
||||
case 24:
|
||||
ecc_bit = ECC_CNFG_24BIT;
|
||||
break;
|
||||
case 28:
|
||||
ecc_bit = ECC_CNFG_28BIT;
|
||||
break;
|
||||
case 32:
|
||||
ecc_bit = ECC_CNFG_32BIT;
|
||||
break;
|
||||
case 36:
|
||||
ecc_bit = ECC_CNFG_36BIT;
|
||||
break;
|
||||
case 40:
|
||||
ecc_bit = ECC_CNFG_40BIT;
|
||||
break;
|
||||
case 44:
|
||||
ecc_bit = ECC_CNFG_44BIT;
|
||||
break;
|
||||
case 48:
|
||||
ecc_bit = ECC_CNFG_48BIT;
|
||||
break;
|
||||
case 52:
|
||||
ecc_bit = ECC_CNFG_52BIT;
|
||||
break;
|
||||
case 56:
|
||||
ecc_bit = ECC_CNFG_56BIT;
|
||||
break;
|
||||
case 60:
|
||||
ecc_bit = ECC_CNFG_60BIT;
|
||||
break;
|
||||
default:
|
||||
dev_err(ecc->dev, "invalid strength %d, default to 4 bits\n",
|
||||
config->strength);
|
||||
}
|
||||
|
||||
if (config->op == ECC_ENCODE) {
|
||||
/* configure ECC encoder (in bits) */
|
||||
enc_sz = config->len << 3;
|
||||
|
||||
reg = ecc_bit | (config->mode << ECC_MODE_SHIFT);
|
||||
reg |= (enc_sz << ECC_MS_SHIFT);
|
||||
writel(reg, ecc->regs + ECC_ENCCNFG);
|
||||
|
||||
if (config->mode != ECC_NFI_MODE)
|
||||
writel(lower_32_bits(config->addr),
|
||||
ecc->regs + ECC_ENCDIADDR);
|
||||
|
||||
} else {
|
||||
/* configure ECC decoder (in bits) */
|
||||
dec_sz = (config->len << 3) +
|
||||
config->strength * ECC_PARITY_BITS;
|
||||
|
||||
reg = ecc_bit | (config->mode << ECC_MODE_SHIFT);
|
||||
reg |= (dec_sz << ECC_MS_SHIFT) | DEC_CNFG_CORRECT;
|
||||
reg |= DEC_EMPTY_EN;
|
||||
writel(reg, ecc->regs + ECC_DECCNFG);
|
||||
|
||||
if (config->sectors)
|
||||
ecc->sectors = 1 << (config->sectors - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void mtk_ecc_get_stats(struct mtk_ecc *ecc, struct mtk_ecc_stats *stats,
|
||||
int sectors)
|
||||
{
|
||||
u32 offset, i, err;
|
||||
u32 bitflips = 0;
|
||||
|
||||
stats->corrected = 0;
|
||||
stats->failed = 0;
|
||||
|
||||
for (i = 0; i < sectors; i++) {
|
||||
offset = (i >> 2) << 2;
|
||||
err = readl(ecc->regs + ECC_DECENUM0 + offset);
|
||||
err = err >> ((i % 4) * 8);
|
||||
err &= ERR_MASK;
|
||||
if (err == ERR_MASK) {
|
||||
/* uncorrectable errors */
|
||||
stats->failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
stats->corrected += err;
|
||||
bitflips = max_t(u32, bitflips, err);
|
||||
}
|
||||
|
||||
stats->bitflips = bitflips;
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_get_stats);
|
||||
|
||||
void mtk_ecc_release(struct mtk_ecc *ecc)
|
||||
{
|
||||
clk_disable_unprepare(ecc->clk);
|
||||
put_device(ecc->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_release);
|
||||
|
||||
static void mtk_ecc_hw_init(struct mtk_ecc *ecc)
|
||||
{
|
||||
mtk_ecc_wait_idle(ecc, ECC_ENCODE);
|
||||
writew(ECC_OP_DISABLE, ecc->regs + ECC_ENCCON);
|
||||
|
||||
mtk_ecc_wait_idle(ecc, ECC_DECODE);
|
||||
writel(ECC_OP_DISABLE, ecc->regs + ECC_DECCON);
|
||||
}
|
||||
|
||||
static struct mtk_ecc *mtk_ecc_get(struct device_node *np)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct mtk_ecc *ecc;
|
||||
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev || !platform_get_drvdata(pdev))
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
get_device(&pdev->dev);
|
||||
ecc = platform_get_drvdata(pdev);
|
||||
clk_prepare_enable(ecc->clk);
|
||||
mtk_ecc_hw_init(ecc);
|
||||
|
||||
return ecc;
|
||||
}
|
||||
|
||||
struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node)
|
||||
{
|
||||
struct mtk_ecc *ecc = NULL;
|
||||
struct device_node *np;
|
||||
|
||||
np = of_parse_phandle(of_node, "ecc-engine", 0);
|
||||
if (np) {
|
||||
ecc = mtk_ecc_get(np);
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
return ecc;
|
||||
}
|
||||
EXPORT_SYMBOL(of_mtk_ecc_get);
|
||||
|
||||
int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config)
|
||||
{
|
||||
enum mtk_ecc_operation op = config->op;
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&ecc->lock);
|
||||
if (ret) {
|
||||
dev_err(ecc->dev, "interrupted when attempting to lock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mtk_ecc_wait_idle(ecc, op);
|
||||
mtk_ecc_config(ecc, config);
|
||||
writew(ECC_OP_ENABLE, ecc->regs + ECC_CTL_REG(op));
|
||||
|
||||
init_completion(&ecc->done);
|
||||
writew(ECC_IRQ_EN, ecc->regs + ECC_IRQ_REG(op));
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_enable);
|
||||
|
||||
void mtk_ecc_disable(struct mtk_ecc *ecc)
|
||||
{
|
||||
enum mtk_ecc_operation op = ECC_ENCODE;
|
||||
|
||||
/* find out the running operation */
|
||||
if (readw(ecc->regs + ECC_CTL_REG(op)) != ECC_OP_ENABLE)
|
||||
op = ECC_DECODE;
|
||||
|
||||
/* disable it */
|
||||
mtk_ecc_wait_idle(ecc, op);
|
||||
writew(0, ecc->regs + ECC_IRQ_REG(op));
|
||||
writew(ECC_OP_DISABLE, ecc->regs + ECC_CTL_REG(op));
|
||||
|
||||
mutex_unlock(&ecc->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_disable);
|
||||
|
||||
int mtk_ecc_wait_done(struct mtk_ecc *ecc, enum mtk_ecc_operation op)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = wait_for_completion_timeout(&ecc->done, msecs_to_jiffies(500));
|
||||
if (!ret) {
|
||||
dev_err(ecc->dev, "%s timeout - interrupt did not arrive)\n",
|
||||
(op == ECC_ENCODE) ? "encoder" : "decoder");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_wait_done);
|
||||
|
||||
int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config,
|
||||
u8 *data, u32 bytes)
|
||||
{
|
||||
dma_addr_t addr;
|
||||
u32 *p, len, i;
|
||||
int ret = 0;
|
||||
|
||||
addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE);
|
||||
ret = dma_mapping_error(ecc->dev, addr);
|
||||
if (ret) {
|
||||
dev_err(ecc->dev, "dma mapping error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
config->op = ECC_ENCODE;
|
||||
config->addr = addr;
|
||||
ret = mtk_ecc_enable(ecc, config);
|
||||
if (ret) {
|
||||
dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mtk_ecc_wait_done(ecc, ECC_ENCODE);
|
||||
if (ret)
|
||||
goto timeout;
|
||||
|
||||
mtk_ecc_wait_idle(ecc, ECC_ENCODE);
|
||||
|
||||
/* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */
|
||||
len = (config->strength * ECC_PARITY_BITS + 7) >> 3;
|
||||
p = (u32 *)(data + bytes);
|
||||
|
||||
/* write the parity bytes generated by the ECC back to the OOB region */
|
||||
for (i = 0; i < len; i++)
|
||||
p[i] = readl(ecc->regs + ECC_ENCPAR(i));
|
||||
timeout:
|
||||
|
||||
dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
|
||||
mtk_ecc_disable(ecc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_encode);
|
||||
|
||||
void mtk_ecc_adjust_strength(u32 *p)
|
||||
{
|
||||
u32 ecc[] = {4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36,
|
||||
40, 44, 48, 52, 56, 60};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ecc); i++) {
|
||||
if (*p <= ecc[i]) {
|
||||
if (!i)
|
||||
*p = ecc[i];
|
||||
else if (*p != ecc[i])
|
||||
*p = ecc[i - 1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*p = ecc[ARRAY_SIZE(ecc) - 1];
|
||||
}
|
||||
EXPORT_SYMBOL(mtk_ecc_adjust_strength);
|
||||
|
||||
static int mtk_ecc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_ecc *ecc;
|
||||
struct resource *res;
|
||||
int irq, ret;
|
||||
|
||||
ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
|
||||
if (!ecc)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ecc->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(ecc->regs)) {
|
||||
dev_err(dev, "failed to map regs: %ld\n", PTR_ERR(ecc->regs));
|
||||
return PTR_ERR(ecc->regs);
|
||||
}
|
||||
|
||||
ecc->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(ecc->clk)) {
|
||||
dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk));
|
||||
return PTR_ERR(ecc->clk);
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "failed to get irq\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = dma_set_mask(dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set DMA mask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, mtk_ecc_irq, 0x0, "mtk-ecc", ecc);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ecc->dev = dev;
|
||||
mutex_init(&ecc->lock);
|
||||
platform_set_drvdata(pdev, ecc);
|
||||
dev_info(dev, "probed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mtk_ecc_suspend(struct device *dev)
|
||||
{
|
||||
struct mtk_ecc *ecc = dev_get_drvdata(dev);
|
||||
|
||||
clk_disable_unprepare(ecc->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_ecc_resume(struct device *dev)
|
||||
{
|
||||
struct mtk_ecc *ecc = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(ecc->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clk\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mtk_ecc_hw_init(ecc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(mtk_ecc_pm_ops, mtk_ecc_suspend, mtk_ecc_resume);
|
||||
#endif
|
||||
|
||||
static const struct of_device_id mtk_ecc_dt_match[] = {
|
||||
{ .compatible = "mediatek,mt2701-ecc" },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, mtk_ecc_dt_match);
|
||||
|
||||
static struct platform_driver mtk_ecc_driver = {
|
||||
.probe = mtk_ecc_probe,
|
||||
.driver = {
|
||||
.name = "mtk-ecc",
|
||||
.of_match_table = of_match_ptr(mtk_ecc_dt_match),
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.pm = &mtk_ecc_pm_ops,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(mtk_ecc_driver);
|
||||
|
||||
MODULE_AUTHOR("Xiaolei Li <xiaolei.li@mediatek.com>");
|
||||
MODULE_DESCRIPTION("MTK Nand ECC Driver");
|
||||
MODULE_LICENSE("GPL");
|
50
drivers/mtd/nand/mtk_ecc.h
Normal file
50
drivers/mtd/nand/mtk_ecc.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MTK SDG1 ECC controller
|
||||
*
|
||||
* Copyright (c) 2016 Mediatek
|
||||
* Authors: Xiaolei Li <xiaolei.li@mediatek.com>
|
||||
* Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __DRIVERS_MTD_NAND_MTK_ECC_H__
|
||||
#define __DRIVERS_MTD_NAND_MTK_ECC_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define ECC_PARITY_BITS (14)
|
||||
|
||||
enum mtk_ecc_mode {ECC_DMA_MODE = 0, ECC_NFI_MODE = 1};
|
||||
enum mtk_ecc_operation {ECC_ENCODE, ECC_DECODE};
|
||||
|
||||
struct device_node;
|
||||
struct mtk_ecc;
|
||||
|
||||
struct mtk_ecc_stats {
|
||||
u32 corrected;
|
||||
u32 bitflips;
|
||||
u32 failed;
|
||||
};
|
||||
|
||||
struct mtk_ecc_config {
|
||||
enum mtk_ecc_operation op;
|
||||
enum mtk_ecc_mode mode;
|
||||
dma_addr_t addr;
|
||||
u32 strength;
|
||||
u32 sectors;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
int mtk_ecc_encode(struct mtk_ecc *, struct mtk_ecc_config *, u8 *, u32);
|
||||
void mtk_ecc_get_stats(struct mtk_ecc *, struct mtk_ecc_stats *, int);
|
||||
int mtk_ecc_wait_done(struct mtk_ecc *, enum mtk_ecc_operation);
|
||||
int mtk_ecc_enable(struct mtk_ecc *, struct mtk_ecc_config *);
|
||||
void mtk_ecc_disable(struct mtk_ecc *);
|
||||
void mtk_ecc_adjust_strength(u32 *);
|
||||
|
||||
struct mtk_ecc *of_mtk_ecc_get(struct device_node *);
|
||||
void mtk_ecc_release(struct mtk_ecc *);
|
||||
|
||||
#endif
|
1526
drivers/mtd/nand/mtk_nand.c
Normal file
1526
drivers/mtd/nand/mtk_nand.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -168,6 +168,7 @@ struct nand_flash_dev nand_flash_ids[] = {
|
||||
/* Manufacturer IDs */
|
||||
struct nand_manufacturers nand_manuf_ids[] = {
|
||||
{NAND_MFR_TOSHIBA, "Toshiba"},
|
||||
{NAND_MFR_ESMT, "ESMT"},
|
||||
{NAND_MFR_SAMSUNG, "Samsung"},
|
||||
{NAND_MFR_FUJITSU, "Fujitsu"},
|
||||
{NAND_MFR_NATIONAL, "National"},
|
||||
|
@ -118,8 +118,6 @@
|
||||
#define PREFETCH_STATUS_FIFO_CNT(val) ((val >> 24) & 0x7F)
|
||||
#define STATUS_BUFF_EMPTY 0x00000001
|
||||
|
||||
#define OMAP24XX_DMA_GPMC 4
|
||||
|
||||
#define SECTOR_BYTES 512
|
||||
/* 4 bit padding to make byte aligned, 56 = 52 + 4 */
|
||||
#define BCH4_BIT_PAD 4
|
||||
@ -1808,7 +1806,6 @@ static int omap_nand_probe(struct platform_device *pdev)
|
||||
struct nand_chip *nand_chip;
|
||||
int err;
|
||||
dma_cap_mask_t mask;
|
||||
unsigned sig;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
int min_oobbytes = BADBLOCK_MARKER_LENGTH;
|
||||
@ -1921,8 +1918,8 @@ static int omap_nand_probe(struct platform_device *pdev)
|
||||
case NAND_OMAP_PREFETCH_DMA:
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
sig = OMAP24XX_DMA_GPMC;
|
||||
info->dma = dma_request_channel(mask, omap_dma_filter_fn, &sig);
|
||||
info->dma = dma_request_chan(pdev->dev.parent, "rxtx");
|
||||
|
||||
if (!info->dma) {
|
||||
dev_err(&pdev->dev, "DMA engine request failed\n");
|
||||
err = -ENXIO;
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#define NFC_REG_CTL 0x0000
|
||||
#define NFC_REG_ST 0x0004
|
||||
@ -153,6 +154,7 @@
|
||||
|
||||
/* define bit use in NFC_ECC_ST */
|
||||
#define NFC_ECC_ERR(x) BIT(x)
|
||||
#define NFC_ECC_ERR_MSK GENMASK(15, 0)
|
||||
#define NFC_ECC_PAT_FOUND(x) BIT(x + 16)
|
||||
#define NFC_ECC_ERR_CNT(b, x) (((x) >> (((b) % 4) * 8)) & 0xff)
|
||||
|
||||
@ -269,10 +271,12 @@ struct sunxi_nfc {
|
||||
void __iomem *regs;
|
||||
struct clk *ahb_clk;
|
||||
struct clk *mod_clk;
|
||||
struct reset_control *reset;
|
||||
unsigned long assigned_cs;
|
||||
unsigned long clk_rate;
|
||||
struct list_head chips;
|
||||
struct completion complete;
|
||||
struct dma_chan *dmac;
|
||||
};
|
||||
|
||||
static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
|
||||
@ -365,6 +369,67 @@ static int sunxi_nfc_rst(struct sunxi_nfc *nfc)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_dma_op_prepare(struct mtd_info *mtd, const void *buf,
|
||||
int chunksize, int nchunks,
|
||||
enum dma_data_direction ddir,
|
||||
struct scatterlist *sg)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct dma_async_tx_descriptor *dmad;
|
||||
enum dma_transfer_direction tdir;
|
||||
dma_cookie_t dmat;
|
||||
int ret;
|
||||
|
||||
if (ddir == DMA_FROM_DEVICE)
|
||||
tdir = DMA_DEV_TO_MEM;
|
||||
else
|
||||
tdir = DMA_MEM_TO_DEV;
|
||||
|
||||
sg_init_one(sg, buf, nchunks * chunksize);
|
||||
ret = dma_map_sg(nfc->dev, sg, 1, ddir);
|
||||
if (!ret)
|
||||
return -ENOMEM;
|
||||
|
||||
dmad = dmaengine_prep_slave_sg(nfc->dmac, sg, 1, tdir, DMA_CTRL_ACK);
|
||||
if (!dmad) {
|
||||
ret = -EINVAL;
|
||||
goto err_unmap_buf;
|
||||
}
|
||||
|
||||
writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RAM_METHOD,
|
||||
nfc->regs + NFC_REG_CTL);
|
||||
writel(nchunks, nfc->regs + NFC_REG_SECTOR_NUM);
|
||||
writel(chunksize, nfc->regs + NFC_REG_CNT);
|
||||
dmat = dmaengine_submit(dmad);
|
||||
|
||||
ret = dma_submit_error(dmat);
|
||||
if (ret)
|
||||
goto err_clr_dma_flag;
|
||||
|
||||
return 0;
|
||||
|
||||
err_clr_dma_flag:
|
||||
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD,
|
||||
nfc->regs + NFC_REG_CTL);
|
||||
|
||||
err_unmap_buf:
|
||||
dma_unmap_sg(nfc->dev, sg, 1, ddir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sunxi_nfc_dma_op_cleanup(struct mtd_info *mtd,
|
||||
enum dma_data_direction ddir,
|
||||
struct scatterlist *sg)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
|
||||
dma_unmap_sg(nfc->dev, sg, 1, ddir);
|
||||
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD,
|
||||
nfc->regs + NFC_REG_CTL);
|
||||
}
|
||||
|
||||
static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
@ -822,17 +887,15 @@ static void sunxi_nfc_hw_ecc_update_stats(struct mtd_info *mtd,
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_correct(struct mtd_info *mtd, u8 *data, u8 *oob,
|
||||
int step, bool *erased)
|
||||
int step, u32 status, bool *erased)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
u32 status, tmp;
|
||||
u32 tmp;
|
||||
|
||||
*erased = false;
|
||||
|
||||
status = readl(nfc->regs + NFC_REG_ECC_ST);
|
||||
|
||||
if (status & NFC_ECC_ERR(step))
|
||||
return -EBADMSG;
|
||||
|
||||
@ -898,6 +961,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
|
||||
*cur_off = oob_off + ecc->bytes + 4;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_correct(mtd, data, oob_required ? oob : NULL, 0,
|
||||
readl(nfc->regs + NFC_REG_ECC_ST),
|
||||
&erased);
|
||||
if (erased)
|
||||
return 1;
|
||||
@ -967,6 +1031,130 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct mtd_info *mtd,
|
||||
*cur_off = mtd->oobsize + mtd->writesize;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
|
||||
int oob_required, int page,
|
||||
int nchunks)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
unsigned int max_bitflips = 0;
|
||||
int ret, i, raw_mode = 0;
|
||||
struct scatterlist sg;
|
||||
u32 status;
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = sunxi_nfc_dma_op_prepare(mtd, buf, ecc->size, nchunks,
|
||||
DMA_FROM_DEVICE, &sg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(mtd);
|
||||
sunxi_nfc_randomizer_config(mtd, page, false);
|
||||
sunxi_nfc_randomizer_enable(mtd);
|
||||
|
||||
writel((NAND_CMD_RNDOUTSTART << 16) | (NAND_CMD_RNDOUT << 8) |
|
||||
NAND_CMD_READSTART, nfc->regs + NFC_REG_RCMD_SET);
|
||||
|
||||
dma_async_issue_pending(nfc->dmac);
|
||||
|
||||
writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD | NFC_DATA_TRANS,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
if (ret)
|
||||
dmaengine_terminate_all(nfc->dmac);
|
||||
|
||||
sunxi_nfc_randomizer_disable(mtd);
|
||||
sunxi_nfc_hw_ecc_disable(mtd);
|
||||
|
||||
sunxi_nfc_dma_op_cleanup(mtd, DMA_FROM_DEVICE, &sg);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
status = readl(nfc->regs + NFC_REG_ECC_ST);
|
||||
|
||||
for (i = 0; i < nchunks; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + 4);
|
||||
u8 *data = buf + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
bool erased;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_correct(mtd, randomized ? data : NULL,
|
||||
oob_required ? oob : NULL,
|
||||
i, status, &erased);
|
||||
|
||||
/* ECC errors are handled in the second loop. */
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
if (oob_required && !erased) {
|
||||
/* TODO: use DMA to retrieve OOB */
|
||||
nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
|
||||
mtd->writesize + oob_off, -1);
|
||||
nand->read_buf(mtd, oob, ecc->bytes + 4);
|
||||
|
||||
sunxi_nfc_hw_ecc_get_prot_oob_bytes(mtd, oob, i,
|
||||
!i, page);
|
||||
}
|
||||
|
||||
if (erased)
|
||||
raw_mode = 1;
|
||||
|
||||
sunxi_nfc_hw_ecc_update_stats(mtd, &max_bitflips, ret);
|
||||
}
|
||||
|
||||
if (status & NFC_ECC_ERR_MSK) {
|
||||
for (i = 0; i < nchunks; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + 4);
|
||||
u8 *data = buf + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
if (!(status & NFC_ECC_ERR(i)))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Re-read the data with the randomizer disabled to
|
||||
* identify bitflips in erased pages.
|
||||
*/
|
||||
if (randomized) {
|
||||
/* TODO: use DMA to read page in raw mode */
|
||||
nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
|
||||
data_off, -1);
|
||||
nand->read_buf(mtd, data, ecc->size);
|
||||
}
|
||||
|
||||
/* TODO: use DMA to retrieve OOB */
|
||||
nand->cmdfunc(mtd, NAND_CMD_RNDOUT,
|
||||
mtd->writesize + oob_off, -1);
|
||||
nand->read_buf(mtd, oob, ecc->bytes + 4);
|
||||
|
||||
ret = nand_check_erased_ecc_chunk(data, ecc->size,
|
||||
oob, ecc->bytes + 4,
|
||||
NULL, 0,
|
||||
ecc->strength);
|
||||
if (ret >= 0)
|
||||
raw_mode = 1;
|
||||
|
||||
sunxi_nfc_hw_ecc_update_stats(mtd, &max_bitflips, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (oob_required)
|
||||
sunxi_nfc_hw_ecc_read_extra_oob(mtd, nand->oob_poi,
|
||||
NULL, !raw_mode,
|
||||
page);
|
||||
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
|
||||
const u8 *data, int data_off,
|
||||
const u8 *oob, int oob_off,
|
||||
@ -1065,6 +1253,23 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_read_page_dma(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, u8 *buf,
|
||||
int oob_required, int page)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, oob_required, page,
|
||||
chip->ecc.steps);
|
||||
if (ret >= 0)
|
||||
return ret;
|
||||
|
||||
/* Fallback to PIO mode */
|
||||
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
|
||||
|
||||
return sunxi_nfc_hw_ecc_read_page(mtd, chip, buf, oob_required, page);
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
u32 data_offs, u32 readlen,
|
||||
@ -1098,6 +1303,25 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_read_subpage_dma(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
u32 data_offs, u32 readlen,
|
||||
u8 *buf, int page)
|
||||
{
|
||||
int nchunks = DIV_ROUND_UP(data_offs + readlen, chip->ecc.size);
|
||||
int ret;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_read_chunks_dma(mtd, buf, false, page, nchunks);
|
||||
if (ret >= 0)
|
||||
return ret;
|
||||
|
||||
/* Fallback to PIO mode */
|
||||
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
|
||||
|
||||
return sunxi_nfc_hw_ecc_read_subpage(mtd, chip, data_offs, readlen,
|
||||
buf, page);
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required,
|
||||
@ -1130,6 +1354,99 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
u32 data_offs, u32 data_len,
|
||||
const u8 *buf, int oob_required,
|
||||
int page)
|
||||
{
|
||||
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
||||
int ret, i, cur_off = 0;
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(mtd);
|
||||
|
||||
for (i = data_offs / ecc->size;
|
||||
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + 4);
|
||||
const u8 *data = buf + data_off;
|
||||
const u8 *oob = chip->oob_poi + oob_off;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_write_chunk(mtd, data, data_off, oob,
|
||||
oob_off + mtd->writesize,
|
||||
&cur_off, !i, page);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
sunxi_nfc_hw_ecc_disable(mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const u8 *buf,
|
||||
int oob_required,
|
||||
int page)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
struct scatterlist sg;
|
||||
int ret, i;
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = sunxi_nfc_dma_op_prepare(mtd, buf, ecc->size, ecc->steps,
|
||||
DMA_TO_DEVICE, &sg);
|
||||
if (ret)
|
||||
goto pio_fallback;
|
||||
|
||||
for (i = 0; i < ecc->steps; i++) {
|
||||
const u8 *oob = nand->oob_poi + (i * (ecc->bytes + 4));
|
||||
|
||||
sunxi_nfc_hw_ecc_set_prot_oob_bytes(mtd, oob, i, !i, page);
|
||||
}
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(mtd);
|
||||
sunxi_nfc_randomizer_config(mtd, page, false);
|
||||
sunxi_nfc_randomizer_enable(mtd);
|
||||
|
||||
writel((NAND_CMD_RNDIN << 8) | NAND_CMD_PAGEPROG,
|
||||
nfc->regs + NFC_REG_RCMD_SET);
|
||||
|
||||
dma_async_issue_pending(nfc->dmac);
|
||||
|
||||
writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD |
|
||||
NFC_DATA_TRANS | NFC_ACCESS_DIR,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
if (ret)
|
||||
dmaengine_terminate_all(nfc->dmac);
|
||||
|
||||
sunxi_nfc_randomizer_disable(mtd);
|
||||
sunxi_nfc_hw_ecc_disable(mtd);
|
||||
|
||||
sunxi_nfc_dma_op_cleanup(mtd, DMA_TO_DEVICE, &sg);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (oob_required || (chip->options & NAND_NEED_SCRAMBLING))
|
||||
/* TODO: use DMA to transfer extra OOB bytes ? */
|
||||
sunxi_nfc_hw_ecc_write_extra_oob(mtd, chip->oob_poi,
|
||||
NULL, page);
|
||||
|
||||
return 0;
|
||||
|
||||
pio_fallback:
|
||||
return sunxi_nfc_hw_ecc_write_page(mtd, chip, buf, oob_required, page);
|
||||
}
|
||||
|
||||
static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required,
|
||||
@ -1497,10 +1814,19 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (ecc->size != 512 && ecc->size != 1024)
|
||||
return -EINVAL;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Prefer 1k ECC chunk over 512 ones */
|
||||
if (ecc->size == 512 && mtd->writesize > 512) {
|
||||
ecc->size = 1024;
|
||||
ecc->strength *= 2;
|
||||
}
|
||||
|
||||
/* Add ECC info retrieval from DT */
|
||||
for (i = 0; i < ARRAY_SIZE(strengths); i++) {
|
||||
if (ecc->strength <= strengths[i])
|
||||
@ -1550,14 +1876,28 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
|
||||
struct nand_ecc_ctrl *ecc,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
|
||||
int ret;
|
||||
|
||||
ret = sunxi_nand_hw_common_ecc_ctrl_init(mtd, ecc, np);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ecc->read_page = sunxi_nfc_hw_ecc_read_page;
|
||||
ecc->write_page = sunxi_nfc_hw_ecc_write_page;
|
||||
if (nfc->dmac) {
|
||||
ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma;
|
||||
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma;
|
||||
ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma;
|
||||
nand->options |= NAND_USE_BOUNCE_BUFFER;
|
||||
} else {
|
||||
ecc->read_page = sunxi_nfc_hw_ecc_read_page;
|
||||
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage;
|
||||
ecc->write_page = sunxi_nfc_hw_ecc_write_page;
|
||||
}
|
||||
|
||||
/* TODO: support DMA for raw accesses and subpage write */
|
||||
ecc->write_subpage = sunxi_nfc_hw_ecc_write_subpage;
|
||||
ecc->read_oob_raw = nand_read_oob_std;
|
||||
ecc->write_oob_raw = nand_write_oob_std;
|
||||
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage;
|
||||
@ -1871,26 +2211,59 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
goto out_ahb_clk_unprepare;
|
||||
|
||||
nfc->reset = devm_reset_control_get_optional(dev, "ahb");
|
||||
if (!IS_ERR(nfc->reset)) {
|
||||
ret = reset_control_deassert(nfc->reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "reset err %d\n", ret);
|
||||
goto out_mod_clk_unprepare;
|
||||
}
|
||||
} else if (PTR_ERR(nfc->reset) != -ENOENT) {
|
||||
ret = PTR_ERR(nfc->reset);
|
||||
goto out_mod_clk_unprepare;
|
||||
}
|
||||
|
||||
ret = sunxi_nfc_rst(nfc);
|
||||
if (ret)
|
||||
goto out_mod_clk_unprepare;
|
||||
goto out_ahb_reset_reassert;
|
||||
|
||||
writel(0, nfc->regs + NFC_REG_INT);
|
||||
ret = devm_request_irq(dev, irq, sunxi_nfc_interrupt,
|
||||
0, "sunxi-nand", nfc);
|
||||
if (ret)
|
||||
goto out_mod_clk_unprepare;
|
||||
goto out_ahb_reset_reassert;
|
||||
|
||||
nfc->dmac = dma_request_slave_channel(dev, "rxtx");
|
||||
if (nfc->dmac) {
|
||||
struct dma_slave_config dmac_cfg = { };
|
||||
|
||||
dmac_cfg.src_addr = r->start + NFC_REG_IO_DATA;
|
||||
dmac_cfg.dst_addr = dmac_cfg.src_addr;
|
||||
dmac_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
dmac_cfg.dst_addr_width = dmac_cfg.src_addr_width;
|
||||
dmac_cfg.src_maxburst = 4;
|
||||
dmac_cfg.dst_maxburst = 4;
|
||||
dmaengine_slave_config(nfc->dmac, &dmac_cfg);
|
||||
} else {
|
||||
dev_warn(dev, "failed to request rxtx DMA channel\n");
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, nfc);
|
||||
|
||||
ret = sunxi_nand_chips_init(dev, nfc);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to init nand chips\n");
|
||||
goto out_mod_clk_unprepare;
|
||||
goto out_release_dmac;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_release_dmac:
|
||||
if (nfc->dmac)
|
||||
dma_release_channel(nfc->dmac);
|
||||
out_ahb_reset_reassert:
|
||||
if (!IS_ERR(nfc->reset))
|
||||
reset_control_assert(nfc->reset);
|
||||
out_mod_clk_unprepare:
|
||||
clk_disable_unprepare(nfc->mod_clk);
|
||||
out_ahb_clk_unprepare:
|
||||
@ -1904,6 +2277,12 @@ static int sunxi_nfc_remove(struct platform_device *pdev)
|
||||
struct sunxi_nfc *nfc = platform_get_drvdata(pdev);
|
||||
|
||||
sunxi_nand_chips_cleanup(nfc);
|
||||
|
||||
if (!IS_ERR(nfc->reset))
|
||||
reset_control_assert(nfc->reset);
|
||||
|
||||
if (nfc->dmac)
|
||||
dma_release_channel(nfc->dmac);
|
||||
clk_disable_unprepare(nfc->mod_clk);
|
||||
clk_disable_unprepare(nfc->ahb_clk);
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* Copyright © 2012 John Crispin <blogic@openwrt.org>
|
||||
* Copyright © 2016 Hauke Mehrtens <hauke@hauke-m.de>
|
||||
*/
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
@ -16,20 +17,28 @@
|
||||
#define EBU_ADDSEL1 0x24
|
||||
#define EBU_NAND_CON 0xB0
|
||||
#define EBU_NAND_WAIT 0xB4
|
||||
#define NAND_WAIT_RD BIT(0) /* NAND flash status output */
|
||||
#define NAND_WAIT_WR_C BIT(3) /* NAND Write/Read complete */
|
||||
#define EBU_NAND_ECC0 0xB8
|
||||
#define EBU_NAND_ECC_AC 0xBC
|
||||
|
||||
/* nand commands */
|
||||
#define NAND_CMD_ALE (1 << 2)
|
||||
#define NAND_CMD_CLE (1 << 3)
|
||||
#define NAND_CMD_CS (1 << 4)
|
||||
#define NAND_WRITE_CMD_RESET 0xff
|
||||
/*
|
||||
* nand commands
|
||||
* The pins of the NAND chip are selected based on the address bits of the
|
||||
* "register" read and write. There are no special registers, but an
|
||||
* address range and the lower address bits are used to activate the
|
||||
* correct line. For example when the bit (1 << 2) is set in the address
|
||||
* the ALE pin will be activated.
|
||||
*/
|
||||
#define NAND_CMD_ALE BIT(2) /* address latch enable */
|
||||
#define NAND_CMD_CLE BIT(3) /* command latch enable */
|
||||
#define NAND_CMD_CS BIT(4) /* chip select */
|
||||
#define NAND_CMD_SE BIT(5) /* spare area access latch */
|
||||
#define NAND_CMD_WP BIT(6) /* write protect */
|
||||
#define NAND_WRITE_CMD (NAND_CMD_CS | NAND_CMD_CLE)
|
||||
#define NAND_WRITE_ADDR (NAND_CMD_CS | NAND_CMD_ALE)
|
||||
#define NAND_WRITE_DATA (NAND_CMD_CS)
|
||||
#define NAND_READ_DATA (NAND_CMD_CS)
|
||||
#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)
|
||||
@ -54,31 +63,41 @@
|
||||
#define NAND_CON_CSMUX (1 << 1)
|
||||
#define NAND_CON_NANDM 1
|
||||
|
||||
static void xway_reset_chip(struct nand_chip *chip)
|
||||
struct xway_nand_data {
|
||||
struct nand_chip chip;
|
||||
unsigned long csflags;
|
||||
void __iomem *nandaddr;
|
||||
};
|
||||
|
||||
static u8 xway_readb(struct mtd_info *mtd, int op)
|
||||
{
|
||||
unsigned long nandaddr = (unsigned long) chip->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct xway_nand_data *data = nand_get_controller_data(chip);
|
||||
|
||||
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);
|
||||
return readb(data->nandaddr + op);
|
||||
}
|
||||
|
||||
static void xway_select_chip(struct mtd_info *mtd, int chip)
|
||||
static void xway_writeb(struct mtd_info *mtd, int op, u8 value)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct xway_nand_data *data = nand_get_controller_data(chip);
|
||||
|
||||
switch (chip) {
|
||||
writeb(value, data->nandaddr + op);
|
||||
}
|
||||
|
||||
static void xway_select_chip(struct mtd_info *mtd, int select)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct xway_nand_data *data = nand_get_controller_data(chip);
|
||||
|
||||
switch (select) {
|
||||
case -1:
|
||||
ltq_ebu_w32_mask(NAND_CON_CE, 0, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(NAND_CON_NANDM, 0, EBU_NAND_CON);
|
||||
spin_unlock_irqrestore(&ebu_lock, data->csflags);
|
||||
break;
|
||||
case 0:
|
||||
spin_lock_irqsave(&ebu_lock, data->csflags);
|
||||
ltq_ebu_w32_mask(0, NAND_CON_NANDM, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(0, NAND_CON_CE, EBU_NAND_CON);
|
||||
break;
|
||||
@ -89,26 +108,16 @@ static void xway_select_chip(struct mtd_info *mtd, int chip)
|
||||
|
||||
static void xway_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *this = mtd_to_nand(mtd);
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
if (cmd == NAND_CMD_NONE)
|
||||
return;
|
||||
|
||||
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 (ctrl & NAND_CLE)
|
||||
xway_writeb(mtd, NAND_WRITE_CMD, cmd);
|
||||
else if (ctrl & NAND_ALE)
|
||||
xway_writeb(mtd, NAND_WRITE_ADDR, cmd);
|
||||
|
||||
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);
|
||||
}
|
||||
while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0)
|
||||
;
|
||||
}
|
||||
|
||||
static int xway_dev_ready(struct mtd_info *mtd)
|
||||
@ -118,80 +127,122 @@ static int xway_dev_ready(struct mtd_info *mtd)
|
||||
|
||||
static unsigned char xway_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd_to_nand(mtd);
|
||||
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;
|
||||
return xway_readb(mtd, NAND_READ_DATA);
|
||||
}
|
||||
|
||||
static void xway_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = xway_readb(mtd, NAND_WRITE_DATA);
|
||||
}
|
||||
|
||||
static void xway_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
xway_writeb(mtd, NAND_WRITE_DATA, buf[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for the NAND device.
|
||||
*/
|
||||
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);
|
||||
struct xway_nand_data *data;
|
||||
struct mtd_info *mtd;
|
||||
struct resource *res;
|
||||
int err;
|
||||
u32 cs;
|
||||
u32 cs_flag = 0;
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct xway_nand_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->nandaddr = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->nandaddr))
|
||||
return PTR_ERR(data->nandaddr);
|
||||
|
||||
nand_set_flash_node(&data->chip, pdev->dev.of_node);
|
||||
mtd = nand_to_mtd(&data->chip);
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
|
||||
data->chip.cmd_ctrl = xway_cmd_ctrl;
|
||||
data->chip.dev_ready = xway_dev_ready;
|
||||
data->chip.select_chip = xway_select_chip;
|
||||
data->chip.write_buf = xway_write_buf;
|
||||
data->chip.read_buf = xway_read_buf;
|
||||
data->chip.read_byte = xway_read_byte;
|
||||
data->chip.chip_delay = 30;
|
||||
|
||||
data->chip.ecc.mode = NAND_ECC_SOFT;
|
||||
data->chip.ecc.algo = NAND_ECC_HAMMING;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
nand_set_controller_data(&data->chip, data);
|
||||
|
||||
/* load our CS from the DT. Either we find a valid 1 or default to 0 */
|
||||
if (cs && (*cs == 1))
|
||||
err = of_property_read_u32(pdev->dev.of_node, "lantiq,cs", &cs);
|
||||
if (!err && cs == 1)
|
||||
cs_flag = NAND_CON_IN_CS1 | NAND_CON_OUT_CS1;
|
||||
|
||||
/* setup the EBU to run in NAND mode on our base addr */
|
||||
ltq_ebu_w32(CPHYSADDR(nandaddr)
|
||||
| ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1);
|
||||
ltq_ebu_w32(CPHYSADDR(data->nandaddr)
|
||||
| ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1);
|
||||
|
||||
ltq_ebu_w32(BUSCON1_SETUP | BUSCON1_BCGEN_RES | BUSCON1_WAITWRC2
|
||||
| BUSCON1_WAITRDC2 | BUSCON1_HOLDC1 | BUSCON1_RECOVC1
|
||||
| BUSCON1_CMULT4, LTQ_EBU_BUSCON1);
|
||||
| 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);
|
||||
| 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);
|
||||
/* Scan to find existence of the device */
|
||||
err = nand_scan(mtd, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
err = mtd_device_register(mtd, NULL, 0);
|
||||
if (err)
|
||||
nand_release(mtd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_nand_data xway_nand_data = {
|
||||
.chip = {
|
||||
.nr_chips = 1,
|
||||
.chip_delay = 30,
|
||||
},
|
||||
.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
|
||||
* Remove a NAND device.
|
||||
*/
|
||||
static int __init xway_register_nand(void)
|
||||
static int xway_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct platform_device *pdev;
|
||||
struct xway_nand_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
nand_release(nand_to_mtd(&data->chip));
|
||||
|
||||
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);
|
||||
static const struct of_device_id xway_nand_match[] = {
|
||||
{ .compatible = "lantiq,nand-xway" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xway_nand_match);
|
||||
|
||||
static struct platform_driver xway_nand_driver = {
|
||||
.probe = xway_nand_probe,
|
||||
.remove = xway_nand_remove,
|
||||
.driver = {
|
||||
.name = "lantiq,nand-xway",
|
||||
.of_match_table = xway_nand_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(xway_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -290,7 +290,7 @@ static int overwrite_test(void)
|
||||
|
||||
while (opno < max_overwrite) {
|
||||
|
||||
err = rewrite_page(0);
|
||||
err = write_page(0);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
|
@ -783,6 +783,7 @@ static inline void nand_set_controller_data(struct nand_chip *chip, void *priv)
|
||||
* NAND Flash Manufacturer ID Codes
|
||||
*/
|
||||
#define NAND_MFR_TOSHIBA 0x98
|
||||
#define NAND_MFR_ESMT 0xc8
|
||||
#define NAND_MFR_SAMSUNG 0xec
|
||||
#define NAND_MFR_FUJITSU 0x04
|
||||
#define NAND_MFR_NATIONAL 0x8f
|
||||
|
Loading…
Reference in New Issue
Block a user