diff options
Diffstat (limited to 'drivers/mtd')
83 files changed, 6188 insertions, 1572 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 36dbe9f825..781e82fce9 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig MTD bool "Memory Technology Device (MTD) support" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index bfac8ebe6f..dc05793ca1 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NAND) += nand/ obj-$(CONFIG_DRIVER_CFI) += nor/ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index c6c1e1e7cf..ec2c3ff7bb 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * (C) Copyright 2005 * 2N Telekomunikace, a.s. <www.2n.cz> * Ladislav Michl <michl@2n.cz> - * - * 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 <common.h> #include <linux/mtd/nand.h> @@ -598,7 +590,7 @@ static int mtd_part_compare(struct list_head *a, struct list_head *b) return 0; } -static int mtd_detect(struct device_d *dev) +static int mtd_detect(struct device *dev) { struct mtd_info *mtd = container_of(dev, struct mtd_info, dev); int bufsize = 512; @@ -607,7 +599,10 @@ static int mtd_detect(struct device_d *dev) enum filetype filetype; int npebs = mtd_div_by_eb(mtd->size, mtd); - npebs = max(npebs, 64); + /* No point in looking for UBI on a partition that's too small */ + npebs = min(npebs, 64); + if (npebs < 5) + return 0; /* * Do not try to attach an UBI device if this device has partitions @@ -652,8 +647,8 @@ static int mtd_partition_fixup_generic(struct mtd_info *mtd, struct device_node np = of_find_node_by_reproducible_name(root, name); free(name); if (!np) { - dev_err(&mtd->dev, "Cannot find nodepath %s, cannot fixup\n", - mtdnp->full_name); + dev_err(&mtd->dev, "Cannot find nodepath %pOF, cannot fixup\n", + mtdnp); return -EINVAL; } @@ -696,6 +691,7 @@ int add_mtd_device(struct mtd_info *mtd, const char *devname, int device_id) mtd->dev.id); INIT_LIST_HEAD(&mtd->partitions); + INIT_LIST_HEAD(&mtd->partitions_entry); mtd->cdev.priv = mtd; mtd->cdev.dev = &mtd->dev; @@ -771,8 +767,7 @@ int del_mtd_device(struct mtd_info *mtd) unregister_device(&mtd->dev); free(mtd->param_size.value); free(mtd->cdev.name); - if (mtd->parent) - list_del(&mtd->partitions_entry); + list_del(&mtd->partitions_entry); return 0; } diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 47296cf518..447d478133 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menu "Self contained MTD devices" depends on MTD!=n diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index bc1960e1b2..5f93c11057 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Makefile for the self containted memory technology device drivers. # diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index cb0bb5cfec..593a7035e5 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -1,18 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Handles the M-Systems DiskOnChip G3 chip * * Copyright (C) 2011 Robert Jarzmik * - * 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. - * * Taken from linux kernel. */ @@ -1080,7 +1071,7 @@ static void __init doc_set_driver_info(int chip_id, struct mtd_info *mtd) } static struct mtd_info *doc_probe_device(void __iomem *base, int floor, - struct device_d *dev) + struct device *dev) { int ret, bbt_nbpages; u16 chip_id, chip_id_inv; @@ -1140,7 +1131,7 @@ nomem1: return ERR_PTR(ret); } -static int __init docg3_probe(struct device_d *dev) +static int __init docg3_probe(struct device *dev) { struct resource *iores; struct mtd_info *mtd; @@ -1195,7 +1186,7 @@ nomem2: return ret; } -static struct driver_d g3_driver = { +static struct driver g3_driver = { .name = "docg3", .probe = docg3_probe, }; diff --git a/drivers/mtd/devices/docg3.h b/drivers/mtd/devices/docg3.h index 97eafe3520..f917dc4a1e 100644 --- a/drivers/mtd/devices/docg3.h +++ b/drivers/mtd/devices/docg3.h @@ -1,18 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Handles the M-Systems DiskOnChip G3 chip * * Copyright (C) 2011 Robert Jarzmik * - * 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 _MTD_DOCG3_H @@ -263,7 +254,7 @@ #define DOC_LAYOUT_DPS_KEY_LENGTH 8 struct docg3 { - struct device_d *dev; + struct device *dev; void __iomem *base; unsigned int device_id:4; unsigned int if_cfg:1; diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 8845ec3a3e..f4db8c402a 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * MTD SPI driver for ST M25Pxx (and similar) serial flash chips * @@ -7,11 +8,6 @@ * Some parts are based on lart.c by Abraham Van Der Merwe * * Cleaned up and generalized based on mtd_dataflash.c - * - * This code 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 <common.h> @@ -19,7 +15,6 @@ #include <driver.h> #include <of.h> #include <spi/spi.h> -#include <spi/flash.h> #include <xfuncs.h> #include <malloc.h> #include <errno.h> @@ -205,11 +200,10 @@ static const struct platform_device_id m25p_ids[] = { * matches what the READ command supports, at least until this driver * understands FAST_READ (for clocks over 25 MHz). */ -static int m25p_probe(struct device_d *dev) +static int m25p_probe(struct device *dev) { struct spi_device *spi = (struct spi_device *)dev->type_data; struct spi_mem *spimem = spi->mem; - struct flash_platform_data *data; struct m25p *flash; struct spi_nor *nor; struct spi_nor_hwcaps hwcaps = { @@ -222,8 +216,6 @@ static int m25p_probe(struct device_d *dev) bool use_large_blocks; int ret; - data = dev->platform_data; - flash = xzalloc(sizeof *flash); nor = &flash->spi_nor; @@ -249,30 +241,21 @@ static int m25p_probe(struct device_d *dev) dev->priv = (void *)flash; - if (data && data->name) - flash->mtd.name = data->name; - - if (data && data->type) - flash_name = data->type; - else if (data && data->name) - flash_name = data->name; - else if (dev->id_entry) + if (dev->id_entry) flash_name = dev->id_entry->name; else flash_name = NULL; /* auto-detect */ - use_large_blocks = of_property_read_bool(dev->device_node, - "use-large-blocks"); + use_large_blocks = of_property_read_bool(dev->of_node, + "use-large-blocks"); ret = spi_nor_scan(nor, flash_name, &hwcaps, use_large_blocks); if (ret) return ret; device_id = DEVICE_ID_SINGLE; - if (dev->device_node) - flash_name = of_alias_get(dev->device_node); - else if (data && data->name) - flash_name = data->name; + if (dev->of_node) + flash_name = of_alias_get(dev->of_node); if (!flash_name) { device_id = DEVICE_ID_DYNAMIC; @@ -291,8 +274,9 @@ static __maybe_unused struct of_device_id m25p80_dt_ids[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, m25p80_dt_ids); -static struct driver_d m25p80_driver = { +static struct driver m25p80_driver = { .name = "m25p80", .probe = m25p_probe, .of_compatible = DRV_OF_COMPAT(m25p80_dt_ids), diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index 9d4105f82b..80d16dec10 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Atmel AT45xxx DataFlash MTD driver for lightweight SPI framework * * Largely derived from at91_dataflash.c: * Copyright (C) 2003-2005 SAN People (Pty) Ltd - * - * 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 <common.h> #include <init.h> #include <malloc.h> @@ -21,7 +17,6 @@ #include <clock.h> #include <spi/spi.h> -#include <spi/flash.h> #include <linux/math64.h> #include <linux/mtd/mtd.h> @@ -603,7 +598,6 @@ add_dataflash_otp(struct spi_device *spi, char *name, { struct dataflash *priv; struct mtd_info *device; - struct flash_platform_data *pdata = spi->dev.platform_data; char *otp_tag = ""; int err = 0; @@ -621,7 +615,7 @@ add_dataflash_otp(struct spi_device *spi, char *name, name); device = &priv->mtd; - device->name = (pdata && pdata->name) ? pdata->name : "dataflash"; + device->name = "dataflash"; device->size = nr_pages * (uint64_t)pagesize; device->erasesize = pagesize; device->writesize = pagesize; @@ -820,7 +814,7 @@ static struct flash_info * jedec_probe(struct spi_device *spi) * AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11 * AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11 */ -static int dataflash_probe(struct device_d *dev) +static int dataflash_probe(struct device *dev) { struct spi_device *spi = (struct spi_device *)dev->type_data; int status; @@ -901,8 +895,9 @@ static __maybe_unused struct of_device_id dataflash_dt_ids[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, dataflash_dt_ids); -static struct driver_d dataflash_driver = { +static struct driver dataflash_driver = { .name = "mtd_dataflash", .probe = dataflash_probe, .of_compatible = DRV_OF_COMPAT(dataflash_dt_ids), diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c index 9cc8194b5d..33c221e3a1 100644 --- a/drivers/mtd/devices/mtdram.c +++ b/drivers/mtd/devices/mtdram.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Author: Sebastian Block <basti@linux-source.de> * Copyright (c) 2014 @@ -5,11 +6,6 @@ * Some parts are based on mtdram.c found in Linux kernel * by Alexander Larsson <alex@cendio.se> * and Joern Engel <joern@wh.fh-wedel.de>. - * - * This code 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 <common.h> #include <environment.h> @@ -20,11 +16,6 @@ #include <malloc.h> #include <of.h> -struct mtdram_priv_data { - struct mtd_info mtd; - void *base; -}; - static int ram_erase(struct mtd_info *mtd, struct erase_info *instr) { memset((char *)mtd->priv + instr->addr, 0xff, instr->len); @@ -45,28 +36,29 @@ static int ram_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retle return 0; } -static int mtdram_probe(struct device_d *dev) +static int mtdram_probe(struct device *dev) { + long type; struct resource *iores; - void __iomem *base; int device_id; struct mtd_info *mtd; - struct resource *res; loff_t size; int ret = 0; mtd = xzalloc(sizeof(struct mtd_info)); device_id = DEVICE_ID_SINGLE; - if (dev->device_node) { - const char *alias = of_alias_get(dev->device_node); + if (dev->of_node) { + const char *alias = of_alias_get(dev->of_node); if (alias) mtd->name = xstrdup(alias); } + type = (long)device_get_match_data(dev); + if (!mtd->name) { device_id = DEVICE_ID_DYNAMIC; - mtd->name = "mtdram"; + mtd->name = type == MTD_RAM ? "mtdram" : "mtdrom"; } iores = dev_request_mem_resource(dev, 0); @@ -74,22 +66,23 @@ static int mtdram_probe(struct device_d *dev) ret = PTR_ERR(iores); goto nobase; } - base = IOMEM(iores->start); - res = dev_get_resource(dev, IORESOURCE_MEM, 0); - size = (unsigned long) resource_size(res); - mtd->priv = base; + mtd->priv = IOMEM(iores->start); + size = (unsigned long) resource_size(iores); - mtd->type = MTD_RAM; + mtd->type = type; mtd->writesize = 1; mtd->writebufsize = 64; - mtd->flags = MTD_CAP_RAM; mtd->size = size; mtd->_read = ram_read; - mtd->_write = ram_write; - mtd->_erase = ram_erase; - mtd->erasesize = 1; + + if (type == MTD_RAM) { + mtd->flags = MTD_CAP_RAM; + mtd->_write = ram_write; + mtd->_erase = ram_erase; + mtd->erasesize = 1; + } mtd->dev.parent = dev; @@ -105,12 +98,17 @@ nobase: static __maybe_unused struct of_device_id mtdram_dt_ids[] = { { .compatible = "mtd-ram", + .data = (void *)MTD_RAM + }, { + .compatible = "mtd-rom", + .data = (void *)MTD_ROM }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, mtdram_dt_ids); -static struct driver_d mtdram_driver = { +static struct driver mtdram_driver = { .name = "mtdram", .probe = mtdram_probe, .of_compatible = DRV_OF_COMPAT(mtdram_dt_ids), diff --git a/drivers/mtd/mtd.h b/drivers/mtd/mtd.h index 2a85265f98..725731e626 100644 --- a/drivers/mtd/mtd.h +++ b/drivers/mtd/mtd.h @@ -1,18 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * MTD devices registration * * Copyright (C) 2011 Robert Jarzmik - * - * 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. - * */ /** diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 8cd82327ba..964b00166a 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * MTD device concatenation layer * @@ -6,16 +7,6 @@ * * NAND support by Christian Gan <cgan@iders.ca> * - * 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 St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/drivers/mtd/mtdoob.c b/drivers/mtd/mtdoob.c index 19719c4d62..df3279c373 100644 --- a/drivers/mtd/mtdoob.c +++ b/drivers/mtd/mtdoob.c @@ -1,18 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * MTD oob device * * Copyright (C) 2011 Sascha Hauer * - * 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. - * * Adds a character devices : * - mtdoob<N> */ diff --git a/drivers/mtd/mtdraw.c b/drivers/mtd/mtdraw.c index 57630647b7..b284d92ed3 100644 --- a/drivers/mtd/mtdraw.c +++ b/drivers/mtd/mtdraw.c @@ -1,18 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * MTD raw device * * Copyright (C) 2011 Robert Jarzmik * - * 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. - * * Adds a character devices : * - mtdraw<N> * diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index cf9f1fdc0c..19f4322f65 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig NAND bool "NAND support" help @@ -39,7 +40,7 @@ config NAND_IMX "MXC" series: i.MX21/25/27/31/35/51/53. This is not for the "MXS" series i.MX processors (23 & 28), or i.MX6 - and later, which use the GPMI NAND controller from the MXS series. + and later, which use the GPMI NAND controller from the MXS series. See the i.MX 'mxs' driver for those chips. config NAND_FSL_IFC @@ -64,6 +65,7 @@ config NAND_MXS config NAND_OMAP_GPMC tristate "NAND Flash Support for GPMC based OMAP platforms" depends on OMAP_GPMC + depends on BUS_OMAP_GPMC help Support for NAND flash using GPMC. GPMC is a common memory interface found on Texas Instrument's OMAP platforms @@ -81,7 +83,7 @@ config MTD_NAND_OMAP_ELM config NAND_ORION bool prompt "Marvell Orion NAND driver" - depends on ARM && (ARCH_KIRKWOOD || COMPILE_TEST) + depends on (ARM && !CPU_32v4T) && (ARCH_KIRKWOOD || COMPILE_TEST) help Support for the Orion NAND controller, present in Kirkwood SoCs. @@ -93,25 +95,41 @@ config NAND_MRVL_NFC Support for the PXA3xx NAND controller, present in Armada 370/XP and PXA3xx SoCs. +config NAND_STM32 + bool "Support for NAND controller on STM32MP SoCs" + depends on ARCH_STM32MP || COMPILE_TEST + select STM32_FMC2_EBI if ARCH_STM32MP + select RESET_CONTROLLER if ARCH_STM32MP + select RESET_SIMPLE if ARCH_STM32MP + help + Enables support for NAND Flash chips on SoCs containing the FMC2 + NAND controller. This controller is found on STM32MP SoCs. + The controller supports a maximum 8k page size and supports + a maximum 8-bit correction error per sector of 512 bytes. + config NAND_ATMEL bool prompt "Atmel (AT91SAM9xxx) NAND driver" - depends on ARCH_AT91 + select GENERIC_ALLOCATOR if OFDEVICE + depends on ARCH_AT91 || (OFDEVICE && COMPILE_TEST) + +config NAND_ATMEL_LEGACY + def_bool !AT91_MULTI_BOARDS || SOC_AT91SAM9 + depends on NAND_ATMEL + help + Select legacy driver for non-DT-enabled platforms + and for the deprecated non-EBI binding. + + The deprecated binding is currently the only one + support for AT91SAM9. config NAND_ATMEL_PMECC bool prompt "PMECC support" - depends on NAND_ATMEL || COMPILE_TEST + depends on NAND_ATMEL_LEGACY help Support for PMECC present on the SoC sam9x5 and sam9n12 -config NAND_S3C24XX - bool - prompt "Samsung S3C24XX NAND driver" - depends on ARCH_S3C24xx - help - Add support for processor's NAND device controller. - config MTD_NAND_ECC_SW_HAMMING_SMC bool "NAND ECC Smart Media byte order" default n diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 4fd14ddd63..a0207d328b 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # Generic NAND options obj-$(CONFIG_NAND) += nand_ecc.o @@ -15,10 +16,9 @@ obj-$(CONFIG_NAND_IMX) += nand_imx.o obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o nand_omap_bch_decoder.o obj-$(CONFIG_MTD_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_ORION) += nand_orion.o +obj-$(CONFIG_NAND_STM32) += stm32_fmc2_nand.o obj-$(CONFIG_NAND_MRVL_NFC) += nand_mrvl_nfc.o -obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o -obj-$(CONFIG_NAND_S3C24XX) += nand_s3c24xx.o -pbl-$(CONFIG_NAND_S3C24XX) += nand_s3c24xx.o +obj-$(CONFIG_NAND_ATMEL) += atmel/ obj-$(CONFIG_NAND_MXS) += nand_mxs.o obj-$(CONFIG_MTD_NAND_DENALI) += nand_denali.o obj-$(CONFIG_MTD_NAND_DENALI_DT) += nand_denali_dt.o diff --git a/drivers/mtd/nand/atmel/Makefile b/drivers/mtd/nand/atmel/Makefile new file mode 100644 index 0000000000..0f739c3f31 --- /dev/null +++ b/drivers/mtd/nand/atmel/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_OFDEVICE) += nand-controller.o pmecc.o +obj-$(CONFIG_NAND_ATMEL_LEGACY) += legacy.o diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel/atmel_nand_ecc.h index e39aada36b..c7864d96dd 100644 --- a/drivers/mtd/nand/atmel_nand_ecc.h +++ b/drivers/mtd/nand/atmel/atmel_nand_ecc.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * 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 diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel/legacy.c index 58d53b7a78..cee9e49be0 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel/legacy.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2003 Rick Bronson * @@ -14,12 +15,6 @@ * Derived from Das U-Boot source code * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) * (C) Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas - * - * - * 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 <common.h> @@ -38,7 +33,7 @@ #include <linux/err.h> #include <io.h> -#include <mach/board.h> +#include <mach/at91/board.h> #include <errno.h> @@ -80,7 +75,7 @@ struct atmel_nand_host { struct nand_chip nand_chip; void __iomem *io_base; struct atmel_nand_data *board; - struct device_d *dev; + struct device *dev; void __iomem *ecc; int pmecc_bytes_per_sector; @@ -840,8 +835,8 @@ static int pmecc_build_galois_table(unsigned int mm, int16_t *index_of, return 0; } -static int __init atmel_pmecc_nand_init_params(struct device_d *dev, - struct atmel_nand_host *host) +static int __init atmel_pmecc_nand_init_params(struct device *dev, + struct atmel_nand_host *host) { struct resource *iores; struct nand_chip *nand_chip = &host->nand_chip; @@ -858,10 +853,6 @@ static int __init atmel_pmecc_nand_init_params(struct device_d *dev, if (IS_ERR(iores)) return PTR_ERR(iores); host->ecc = IOMEM(iores->start); - if (IS_ERR(host->ecc)) { - dev_err(host->dev, "ioremap failed\n"); - return -EIO; - } iores = dev_request_mem_resource(dev, 2); if (IS_ERR(iores)) { @@ -918,7 +909,7 @@ static int __init atmel_pmecc_nand_init_params(struct device_d *dev, dev_err(host->dev, "No room for ECC bytes\n"); return -EINVAL; } - mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops); + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); break; case 512: case 1024: @@ -1204,7 +1195,7 @@ static int atmel_nand_of_init(struct atmel_nand_host *host, struct device_node * return 0; } -static int atmel_hw_nand_init_params(struct device_d *dev, +static int atmel_hw_nand_init_params(struct device *dev, struct atmel_nand_host *host) { struct resource *iores; @@ -1215,10 +1206,6 @@ static int atmel_hw_nand_init_params(struct device_d *dev, if (IS_ERR(iores)) return PTR_ERR(iores); host->ecc = IOMEM(iores->start); - if (IS_ERR(host->ecc)) { - dev_err(host->dev, "ioremap failed\n"); - return -EIO; - } /* ECC is calculated for the whole page (1 step) */ nand_chip->ecc.size = mtd->writesize; @@ -1266,7 +1253,7 @@ static int atmel_hw_nand_init_params(struct device_d *dev, /* * Probe for the NAND device. */ -static int __init atmel_nand_probe(struct device_d *dev) +static int __init atmel_nand_probe(struct device *dev) { struct resource *iores; struct atmel_nand_data *pdata = NULL; @@ -1294,8 +1281,8 @@ static int __init atmel_nand_probe(struct device_d *dev) host->board = pdata; host->dev = dev; - if (dev->device_node) { - res = atmel_nand_of_init(host, dev->device_node); + if (dev->of_node) { + res = atmel_nand_of_init(host, dev->of_node); if (res) goto err_no_card; } else { @@ -1349,19 +1336,14 @@ static int __init atmel_nand_probe(struct device_d *dev) nand_chip->ecc.mode = pdata->ecc_mode; nand_chip->ecc.strength = pdata->ecc_strength ? : 1; - nand_chip->ecc.size = 1 << pdata->ecc_size_shift ? : 512; + nand_chip->ecc.size = 1 << (pdata->ecc_size_shift ? : 9); - if (pdata->ecc_mode == NAND_ECC_HW) { - nand_chip->ecc.mode = NAND_ECC_HW; + if (pdata->ecc_mode == NAND_ECC_SOFT) { + nand_chip->ecc.algo = NAND_ECC_ALGO_HAMMING; } nand_chip->legacy.chip_delay = 40; /* 40us command delay time */ - if (IS_ENABLED(CONFIG_NAND_ECC_BCH) && - pdata->ecc_mode == NAND_ECC_SOFT_BCH) { - nand_chip->ecc.mode = NAND_ECC_SOFT_BCH; - } - if (host->board->bus_width_16) { /* 16-bit bus width */ nand_chip->options |= NAND_BUSWIDTH_16; nand_chip->legacy.read_buf = atmel_read_buf16; @@ -1446,7 +1428,7 @@ static struct of_device_id atmel_nand_dt_ids[] = { { /* sentinel */ } }; -static struct driver_d atmel_nand_driver = { +static struct driver atmel_nand_driver = { .name = "atmel_nand", .probe = atmel_nand_probe, .of_compatible = DRV_OF_COMPAT(atmel_nand_dt_ids), diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c new file mode 100644 index 0000000000..5188a11cbe --- /dev/null +++ b/drivers/mtd/nand/atmel/nand-controller.c @@ -0,0 +1,2049 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 ATMEL + * Copyright 2017 Free Electrons + * + * Author: Boris Brezillon <boris.brezillon@free-electrons.com> + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright 2000 Steven J. Hill (sjhill@cotw.com) + * + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + * + * A few words about the naming convention in this file. This convention + * applies to structure and function names. + * + * Prefixes: + * + * - atmel_nand_: all generic structures/functions + * - atmel_smc_nand_: all structures/functions specific to the SMC interface + * (at91sam9 and avr32 SoCs) + * - atmel_hsmc_nand_: all structures/functions specific to the HSMC interface + * (sama5 SoCs and later) + * - atmel_nfc_: all structures/functions used to manipulate the NFC sub-block + * that is available in the HSMC block + * - <soc>_nand_: all SoC specific structures/functions + */ + +#include <linux/clk.h> +#include <linux/genalloc.h> +#include <linux/gpio/consumer.h> +#include <mfd/syscon.h> +#include <linux/mfd/syscon/atmel-matrix.h> +#include <linux/mfd/syscon/atmel-smc.h> +#include <module.h> +#include <linux/mtd/rawnand.h> +#include <of_address.h> +#include <of.h> +#include <of_device.h> +#include <linux/iopoll.h> +#include <linux/regmap.h> +#include <soc/at91/atmel-sfr.h> + +#include "pmecc.h" + +#define ATMEL_HSMC_NFC_CFG 0x0 +#define ATMEL_HSMC_NFC_CFG_SPARESIZE(x) (((x) / 4) << 24) +#define ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK GENMASK(30, 24) +#define ATMEL_HSMC_NFC_CFG_DTO(cyc, mul) (((cyc) << 16) | ((mul) << 20)) +#define ATMEL_HSMC_NFC_CFG_DTO_MAX GENMASK(22, 16) +#define ATMEL_HSMC_NFC_CFG_RBEDGE BIT(13) +#define ATMEL_HSMC_NFC_CFG_FALLING_EDGE BIT(12) +#define ATMEL_HSMC_NFC_CFG_RSPARE BIT(9) +#define ATMEL_HSMC_NFC_CFG_WSPARE BIT(8) +#define ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK GENMASK(2, 0) +#define ATMEL_HSMC_NFC_CFG_PAGESIZE(x) (fls((x) / 512) - 1) + +#define ATMEL_HSMC_NFC_CTRL 0x4 +#define ATMEL_HSMC_NFC_CTRL_EN BIT(0) +#define ATMEL_HSMC_NFC_CTRL_DIS BIT(1) + +#define ATMEL_HSMC_NFC_SR 0x8 +#define ATMEL_HSMC_NFC_IER 0xc +#define ATMEL_HSMC_NFC_IDR 0x10 +#define ATMEL_HSMC_NFC_IMR 0x14 +#define ATMEL_HSMC_NFC_SR_ENABLED BIT(1) +#define ATMEL_HSMC_NFC_SR_RB_RISE BIT(4) +#define ATMEL_HSMC_NFC_SR_RB_FALL BIT(5) +#define ATMEL_HSMC_NFC_SR_BUSY BIT(8) +#define ATMEL_HSMC_NFC_SR_WR BIT(11) +#define ATMEL_HSMC_NFC_SR_CSID GENMASK(14, 12) +#define ATMEL_HSMC_NFC_SR_XFRDONE BIT(16) +#define ATMEL_HSMC_NFC_SR_CMDDONE BIT(17) +#define ATMEL_HSMC_NFC_SR_DTOE BIT(20) +#define ATMEL_HSMC_NFC_SR_UNDEF BIT(21) +#define ATMEL_HSMC_NFC_SR_AWB BIT(22) +#define ATMEL_HSMC_NFC_SR_NFCASE BIT(23) +#define ATMEL_HSMC_NFC_SR_ERRORS (ATMEL_HSMC_NFC_SR_DTOE | \ + ATMEL_HSMC_NFC_SR_UNDEF | \ + ATMEL_HSMC_NFC_SR_AWB | \ + ATMEL_HSMC_NFC_SR_NFCASE) +#define ATMEL_HSMC_NFC_SR_RBEDGE(x) BIT((x) + 24) + +#define ATMEL_HSMC_NFC_ADDR 0x18 +#define ATMEL_HSMC_NFC_BANK 0x1c + +#define ATMEL_NFC_MAX_RB_ID 7 + +#define ATMEL_NFC_SRAM_SIZE 0x2400 + +#define ATMEL_NFC_CMD(pos, cmd) ((cmd) << (((pos) * 8) + 2)) +#define ATMEL_NFC_VCMD2 BIT(18) +#define ATMEL_NFC_ACYCLE(naddrs) ((naddrs) << 19) +#define ATMEL_NFC_CSID(cs) ((cs) << 22) +#define ATMEL_NFC_DATAEN BIT(25) +#define ATMEL_NFC_NFCWR BIT(26) + +#define ATMEL_NFC_MAX_ADDR_CYCLES 5 + +#define ATMEL_NAND_ALE_OFFSET BIT(21) +#define ATMEL_NAND_CLE_OFFSET BIT(22) + +#define DEFAULT_TIMEOUT_MS 1000 + +enum atmel_nand_rb_type { + ATMEL_NAND_NO_RB, + ATMEL_NAND_NATIVE_RB, + ATMEL_NAND_GPIO_RB, +}; + +struct atmel_nand_rb { + enum atmel_nand_rb_type type; + union { + struct gpio_desc *gpio; + int id; + }; +}; + +struct atmel_nand_cs { + int id; + struct atmel_nand_rb rb; + struct gpio_desc *csgpio; + struct { + void __iomem *virt; + } io; + + struct atmel_smc_cs_conf smcconf; +}; + +struct atmel_nand { + struct list_head node; + struct device *dev; + struct nand_chip base; + struct atmel_nand_cs *activecs; + struct atmel_pmecc_user *pmecc; + struct gpio_desc *cdgpio; + int numcs; + struct atmel_nand_cs cs[]; +}; + +static inline struct atmel_nand *to_atmel_nand(struct nand_chip *chip) +{ + return container_of(chip, struct atmel_nand, base); +} + +enum atmel_nfc_data_xfer { + ATMEL_NFC_NO_DATA, + ATMEL_NFC_READ_DATA, + ATMEL_NFC_WRITE_DATA, +}; + +struct atmel_nfc_op { + u8 cs; + u8 ncmds; + u8 cmds[2]; + u8 naddrs; + u8 addrs[5]; + enum atmel_nfc_data_xfer data; + u32 wait; + u32 errors; +}; + +struct atmel_nand_controller; +struct atmel_nand_controller_caps; + +struct atmel_nand_controller_ops { + int (*probe)(struct device *dev, + const struct atmel_nand_controller_caps *caps); + void (*nand_init)(struct atmel_nand_controller *nc, + struct atmel_nand *nand); + int (*ecc_init)(struct nand_chip *chip); + int (*setup_interface)(struct atmel_nand *nand, int csline, + const struct nand_interface_config *conf); + int (*exec_op)(struct atmel_nand *nand, + const struct nand_operation *op, bool check_only); +}; + +struct atmel_nand_controller_caps { + u32 ale_offs; + u32 cle_offs; + const char *ebi_csa_regmap_name; + const struct atmel_nand_controller_ops *ops; +}; + +struct atmel_nand_controller { + struct nand_controller base; + const struct atmel_nand_controller_caps *caps; + struct device *dev; + struct regmap *smc; + struct atmel_pmecc *pmecc; + struct list_head chips; + struct clk *mck; +}; + +static inline struct atmel_nand_controller * +to_nand_controller(struct nand_controller *ctl) +{ + return container_of(ctl, struct atmel_nand_controller, base); +} + +struct atmel_smc_nand_ebi_csa_cfg { + u32 offs; + u32 nfd0_on_d16; +}; + +struct atmel_smc_nand_controller { + struct atmel_nand_controller base; + struct regmap *ebi_csa_regmap; + struct atmel_smc_nand_ebi_csa_cfg *ebi_csa; +}; + +static inline struct atmel_smc_nand_controller * +to_smc_nand_controller(struct nand_controller *ctl) +{ + return container_of(to_nand_controller(ctl), + struct atmel_smc_nand_controller, base); +} + +struct atmel_hsmc_nand_controller { + struct atmel_nand_controller base; + struct { + struct gen_pool *pool; + void __iomem *virt; + } sram; + const struct atmel_hsmc_reg_layout *hsmc_layout; + struct regmap *io; + struct atmel_nfc_op op; + u32 cfg; +}; + +static inline struct atmel_hsmc_nand_controller * +to_hsmc_nand_controller(struct nand_controller *ctl) +{ + return container_of(to_nand_controller(ctl), + struct atmel_hsmc_nand_controller, base); +} + +static bool atmel_nfc_op_done(struct atmel_nfc_op *op, u32 status) +{ + op->errors |= status & ATMEL_HSMC_NFC_SR_ERRORS; + op->wait ^= status & op->wait; + + return !op->wait || op->errors; +} + +static int atmel_nfc_wait(struct atmel_hsmc_nand_controller *nc, + unsigned int timeout_ms) +{ + u32 status; + int ret; + + if (!timeout_ms) + timeout_ms = DEFAULT_TIMEOUT_MS; + + + ret = regmap_read_poll_timeout(nc->base.smc, + ATMEL_HSMC_NFC_SR, status, + atmel_nfc_op_done(&nc->op, + status), + timeout_ms * 1000); + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_DTOE) { + dev_err(nc->base.dev, "Waiting NAND R/B Timeout\n"); + ret = -ETIMEDOUT; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_UNDEF) { + dev_err(nc->base.dev, "Access to an undefined area\n"); + ret = -EIO; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_AWB) { + dev_err(nc->base.dev, "Access while busy\n"); + ret = -EIO; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_NFCASE) { + dev_err(nc->base.dev, "Wrong access size\n"); + ret = -EIO; + } + + return ret; +} + +static int atmel_nfc_exec_op(struct atmel_hsmc_nand_controller *nc) +{ + u8 *addrs = nc->op.addrs; + unsigned int op = 0; + u32 addr, val; + int i, ret; + + nc->op.wait = ATMEL_HSMC_NFC_SR_CMDDONE; + + for (i = 0; i < nc->op.ncmds; i++) + op |= ATMEL_NFC_CMD(i, nc->op.cmds[i]); + + if (nc->op.naddrs == ATMEL_NFC_MAX_ADDR_CYCLES) + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_ADDR, *addrs++); + + op |= ATMEL_NFC_CSID(nc->op.cs) | + ATMEL_NFC_ACYCLE(nc->op.naddrs); + + if (nc->op.ncmds > 1) + op |= ATMEL_NFC_VCMD2; + + addr = addrs[0] | (addrs[1] << 8) | (addrs[2] << 16) | + (addrs[3] << 24); + + if (nc->op.data != ATMEL_NFC_NO_DATA) { + op |= ATMEL_NFC_DATAEN; + nc->op.wait |= ATMEL_HSMC_NFC_SR_XFRDONE; + + if (nc->op.data == ATMEL_NFC_WRITE_DATA) + op |= ATMEL_NFC_NFCWR; + } + + /* Clear all flags. */ + regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &val); + + /* Send the command. */ + regmap_write(nc->io, op, addr); + + ret = atmel_nfc_wait(nc, 0); + if (ret) + dev_err(nc->base.dev, + "Failed to send NAND command (err = %d)!", + ret); + + /* Reset the op state. */ + memset(&nc->op, 0, sizeof(nc->op)); + + return ret; +} + +static void atmel_nand_data_in(struct atmel_nand *nand, void *buf, + unsigned int len, bool force_8bit) +{ + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit) + ioread16_rep(nand->activecs->io.virt, buf, len / 2); + else + ioread8_rep(nand->activecs->io.virt, buf, len); +} + +static void atmel_nand_data_out(struct atmel_nand *nand, const void *buf, + unsigned int len, bool force_8bit) +{ + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit) + iowrite16_rep(nand->activecs->io.virt, buf, len / 2); + else + iowrite8_rep(nand->activecs->io.virt, buf, len); +} + +static int atmel_nand_waitrdy(struct atmel_nand *nand, unsigned int timeout_ms) +{ + if (nand->activecs->rb.type == ATMEL_NAND_NO_RB) + return nand_soft_waitrdy(&nand->base, timeout_ms); + + return nand_gpio_waitrdy(&nand->base, nand->activecs->rb.gpio, + timeout_ms); +} + +static int atmel_hsmc_nand_waitrdy(struct atmel_nand *nand, + unsigned int timeout_ms) +{ + struct atmel_hsmc_nand_controller *nc; + u32 status, mask; + + if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) + return atmel_nand_waitrdy(nand, timeout_ms); + + nc = to_hsmc_nand_controller(nand->base.controller); + mask = ATMEL_HSMC_NFC_SR_RBEDGE(nand->activecs->rb.id); + return regmap_read_poll_timeout(nc->base.smc, ATMEL_HSMC_NFC_SR, + status, status & mask, + timeout_ms * 1000); +} + +static void atmel_nand_select_target(struct atmel_nand *nand, + unsigned int cs) +{ + nand->activecs = &nand->cs[cs]; +} + +static void atmel_hsmc_nand_select_target(struct atmel_nand *nand, + unsigned int cs) +{ + struct mtd_info *mtd = nand_to_mtd(&nand->base); + struct atmel_hsmc_nand_controller *nc; + u32 cfg = ATMEL_HSMC_NFC_CFG_PAGESIZE(mtd->writesize) | + ATMEL_HSMC_NFC_CFG_SPARESIZE(mtd->oobsize) | + ATMEL_HSMC_NFC_CFG_RSPARE; + + nand->activecs = &nand->cs[cs]; + nc = to_hsmc_nand_controller(nand->base.controller); + if (nc->cfg == cfg) + return; + + regmap_update_bits(nc->base.smc, ATMEL_HSMC_NFC_CFG, + ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK | + ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK | + ATMEL_HSMC_NFC_CFG_RSPARE | + ATMEL_HSMC_NFC_CFG_WSPARE, + cfg); + nc->cfg = cfg; +} + +static int atmel_smc_nand_exec_instr(struct atmel_nand *nand, + const struct nand_op_instr *instr) +{ + struct atmel_nand_controller *nc; + unsigned int i; + + nc = to_nand_controller(nand->base.controller); + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb(instr->ctx.cmd.opcode, + nand->activecs->io.virt + nc->caps->cle_offs); + return 0; + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb(instr->ctx.addr.addrs[i], + nand->activecs->io.virt + nc->caps->ale_offs); + return 0; + case NAND_OP_DATA_IN_INSTR: + atmel_nand_data_in(nand, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + return 0; + case NAND_OP_DATA_OUT_INSTR: + atmel_nand_data_out(nand, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + return 0; + case NAND_OP_WAITRDY_INSTR: + return atmel_nand_waitrdy(nand, + instr->ctx.waitrdy.timeout_ms); + default: + break; + } + + return -EINVAL; +} + +static int atmel_smc_nand_exec_op(struct atmel_nand *nand, + const struct nand_operation *op, + bool check_only) +{ + unsigned int i; + int ret = 0; + + if (check_only) + return 0; + + atmel_nand_select_target(nand, op->cs); + gpiod_set_value(nand->activecs->csgpio, 0); + for (i = 0; i < op->ninstrs; i++) { + ret = atmel_smc_nand_exec_instr(nand, &op->instrs[i]); + if (ret) + break; + } + gpiod_set_value(nand->activecs->csgpio, 1); + + return ret; +} + +static int atmel_hsmc_exec_cmd_addr(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + unsigned int i, j; + + nc = to_hsmc_nand_controller(chip->controller); + + nc->op.cs = nand->activecs->id; + for (i = 0; i < subop->ninstrs; i++) { + const struct nand_op_instr *instr = &subop->instrs[i]; + + if (instr->type == NAND_OP_CMD_INSTR) { + nc->op.cmds[nc->op.ncmds++] = instr->ctx.cmd.opcode; + continue; + } + + for (j = nand_subop_get_addr_start_off(subop, i); + j < nand_subop_get_num_addr_cyc(subop, i); j++) { + nc->op.addrs[nc->op.naddrs] = instr->ctx.addr.addrs[j]; + nc->op.naddrs++; + } + } + + return atmel_nfc_exec_op(nc); +} + +static int atmel_hsmc_exec_rw(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr = subop->instrs; + struct atmel_nand *nand = to_atmel_nand(chip); + + if (instr->type == NAND_OP_DATA_IN_INSTR) + atmel_nand_data_in(nand, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + else + atmel_nand_data_out(nand, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + + return 0; +} + +static int atmel_hsmc_exec_waitrdy(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr = subop->instrs; + struct atmel_nand *nand = to_atmel_nand(chip); + + return atmel_hsmc_nand_waitrdy(nand, instr->ctx.waitrdy.timeout_ms); +} + +static const struct nand_op_parser atmel_hsmc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_cmd_addr, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), + NAND_OP_PARSER_PAT_CMD_ELEM(true)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw, + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_waitrdy, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), +); + +static int atmel_hsmc_nand_exec_op(struct atmel_nand *nand, + const struct nand_operation *op, + bool check_only) +{ + int ret; + + if (check_only) + return nand_op_parser_exec_op(&nand->base, + &atmel_hsmc_op_parser, op, true); + + atmel_hsmc_nand_select_target(nand, op->cs); + ret = nand_op_parser_exec_op(&nand->base, &atmel_hsmc_op_parser, op, + false); + + return ret; +} + +static void atmel_nfc_copy_to_sram(struct nand_chip *chip, const u8 *buf, + bool oob_required) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + + nc = to_hsmc_nand_controller(chip->controller); + + /* Falling back to CPU copy. */ + memcpy_toio(nc->sram.virt, buf, mtd->writesize); + + if (oob_required) + memcpy_toio(nc->sram.virt + mtd->writesize, chip->oob_poi, + mtd->oobsize); +} + +static void atmel_nfc_copy_from_sram(struct nand_chip *chip, u8 *buf, + bool oob_required) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + + nc = to_hsmc_nand_controller(chip->controller); + + memcpy_fromio(buf, nc->sram.virt, mtd->writesize); + + if (oob_required) + memcpy_fromio(chip->oob_poi, nc->sram.virt + mtd->writesize, + mtd->oobsize); +} + +static void atmel_nfc_set_op_addr(struct nand_chip *chip, int page, int column) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + + nc = to_hsmc_nand_controller(chip->controller); + + if (column >= 0) { + nc->op.addrs[nc->op.naddrs++] = column; + + /* + * 2 address cycles for the column offset on large page NANDs. + */ + if (mtd->writesize > 512) + nc->op.addrs[nc->op.naddrs++] = column >> 8; + } + + if (page >= 0) { + nc->op.addrs[nc->op.naddrs++] = page; + nc->op.addrs[nc->op.naddrs++] = page >> 8; + + if (chip->options & NAND_ROW_ADDR_3) + nc->op.addrs[nc->op.naddrs++] = page >> 16; + } +} + +static int atmel_nand_pmecc_enable(struct nand_chip *chip, int op, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_enable(nand->pmecc, op); + if (ret) + dev_err(nc->dev, + "Failed to enable ECC engine (err = %d)\n", ret); + + return ret; +} + +static void atmel_nand_pmecc_disable(struct nand_chip *chip, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + + if (!raw) + atmel_pmecc_disable(nand->pmecc); +} + +static int atmel_nand_pmecc_generate_eccbytes(struct nand_chip *chip, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand_controller *nc; + struct mtd_oob_region oobregion; + void *eccbuf; + int ret, i; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_wait_rdy(nand->pmecc); + if (ret) { + dev_err(nc->dev, + "Failed to transfer NAND page data (err = %d)\n", + ret); + return ret; + } + + mtd_ooblayout_ecc(mtd, 0, &oobregion); + eccbuf = chip->oob_poi + oobregion.offset; + + for (i = 0; i < chip->ecc.steps; i++) { + atmel_pmecc_get_generated_eccbytes(nand->pmecc, i, + eccbuf); + eccbuf += chip->ecc.bytes; + } + + return 0; +} + +static int atmel_nand_pmecc_correct_data(struct nand_chip *chip, void *buf, + bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand_controller *nc; + struct mtd_oob_region oobregion; + int ret, i, max_bitflips = 0; + void *databuf, *eccbuf; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_wait_rdy(nand->pmecc); + if (ret) { + dev_err(nc->dev, + "Failed to read NAND page data (err = %d)\n", + ret); + return ret; + } + + mtd_ooblayout_ecc(mtd, 0, &oobregion); + eccbuf = chip->oob_poi + oobregion.offset; + databuf = buf; + + for (i = 0; i < chip->ecc.steps; i++) { + ret = atmel_pmecc_correct_sector(nand->pmecc, i, databuf, + eccbuf); + if (ret < 0 && !atmel_pmecc_correct_erased_chunks(nand->pmecc)) + ret = nand_check_erased_ecc_chunk(databuf, + chip->ecc.size, + eccbuf, + chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + + if (ret >= 0) { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(ret, max_bitflips); + } else { + mtd->ecc_stats.failed++; + } + + databuf += chip->ecc.size; + eccbuf += chip->ecc.bytes; + } + + return max_bitflips; +} + +static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf, + bool oob_required, int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + int ret; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw); + if (ret) + return ret; + + nand_write_data_op(chip, buf, mtd->writesize, false); + + ret = atmel_nand_pmecc_generate_eccbytes(chip, raw); + if (ret) { + atmel_pmecc_disable(nand->pmecc); + return ret; + } + + atmel_nand_pmecc_disable(chip, raw); + + nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +static int atmel_nand_pmecc_write_page(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, false); +} + +static int atmel_nand_pmecc_write_page_raw(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, true); +} + +static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf, + bool oob_required, int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + nand_read_page_op(chip, page, 0, NULL, 0); + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw); + if (ret) + return ret; + + ret = nand_read_data_op(chip, buf, mtd->writesize, false, false); + if (ret) + goto out_disable; + + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, false); + if (ret) + goto out_disable; + + ret = atmel_nand_pmecc_correct_data(chip, buf, raw); + +out_disable: + atmel_nand_pmecc_disable(chip, raw); + + return ret; +} + +static int atmel_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, false); +} + +static int atmel_nand_pmecc_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, true); +} + +static int atmel_hsmc_nand_pmecc_write_pg(struct nand_chip *chip, + const u8 *buf, bool oob_required, + int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + int ret; + + atmel_hsmc_nand_select_target(nand, chip->cur_cs); + nc = to_hsmc_nand_controller(chip->controller); + + atmel_nfc_copy_to_sram(chip, buf, false); + + nc->op.cmds[0] = NAND_CMD_SEQIN; + nc->op.ncmds = 1; + atmel_nfc_set_op_addr(chip, page, 0x0); + nc->op.cs = nand->activecs->id; + nc->op.data = ATMEL_NFC_WRITE_DATA; + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw); + if (ret) + return ret; + + ret = atmel_nfc_exec_op(nc); + if (ret) { + atmel_nand_pmecc_disable(chip, raw); + dev_err(nc->base.dev, + "Failed to transfer NAND page data (err = %d)\n", + ret); + return ret; + } + + ret = atmel_nand_pmecc_generate_eccbytes(chip, raw); + + atmel_nand_pmecc_disable(chip, raw); + + if (ret) + return ret; + + nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +static int atmel_hsmc_nand_pmecc_write_page(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page, + false); +} + +static int atmel_hsmc_nand_pmecc_write_page_raw(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page, + true); +} + +static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf, + bool oob_required, int page, + bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + int ret; + + atmel_hsmc_nand_select_target(nand, chip->cur_cs); + nc = to_hsmc_nand_controller(chip->controller); + + /* + * Optimized read page accessors only work when the NAND R/B pin is + * connected to a native SoC R/B pin. If that's not the case, fallback + * to the non-optimized one. + */ + if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, + raw); + + nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READ0; + + if (mtd->writesize > 512) + nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READSTART; + + atmel_nfc_set_op_addr(chip, page, 0x0); + nc->op.cs = nand->activecs->id; + nc->op.data = ATMEL_NFC_READ_DATA; + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw); + if (ret) + return ret; + + ret = atmel_nfc_exec_op(nc); + if (ret) { + atmel_nand_pmecc_disable(chip, raw); + dev_err(nc->base.dev, + "Failed to load NAND page data (err = %d)\n", + ret); + return ret; + } + + atmel_nfc_copy_from_sram(chip, buf, true); + + ret = atmel_nand_pmecc_correct_data(chip, buf, raw); + + atmel_nand_pmecc_disable(chip, raw); + + return ret; +} + +static int atmel_hsmc_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page, + false); +} + +static int atmel_hsmc_nand_pmecc_read_page_raw(struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page, + true); +} + +static int atmel_nand_pmecc_init(struct nand_chip *chip) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + struct atmel_pmecc_user_req req; + struct device_node *dn; + + nc = to_nand_controller(chip->controller); + + if (!nc->pmecc) { + dev_err(nc->dev, "HW ECC not supported\n"); + return -ENOTSUPP; + } + + dn = nand_get_flash_node(chip); + + if (of_property_read_bool(dn, "nand-ecc-maximize")) + req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH; + else if (chip->ecc.strength) + req.ecc.strength = chip->ecc.strength; + else if (requirements->strength) + req.ecc.strength = requirements->strength; + else + req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH; + + if (chip->ecc.size) + req.ecc.sectorsize = chip->ecc.size; + else if (requirements->step_size) + req.ecc.sectorsize = requirements->step_size; + else + req.ecc.sectorsize = ATMEL_PMECC_SECTOR_SIZE_AUTO; + + req.pagesize = mtd->writesize; + req.oobsize = mtd->oobsize; + + if (mtd->writesize <= 512) { + req.ecc.bytes = 4; + req.ecc.ooboffset = 0; + } else { + req.ecc.bytes = mtd->oobsize - 2; + req.ecc.ooboffset = ATMEL_PMECC_OOBOFFSET_AUTO; + } + + nand->pmecc = atmel_pmecc_create_user(nc->pmecc, &req); + if (IS_ERR(nand->pmecc)) + return PTR_ERR(nand->pmecc); + + chip->ecc.algo = NAND_ECC_ALGO_BCH; + chip->ecc.size = req.ecc.sectorsize; + chip->ecc.bytes = req.ecc.bytes / req.ecc.nsectors; + chip->ecc.strength = req.ecc.strength; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + + return 0; +} + +static int atmel_nand_ecc_init(struct nand_chip *chip) +{ + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(chip->controller); + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + /* + * Nothing to do, the core will initialize everything for us. + */ + break; + + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = atmel_nand_pmecc_init(chip); + if (ret) + return ret; + + chip->ecc.read_page = atmel_nand_pmecc_read_page; + chip->ecc.write_page = atmel_nand_pmecc_write_page; + chip->ecc.read_page_raw = atmel_nand_pmecc_read_page_raw; + chip->ecc.write_page_raw = atmel_nand_pmecc_write_page_raw; + break; + + default: + /* Other modes are not supported. */ + dev_err(nc->dev, "Unsupported ECC mode: %d\n", + chip->ecc.engine_type); + return -ENOTSUPP; + } + + return 0; +} + +static int atmel_hsmc_nand_ecc_init(struct nand_chip *chip) +{ + int ret; + + ret = atmel_nand_ecc_init(chip); + if (ret) + return ret; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + /* Adjust the ECC operations for the HSMC IP. */ + chip->ecc.read_page = atmel_hsmc_nand_pmecc_read_page; + chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page; + chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw; + chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw; + + return 0; +} + +static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand, + const struct nand_interface_config *conf, + struct atmel_smc_cs_conf *smcconf) +{ + u32 ncycles, totalcycles, timeps, mckperiodps; + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(nand->base.controller); + + /* DDR interface not supported. */ + if (!nand_interface_is_sdr(conf)) + return -ENOTSUPP; + + /* + * tRC < 30ns implies EDO mode. This controller does not support this + * mode. + */ + if (conf->timings.sdr.tRC_min < 30000) + return -ENOTSUPP; + + atmel_smc_cs_conf_init(smcconf); + + mckperiodps = NSEC_PER_SEC / clk_get_rate(nc->mck); + mckperiodps *= 1000; + + /* + * Set write pulse timing. This one is easy to extract: + * + * NWE_PULSE = tWP + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWP_min, mckperiodps); + totalcycles = ncycles; + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * The write setup timing depends on the operation done on the NAND. + * All operations goes through the same data bus, but the operation + * type depends on the address we are writing to (ALE/CLE address + * lines). + * Since we have no way to differentiate the different operations at + * the SMC level, we must consider the worst case (the biggest setup + * time among all operation types): + * + * NWE_SETUP = max(tCLS, tCS, tALS, tDS) - NWE_PULSE + */ + timeps = max3(conf->timings.sdr.tCLS_min, conf->timings.sdr.tCS_min, + conf->timings.sdr.tALS_min); + timeps = max(timeps, conf->timings.sdr.tDS_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + ncycles = ncycles > totalcycles ? ncycles - totalcycles : 0; + totalcycles += ncycles; + ret = atmel_smc_cs_conf_set_setup(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * As for the write setup timing, the write hold timing depends on the + * operation done on the NAND: + * + * NWE_HOLD = max(tCLH, tCH, tALH, tDH, tWH) + */ + timeps = max3(conf->timings.sdr.tCLH_min, conf->timings.sdr.tCH_min, + conf->timings.sdr.tALH_min); + timeps = max3(timeps, conf->timings.sdr.tDH_min, + conf->timings.sdr.tWH_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + totalcycles += ncycles; + + /* + * The write cycle timing is directly matching tWC, but is also + * dependent on the other timings on the setup and hold timings we + * calculated earlier, which gives: + * + * NWE_CYCLE = max(tWC, NWE_SETUP + NWE_PULSE + NWE_HOLD) + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWC_min, mckperiodps); + ncycles = max(totalcycles, ncycles); + ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * We don't want the CS line to be toggled between each byte/word + * transfer to the NAND. The only way to guarantee that is to have the + * NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means: + * + * NCS_WR_PULSE = NWE_CYCLE + */ + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_WR_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * As for the write setup timing, the read hold timing depends on the + * operation done on the NAND: + * + * NRD_HOLD = max(tREH, tRHOH) + */ + timeps = max(conf->timings.sdr.tREH_min, conf->timings.sdr.tRHOH_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + totalcycles = ncycles; + + /* + * TDF = tRHZ - NRD_HOLD + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRHZ_max, mckperiodps); + ncycles -= totalcycles; + + /* + * In ONFI 4.0 specs, tRHZ has been increased to support EDO NANDs and + * we might end up with a config that does not fit in the TDF field. + * Just take the max value in this case and hope that the NAND is more + * tolerant than advertised. + */ + if (ncycles > ATMEL_SMC_MODE_TDF_MAX) + ncycles = ATMEL_SMC_MODE_TDF_MAX; + else if (ncycles < ATMEL_SMC_MODE_TDF_MIN) + ncycles = ATMEL_SMC_MODE_TDF_MIN; + + smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles) | + ATMEL_SMC_MODE_TDFMODE_OPTIMIZED; + + /* + * Read pulse timing directly matches tRP: + * + * NRD_PULSE = tRP + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps); + totalcycles += ncycles; + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * The write cycle timing is directly matching tWC, but is also + * dependent on the setup and hold timings we calculated earlier, + * which gives: + * + * NRD_CYCLE = max(tRC, NRD_PULSE + NRD_HOLD) + * + * NRD_SETUP is always 0. + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRC_min, mckperiodps); + ncycles = max(totalcycles, ncycles); + ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NRD_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * We don't want the CS line to be toggled between each byte/word + * transfer from the NAND. The only way to guarantee that is to have + * the NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means: + * + * NCS_RD_PULSE = NRD_CYCLE + */ + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_RD_SHIFT, + ncycles); + if (ret) + return ret; + + /* Txxx timings are directly matching tXXX ones. */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tCLR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TCLR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tADL_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TADL_SHIFT, + ncycles); + /* + * Version 4 of the ONFI spec mandates that tADL be at least 400 + * nanoseconds, but, depending on the master clock rate, 400 ns may not + * fit in the tADL field of the SMC reg. We need to relax the check and + * accept the -ERANGE return code. + * + * Note that previous versions of the ONFI spec had a lower tADL_min + * (100 or 200 ns). It's not clear why this timing constraint got + * increased but it seems most NANDs are fine with values lower than + * 400ns, so we should be safe. + */ + if (ret && ret != -ERANGE) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tAR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TAR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TRR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWB_max, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TWB_SHIFT, + ncycles); + if (ret) + return ret; + + /* Attach the CS line to the NFC logic. */ + smcconf->timings |= ATMEL_HSMC_TIMINGS_NFSEL; + + /* Set the appropriate data bus width. */ + if (nand->base.options & NAND_BUSWIDTH_16) + smcconf->mode |= ATMEL_SMC_MODE_DBW_16; + + /* Operate in NRD/NWE READ/WRITEMODE. */ + smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD | + ATMEL_SMC_MODE_WRITEMODE_NWE; + + return 0; +} + +static int atmel_smc_nand_setup_interface(struct atmel_nand *nand, + int csline, + const struct nand_interface_config *conf) +{ + struct atmel_nand_controller *nc; + struct atmel_smc_cs_conf smcconf; + struct atmel_nand_cs *cs; + int ret; + + nc = to_nand_controller(nand->base.controller); + + ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf); + if (ret) + return ret; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + cs = &nand->cs[csline]; + cs->smcconf = smcconf; + atmel_smc_cs_conf_apply(nc->smc, cs->id, &cs->smcconf); + + return 0; +} + +static int atmel_hsmc_nand_setup_interface(struct atmel_nand *nand, + int csline, + const struct nand_interface_config *conf) +{ + struct atmel_hsmc_nand_controller *nc; + struct atmel_smc_cs_conf smcconf; + struct atmel_nand_cs *cs; + int ret; + + nc = to_hsmc_nand_controller(nand->base.controller); + + ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf); + if (ret) + return ret; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + cs = &nand->cs[csline]; + cs->smcconf = smcconf; + + if (cs->rb.type == ATMEL_NAND_NATIVE_RB) + cs->smcconf.timings |= ATMEL_HSMC_TIMINGS_RBNSEL(cs->rb.id); + + atmel_hsmc_cs_conf_apply(nc->base.smc, nc->hsmc_layout, cs->id, + &cs->smcconf); + + return 0; +} + +static int atmel_nand_setup_interface(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + const struct nand_sdr_timings *sdr; + struct atmel_nand_controller *nc; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + nc = to_nand_controller(nand->base.controller); + + if (csline >= nand->numcs || + (csline < 0 && csline != NAND_DATA_IFACE_CHECK_ONLY)) + return -EINVAL; + + return nc->caps->ops->setup_interface(nand, csline, conf); +} + +static int atmel_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + return nc->caps->ops->exec_op(nand, op, check_only); +} + +static void atmel_nand_init(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct mtd_info *mtd = nand_to_mtd(chip); + + mtd->dev.parent = nc->dev; + nand->base.controller = &nc->base; + + if (!nc->mck || !nc->caps->ops->setup_interface) + chip->options |= NAND_KEEP_TIMINGS; + + /* Default to HW ECC if pmecc is available. */ + if (nc->pmecc) + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; +} + +static void atmel_smc_nand_init(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct atmel_smc_nand_controller *smc_nc; + int i; + + atmel_nand_init(nc, nand); + + smc_nc = to_smc_nand_controller(chip->controller); + if (!smc_nc->ebi_csa_regmap) + return; + + /* Attach the CS to the NAND Flash logic. */ + for (i = 0; i < nand->numcs; i++) + regmap_update_bits(smc_nc->ebi_csa_regmap, + smc_nc->ebi_csa->offs, + BIT(nand->cs[i].id), BIT(nand->cs[i].id)); + + if (smc_nc->ebi_csa->nfd0_on_d16) + regmap_update_bits(smc_nc->ebi_csa_regmap, + smc_nc->ebi_csa->offs, + smc_nc->ebi_csa->nfd0_on_d16, + smc_nc->ebi_csa->nfd0_on_d16); +} + +static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, + struct device_node *np, + int reg_cells) +{ + struct atmel_nand *nand; + struct gpio_desc *gpio; + int numcs, ret, i; + + numcs = of_property_count_elems_of_size(np, "reg", + reg_cells * sizeof(u32)); + if (numcs < 1) { + dev_err(nc->dev, "Missing or invalid reg property\n"); + return ERR_PTR(-EINVAL); + } + + nand = kzalloc(struct_size(nand, cs, numcs), GFP_KERNEL); + if (!nand) + return ERR_PTR(-ENOMEM); + + nand->numcs = numcs; + + gpio = dev_gpiod_get(nc->dev, np, "det", GPIOD_IN, "nand-det"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_err(nc->dev, + "Failed to get detect gpio (err = %ld)\n", + PTR_ERR(gpio)); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) + nand->cdgpio = gpio; + + for (i = 0; i < numcs; i++) { + struct resource res; + u32 val; + + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(nc->dev, "Invalid reg property (err = %d)\n", + ret); + return ERR_PTR(ret); + } + + ret = of_property_read_u32_index(np, "reg", i * reg_cells, + &val); + if (ret) { + dev_err(nc->dev, "Invalid reg property (err = %d)\n", + ret); + return ERR_PTR(ret); + } + + nand->cs[i].id = val; + + nand->cs[i].io.virt = IOMEM(res.start); + ret = dev_request_resource(nc->dev, &res); + if (ret < 0) + return ERR_PTR(ret); + + if (!of_property_read_u32(np, "atmel,rb", &val)) { + if (val > ATMEL_NFC_MAX_RB_ID) + return ERR_PTR(-EINVAL); + + nand->cs[i].rb.type = ATMEL_NAND_NATIVE_RB; + nand->cs[i].rb.id = val; + } else { + gpio = dev_gpiod_get_index(nc->dev, np, "rb", i, GPIOD_IN, "nand-rb"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_errp_probe(nc->dev, gpio, "Failed to get detect gpio\n"); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) { + nand->cs[i].rb.type = ATMEL_NAND_GPIO_RB; + nand->cs[i].rb.gpio = gpio; + } + } + + gpio = dev_gpiod_get_index(nc->dev, np, "cs", i, GPIOD_OUT_HIGH, "nand-cs"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_errp_probe(nc->dev, gpio, "Failed to get CS gpio\n"); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) + nand->cs[i].csgpio = gpio; + } + + nand_set_flash_node(&nand->base, np); + + return nand; +} + +static int +atmel_nand_controller_add_nand(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* No card inserted, skip this NAND. */ + if (nand->cdgpio && gpiod_get_value(nand->cdgpio)) { + dev_info(nc->dev, "No SmartMedia card inserted.\n"); + return 0; + } + + nc->caps->ops->nand_init(nc, nand); + + ret = nand_scan(chip, nand->numcs); + if (ret) { + dev_err(nc->dev, "NAND scan failed: %d\n", ret); + return ret; + } + + ret = add_mtd_nand_device(mtd, "nand"); + if (ret) { + dev_err(nc->dev, "Failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + list_add_tail(&nand->node, &nc->chips); + + return 0; +} + +static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc) +{ + struct device_node *np, *nand_np; + struct device *dev = nc->dev; + int ret, reg_cells; + u32 val; + + np = dev->of_node; + + ret = of_property_read_u32(np, "#address-cells", &val); + if (ret) { + dev_err(dev, "missing #address-cells property\n"); + return ret; + } + + reg_cells = val; + + ret = of_property_read_u32(np, "#size-cells", &val); + if (ret) { + dev_err(dev, "missing #size-cells property\n"); + return ret; + } + + reg_cells += val; + + for_each_child_of_node(np, nand_np) { + struct atmel_nand *nand; + + nand = atmel_nand_create(nc, nand_np, reg_cells); + if (IS_ERR(nand)) + return PTR_ERR(nand); + + ret = atmel_nand_controller_add_nand(nc, nand); + if (ret) + return ret; + } + + return 0; +} + +static void atmel_nand_controller_cleanup(struct atmel_nand_controller *nc) +{ + clk_put(nc->mck); +} + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9260_ebi_csa = { + .offs = AT91SAM9260_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9261_ebi_csa = { + .offs = AT91SAM9261_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9263_ebi_csa = { + .offs = AT91SAM9263_MATRIX_EBI0CSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9rl_ebi_csa = { + .offs = AT91SAM9RL_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9g45_ebi_csa = { + .offs = AT91SAM9G45_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9n12_ebi_csa = { + .offs = AT91SAM9N12_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9x5_ebi_csa = { + .offs = AT91SAM9X5_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = { + .offs = AT91_SFR_CCFG_EBICSA, + .nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16, +}; + +static const struct of_device_id __maybe_unused atmel_ebi_csa_regmap_of_ids[] = { + { + .compatible = "atmel,at91sam9260-matrix", + .data = &at91sam9260_ebi_csa, + }, + { + .compatible = "atmel,at91sam9261-matrix", + .data = &at91sam9261_ebi_csa, + }, + { + .compatible = "atmel,at91sam9263-matrix", + .data = &at91sam9263_ebi_csa, + }, + { + .compatible = "atmel,at91sam9rl-matrix", + .data = &at91sam9rl_ebi_csa, + }, + { + .compatible = "atmel,at91sam9g45-matrix", + .data = &at91sam9g45_ebi_csa, + }, + { + .compatible = "atmel,at91sam9n12-matrix", + .data = &at91sam9n12_ebi_csa, + }, + { + .compatible = "atmel,at91sam9x5-matrix", + .data = &at91sam9x5_ebi_csa, + }, + { + .compatible = "microchip,sam9x60-sfr", + .data = &sam9x60_ebi_csa, + }, + { /* sentinel */ }, +}; + +static int atmel_nand_attach_chip(struct nand_chip *chip) +{ + struct atmel_nand_controller *nc = to_nand_controller(chip->controller); + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nc->caps->ops->ecc_init(chip); + if (ret) + return ret; + + if (!mtd->name) { + /* + * If the new bindings are used and the bootloader has not been + * updated to pass a new mtdparts parameter on the cmdline, you + * should define the following property in your nand node: + * + * label = "atmel_nand"; + * + * This way, mtd->name will be set by the core when + * nand_set_flash_node() is called. + */ + mtd->name = basprintf("%s:nand.%d", dev_name(nc->dev), + nand->cs[0].id); + if (!mtd->name) { + dev_err(nc->dev, "Failed to allocate mtd->name\n"); + return -ENOMEM; + } + } + + return 0; +} + +static const struct nand_controller_ops atmel_nand_controller_ops = { + .attach_chip = atmel_nand_attach_chip, + .setup_interface = atmel_nand_setup_interface, + .exec_op = atmel_nand_exec_op, +}; + +static int atmel_nand_controller_init(struct atmel_nand_controller *nc, + struct device *dev, + const struct atmel_nand_controller_caps *caps) +{ + struct device_node *np = dev->of_node; + int ret; + + nand_controller_init(&nc->base); + nc->base.ops = &atmel_nand_controller_ops; + INIT_LIST_HEAD(&nc->chips); + nc->dev = dev; + nc->caps = caps; + + dev->priv = nc; + + nc->pmecc = dev_atmel_pmecc_get(dev); + if (IS_ERR(nc->pmecc)) + return dev_err_probe(dev, PTR_ERR(nc->pmecc), + "Could not get PMECC object\n"); + + nc->mck = of_clk_get(dev->parent->of_node, 0); + if (IS_ERR(nc->mck)) { + dev_err(dev, "Failed to retrieve MCK clk\n"); + ret = PTR_ERR(nc->mck); + goto out_release_dma; + } + + np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,smc property\n"); + ret = -EINVAL; + goto out_release_dma; + } + + nc->smc = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->smc)) { + ret = PTR_ERR(nc->smc); + dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret); + goto out_release_dma; + } + + return 0; + +out_release_dma: + return ret; +} + +static int +atmel_smc_nand_controller_init(struct atmel_smc_nand_controller *nc) +{ + struct device *dev = nc->base.dev; + const struct of_device_id *match; + struct device_node *np; + int ret; + + np = of_parse_phandle(dev->parent->of_node, + nc->base.caps->ebi_csa_regmap_name, 0); + if (!np) + return 0; + + match = of_match_node(atmel_ebi_csa_regmap_of_ids, np); + if (!match) { + of_node_put(np); + return 0; + } + + nc->ebi_csa_regmap = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->ebi_csa_regmap)) { + ret = PTR_ERR(nc->ebi_csa_regmap); + dev_err(dev, "Could not get EBICSA regmap (err = %d)\n", ret); + return ret; + } + + nc->ebi_csa = (struct atmel_smc_nand_ebi_csa_cfg *)match->data; + + /* + * The at91sam9263 has 2 EBIs, if the NAND controller is under EBI1 + * add 4 to ->ebi_csa->offs. + */ + if (of_device_is_compatible(dev->parent->of_node, + "atmel,at91sam9263-ebi1")) + nc->ebi_csa->offs += 4; + + return 0; +} + +static int +atmel_hsmc_nand_controller_init(struct atmel_hsmc_nand_controller *nc) +{ + struct device *dev = nc->base.dev; + struct device_node *np; + int ret; + + np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,smc property\n"); + return -EINVAL; + } + + nc->hsmc_layout = atmel_hsmc_get_reg_layout(np); + + np = of_parse_phandle(dev->of_node, "atmel,nfc-io", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,nfc-io property\n"); + return -EINVAL; + } + + nc->io = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->io)) { + ret = PTR_ERR(nc->io); + dev_err(dev, "Could not get NFC IO regmap (err = %d)\n", ret); + return ret; + } + + nc->sram.pool = of_gen_pool_get(nc->base.dev->of_node, + "atmel,nfc-sram", 0); + if (!nc->sram.pool) { + dev_err(nc->base.dev, "Missing SRAM\n"); + return -ENOMEM; + } + + nc->sram.virt = (void __iomem *)gen_pool_dma_alloc(nc->sram.pool, + ATMEL_NFC_SRAM_SIZE, + NULL); + if (!nc->sram.virt) { + dev_err(nc->base.dev, + "Could not allocate memory from the NFC SRAM pool\n"); + return -ENOMEM; + } + + return 0; +} + +static void +atmel_hsmc_nand_controller_remove(struct atmel_nand_controller *nc) +{ + struct atmel_hsmc_nand_controller *hsmc_nc; + + hsmc_nc = container_of(nc, struct atmel_hsmc_nand_controller, base); + regmap_write(hsmc_nc->base.smc, ATMEL_HSMC_NFC_CTRL, + ATMEL_HSMC_NFC_CTRL_DIS); + + atmel_nand_controller_cleanup(nc); +} + +static int atmel_hsmc_nand_controller_probe(struct device *dev, + const struct atmel_nand_controller_caps *caps) +{ + struct atmel_hsmc_nand_controller *nc; + int ret; + + nc = kzalloc(sizeof(*nc), GFP_KERNEL); + if (!nc) + return -ENOMEM; + + ret = atmel_nand_controller_init(&nc->base, dev, caps); + if (ret) + return ret; + + ret = atmel_hsmc_nand_controller_init(nc); + if (ret) + return ret; + + /* Make sure all irqs are masked . */ + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, 0xffffffff); + + /* Initial NFC configuration. */ + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CFG, + ATMEL_HSMC_NFC_CFG_DTO_MAX); + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CTRL, + ATMEL_HSMC_NFC_CTRL_EN); + + ret = atmel_nand_controller_add_nands(&nc->base); + if (ret) + goto err; + + return 0; + +err: + atmel_hsmc_nand_controller_remove(&nc->base); + + return ret; +} + +static const struct atmel_nand_controller_ops atmel_hsmc_nc_ops = { + .probe = atmel_hsmc_nand_controller_probe, + .ecc_init = atmel_hsmc_nand_ecc_init, + .nand_init = atmel_nand_init, + .setup_interface = atmel_hsmc_nand_setup_interface, + .exec_op = atmel_hsmc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_sama5_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ops = &atmel_hsmc_nc_ops, +}; + +static int atmel_smc_nand_controller_probe(struct device *dev, + const struct atmel_nand_controller_caps *caps) +{ + struct atmel_smc_nand_controller *nc; + int ret; + + nc = kzalloc(sizeof(*nc), GFP_KERNEL); + if (!nc) + return -ENOMEM; + + ret = atmel_nand_controller_init(&nc->base, dev, caps); + if (ret) + return ret; + + ret = atmel_smc_nand_controller_init(nc); + if (ret) + return ret; + + return atmel_nand_controller_add_nands(&nc->base); +} + +/* + * The SMC reg layout of at91rm9200 is completely different which prevents us + * from re-using atmel_smc_nand_setup_interface() for the + * ->setup_interface() hook. + * At this point, there's no support for the at91rm9200 SMC IP, so we leave + * ->setup_interface() unassigned. + */ +static const struct atmel_nand_controller_ops at91rm9200_nc_ops = { + .probe = atmel_smc_nand_controller_probe, + .ecc_init = atmel_nand_ecc_init, + .nand_init = atmel_smc_nand_init, + .exec_op = atmel_smc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_rm9200_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &at91rm9200_nc_ops, +}; + +static const struct atmel_nand_controller_ops atmel_smc_nc_ops = { + .probe = atmel_smc_nand_controller_probe, + .ecc_init = atmel_nand_ecc_init, + .nand_init = atmel_smc_nand_init, + .setup_interface = atmel_smc_nand_setup_interface, + .exec_op = atmel_smc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_sam9260_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps atmel_sam9261_nc_caps = { + .ale_offs = BIT(22), + .cle_offs = BIT(21), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps atmel_sam9g45_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps microchip_sam9x60_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "microchip,sfr", + .ops = &atmel_smc_nc_ops, +}; + +static const struct of_device_id atmel_nand_controller_of_ids[] = { + { + .compatible = "atmel,at91rm9200-nand-controller", + .data = &atmel_rm9200_nc_caps, + }, + { + .compatible = "atmel,at91sam9260-nand-controller", + .data = &atmel_sam9260_nc_caps, + }, + { + .compatible = "atmel,at91sam9261-nand-controller", + .data = &atmel_sam9261_nc_caps, + }, + { + .compatible = "atmel,at91sam9g45-nand-controller", + .data = &atmel_sam9g45_nc_caps, + }, + { + .compatible = "atmel,sama5d3-nand-controller", + .data = &atmel_sama5_nc_caps, + }, + { + .compatible = "microchip,sam9x60-nand-controller", + .data = µchip_sam9x60_nc_caps, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atmel_nand_controller_of_ids); + +static int atmel_nand_controller_probe(struct device *dev) +{ + const struct atmel_nand_controller_caps *caps; + + if (dev->id_entry) + caps = (void *)dev->id_entry->driver_data; + else + caps = of_device_get_match_data(dev); + + if (!caps) { + dev_err(dev, "Could not retrieve NFC caps\n"); + return -EINVAL; + } + + return caps->ops->probe(dev, caps); +} + +static struct driver atmel_nand_controller_driver = { + .name = "atmel-nand-controller", + .of_match_table = atmel_nand_controller_of_ids, + .probe = atmel_nand_controller_probe, +}; +device_platform_driver(atmel_nand_controller_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("NAND Flash Controller driver for Atmel SoCs"); +MODULE_ALIAS("platform:atmel-nand-controller"); diff --git a/drivers/mtd/nand/atmel/pmecc.c b/drivers/mtd/nand/atmel/pmecc.c new file mode 100644 index 0000000000..1b89607a33 --- /dev/null +++ b/drivers/mtd/nand/atmel/pmecc.c @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 ATMEL + * Copyright 2017 Free Electrons + * + * Author: Boris Brezillon <boris.brezillon@free-electrons.com> + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright 2000 Steven J. Hill (sjhill@cotw.com) + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + * + * The PMECC is an hardware assisted BCH engine, which means part of the + * ECC algorithm is left to the software. The hardware/software repartition + * is explained in the "PMECC Controller Functional Description" chapter in + * Atmel datasheets, and some of the functions in this file are directly + * implementing the algorithms described in the "Software Implementation" + * sub-section. + * + * TODO: it seems that the software BCH implementation in lib/bch.c is already + * providing some of the logic we are implementing here. It would be smart + * to expose the needed lib/bch.c helpers/functions and re-use them here. + */ + +#include <linux/iopoll.h> +#include <module.h> +#include <linux/mtd/rawnand.h> +#include <of.h> +#include <of_device.h> +#include <linux/slab.h> + +#include "pmecc.h" + +/* Galois field dimension */ +#define PMECC_GF_DIMENSION_13 13 +#define PMECC_GF_DIMENSION_14 14 + +/* Primitive Polynomial used by PMECC */ +#define PMECC_GF_13_PRIMITIVE_POLY 0x201b +#define PMECC_GF_14_PRIMITIVE_POLY 0x4443 + +#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000 +#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000 + +/* Time out value for reading PMECC status register */ +#define PMECC_MAX_TIMEOUT_MS 100 + +/* PMECC Register Definitions */ +#define ATMEL_PMECC_CFG 0x0 +#define PMECC_CFG_BCH_STRENGTH(x) (x) +#define PMECC_CFG_BCH_STRENGTH_MASK GENMASK(2, 0) +#define PMECC_CFG_SECTOR512 (0 << 4) +#define PMECC_CFG_SECTOR1024 (1 << 4) +#define PMECC_CFG_NSECTORS(x) ((fls(x) - 1) << 8) +#define PMECC_CFG_READ_OP (0 << 12) +#define PMECC_CFG_WRITE_OP (1 << 12) +#define PMECC_CFG_SPARE_ENABLE BIT(16) +#define PMECC_CFG_AUTO_ENABLE BIT(20) + +#define ATMEL_PMECC_SAREA 0x4 +#define ATMEL_PMECC_SADDR 0x8 +#define ATMEL_PMECC_EADDR 0xc + +#define ATMEL_PMECC_CLK 0x10 +#define PMECC_CLK_133MHZ (2 << 0) + +#define ATMEL_PMECC_CTRL 0x14 +#define PMECC_CTRL_RST BIT(0) +#define PMECC_CTRL_DATA BIT(1) +#define PMECC_CTRL_USER BIT(2) +#define PMECC_CTRL_ENABLE BIT(4) +#define PMECC_CTRL_DISABLE BIT(5) + +#define ATMEL_PMECC_SR 0x18 +#define PMECC_SR_BUSY BIT(0) +#define PMECC_SR_ENABLE BIT(4) + +#define ATMEL_PMECC_IER 0x1c +#define ATMEL_PMECC_IDR 0x20 +#define ATMEL_PMECC_IMR 0x24 +#define ATMEL_PMECC_ISR 0x28 +#define PMECC_ERROR_INT BIT(0) + +#define ATMEL_PMECC_ECC(sector, n) \ + ((((sector) + 1) * 0x40) + (n)) + +#define ATMEL_PMECC_REM(sector, n) \ + ((((sector) + 1) * 0x40) + ((n) * 4) + 0x200) + +/* PMERRLOC Register Definitions */ +#define ATMEL_PMERRLOC_ELCFG 0x0 +#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0) +#define PMERRLOC_ELCFG_SECTOR_1024 (1 << 0) +#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16) + +#define ATMEL_PMERRLOC_ELPRIM 0x4 +#define ATMEL_PMERRLOC_ELEN 0x8 +#define ATMEL_PMERRLOC_ELDIS 0xc +#define PMERRLOC_DISABLE BIT(0) + +#define ATMEL_PMERRLOC_ELSR 0x10 +#define PMERRLOC_ELSR_BUSY BIT(0) + +#define ATMEL_PMERRLOC_ELIER 0x14 +#define ATMEL_PMERRLOC_ELIDR 0x18 +#define ATMEL_PMERRLOC_ELIMR 0x1c +#define ATMEL_PMERRLOC_ELISR 0x20 +#define PMERRLOC_ERR_NUM_MASK GENMASK(12, 8) +#define PMERRLOC_CALC_DONE BIT(0) + +#define ATMEL_PMERRLOC_SIGMA(x) (((x) * 0x4) + 0x28) + +#define ATMEL_PMERRLOC_EL(offs, x) (((x) * 0x4) + (offs)) + +struct atmel_pmecc_gf_tables { + u16 *alpha_to; + u16 *index_of; +}; + +struct atmel_pmecc_caps { + const int *strengths; + int nstrengths; + int el_offset; + bool correct_erased_chunks; +}; + +struct atmel_pmecc { + struct device *dev; + const struct atmel_pmecc_caps *caps; + + struct { + void __iomem *base; + void __iomem *errloc; + } regs; + + struct mutex lock; +}; + +struct atmel_pmecc_user_conf_cache { + u32 cfg; + u32 sarea; + u32 saddr; + u32 eaddr; +}; + +struct atmel_pmecc_user { + struct atmel_pmecc_user_conf_cache cache; + struct atmel_pmecc *pmecc; + const struct atmel_pmecc_gf_tables *gf_tables; + int eccbytes; + s16 *partial_syn; + s16 *si; + s16 *lmu; + s16 *smu; + s32 *mu; + s32 *dmu; + s32 *delta; + u32 isr; +}; + +static DEFINE_MUTEX(pmecc_gf_tables_lock); +static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_512; +static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_1024; + +static inline int deg(unsigned int poly) +{ + /* polynomial degree is the most-significant bit index */ + return fls(poly) - 1; +} + +static int atmel_pmecc_build_gf_tables(int mm, unsigned int poly, + struct atmel_pmecc_gf_tables *gf_tables) +{ + unsigned int i, x = 1; + const unsigned int k = BIT(deg(poly)); + unsigned int nn = BIT(mm) - 1; + + /* primitive polynomial must be of degree m */ + if (k != (1u << mm)) + return -EINVAL; + + for (i = 0; i < nn; i++) { + gf_tables->alpha_to[i] = x; + gf_tables->index_of[x] = i; + if (i && (x == 1)) + /* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */ + return -EINVAL; + x <<= 1; + if (x & k) + x ^= poly; + } + gf_tables->alpha_to[nn] = 1; + gf_tables->index_of[0] = 0; + + return 0; +} + +static const struct atmel_pmecc_gf_tables * +atmel_pmecc_create_gf_tables(const struct atmel_pmecc_user_req *req) +{ + struct atmel_pmecc_gf_tables *gf_tables; + unsigned int poly, degree, table_size; + int ret; + + if (req->ecc.sectorsize == 512) { + degree = PMECC_GF_DIMENSION_13; + poly = PMECC_GF_13_PRIMITIVE_POLY; + table_size = PMECC_LOOKUP_TABLE_SIZE_512; + } else { + degree = PMECC_GF_DIMENSION_14; + poly = PMECC_GF_14_PRIMITIVE_POLY; + table_size = PMECC_LOOKUP_TABLE_SIZE_1024; + } + + gf_tables = kzalloc(sizeof(*gf_tables) + + (2 * table_size * sizeof(u16)), + GFP_KERNEL); + if (!gf_tables) + return ERR_PTR(-ENOMEM); + + gf_tables->alpha_to = (void *)(gf_tables + 1); + gf_tables->index_of = gf_tables->alpha_to + table_size; + + ret = atmel_pmecc_build_gf_tables(degree, poly, gf_tables); + if (ret) { + kfree(gf_tables); + return ERR_PTR(ret); + } + + return gf_tables; +} + +static const struct atmel_pmecc_gf_tables * +atmel_pmecc_get_gf_tables(const struct atmel_pmecc_user_req *req) +{ + const struct atmel_pmecc_gf_tables **gf_tables, *ret; + + mutex_lock(&pmecc_gf_tables_lock); + if (req->ecc.sectorsize == 512) + gf_tables = &pmecc_gf_tables_512; + else + gf_tables = &pmecc_gf_tables_1024; + + ret = *gf_tables; + + if (!ret) { + ret = atmel_pmecc_create_gf_tables(req); + if (!IS_ERR(ret)) + *gf_tables = ret; + } + mutex_unlock(&pmecc_gf_tables_lock); + + return ret; +} + +static int atmel_pmecc_prepare_user_req(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req) +{ + int i, max_eccbytes, eccbytes = 0, eccstrength = 0; + + if (req->pagesize <= 0 || req->oobsize <= 0 || req->ecc.bytes <= 0) + return -EINVAL; + + if (req->ecc.ooboffset >= 0 && + req->ecc.ooboffset + req->ecc.bytes > req->oobsize) + return -EINVAL; + + if (req->ecc.sectorsize == ATMEL_PMECC_SECTOR_SIZE_AUTO) { + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH) + return -EINVAL; + + if (req->pagesize > 512) + req->ecc.sectorsize = 1024; + else + req->ecc.sectorsize = 512; + } + + if (req->ecc.sectorsize != 512 && req->ecc.sectorsize != 1024) + return -EINVAL; + + if (req->pagesize % req->ecc.sectorsize) + return -EINVAL; + + req->ecc.nsectors = req->pagesize / req->ecc.sectorsize; + + max_eccbytes = req->ecc.bytes; + + for (i = 0; i < pmecc->caps->nstrengths; i++) { + int nbytes, strength = pmecc->caps->strengths[i]; + + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH && + strength < req->ecc.strength) + continue; + + nbytes = DIV_ROUND_UP(strength * fls(8 * req->ecc.sectorsize), + 8); + nbytes *= req->ecc.nsectors; + + if (nbytes > max_eccbytes) + break; + + eccstrength = strength; + eccbytes = nbytes; + + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH) + break; + } + + if (!eccstrength) + return -EINVAL; + + req->ecc.bytes = eccbytes; + req->ecc.strength = eccstrength; + + if (req->ecc.ooboffset < 0) + req->ecc.ooboffset = req->oobsize - eccbytes; + + return 0; +} + +struct atmel_pmecc_user * +atmel_pmecc_create_user(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req) +{ + struct atmel_pmecc_user *user; + const struct atmel_pmecc_gf_tables *gf_tables; + int strength, size, ret; + + ret = atmel_pmecc_prepare_user_req(pmecc, req); + if (ret) + return ERR_PTR(ret); + + size = sizeof(*user); + size = ALIGN(size, sizeof(u16)); + /* Reserve space for partial_syn, si and smu */ + size += ((2 * req->ecc.strength) + 1) * sizeof(u16) * + (2 + req->ecc.strength + 2); + /* Reserve space for lmu. */ + size += (req->ecc.strength + 1) * sizeof(u16); + /* Reserve space for mu, dmu and delta. */ + size = ALIGN(size, sizeof(s32)); + size += (req->ecc.strength + 1) * sizeof(s32) * 3; + + user = kzalloc(size, GFP_KERNEL); + if (!user) + return ERR_PTR(-ENOMEM); + + user->pmecc = pmecc; + + user->partial_syn = (s16 *)PTR_ALIGN(user + 1, sizeof(u16)); + user->si = user->partial_syn + ((2 * req->ecc.strength) + 1); + user->lmu = user->si + ((2 * req->ecc.strength) + 1); + user->smu = user->lmu + (req->ecc.strength + 1); + user->mu = (s32 *)PTR_ALIGN(user->smu + + (((2 * req->ecc.strength) + 1) * + (req->ecc.strength + 2)), + sizeof(s32)); + user->dmu = user->mu + req->ecc.strength + 1; + user->delta = user->dmu + req->ecc.strength + 1; + + gf_tables = atmel_pmecc_get_gf_tables(req); + if (IS_ERR(gf_tables)) { + kfree(user); + return ERR_CAST(gf_tables); + } + + user->gf_tables = gf_tables; + + user->eccbytes = req->ecc.bytes / req->ecc.nsectors; + + for (strength = 0; strength < pmecc->caps->nstrengths; strength++) { + if (pmecc->caps->strengths[strength] == req->ecc.strength) + break; + } + + user->cache.cfg = PMECC_CFG_BCH_STRENGTH(strength) | + PMECC_CFG_NSECTORS(req->ecc.nsectors); + + if (req->ecc.sectorsize == 1024) + user->cache.cfg |= PMECC_CFG_SECTOR1024; + + user->cache.sarea = req->oobsize - 1; + user->cache.saddr = req->ecc.ooboffset; + user->cache.eaddr = req->ecc.ooboffset + req->ecc.bytes - 1; + + return user; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_create_user); + +void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user) +{ + kfree(user); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_destroy_user); + +static int get_strength(struct atmel_pmecc_user *user) +{ + const int *strengths = user->pmecc->caps->strengths; + + return strengths[user->cache.cfg & PMECC_CFG_BCH_STRENGTH_MASK]; +} + +static int get_sectorsize(struct atmel_pmecc_user *user) +{ + return user->cache.cfg & PMECC_CFG_SECTOR1024 ? 1024 : 512; +} + +static void atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector) +{ + int strength = get_strength(user); + u32 value; + int i; + + /* Fill odd syndromes */ + for (i = 0; i < strength; i++) { + value = readl_relaxed(user->pmecc->regs.base + + ATMEL_PMECC_REM(sector, i / 2)); + if (i & 1) + value >>= 16; + + user->partial_syn[(2 * i) + 1] = value; + } +} + +static void atmel_pmecc_substitute(struct atmel_pmecc_user *user) +{ + int degree = get_sectorsize(user) == 512 ? 13 : 14; + int cw_len = BIT(degree) - 1; + int strength = get_strength(user); + s16 *alpha_to = user->gf_tables->alpha_to; + s16 *index_of = user->gf_tables->index_of; + s16 *partial_syn = user->partial_syn; + s16 *si; + int i, j; + + /* + * si[] is a table that holds the current syndrome value, + * an element of that table belongs to the field + */ + si = user->si; + + memset(&si[1], 0, sizeof(s16) * ((2 * strength) - 1)); + + /* Computation 2t syndromes based on S(x) */ + /* Odd syndromes */ + for (i = 1; i < 2 * strength; i += 2) { + for (j = 0; j < degree; j++) { + if (partial_syn[i] & BIT(j)) + si[i] = alpha_to[i * j] ^ si[i]; + } + } + /* Even syndrome = (Odd syndrome) ** 2 */ + for (i = 2, j = 1; j <= strength; i = ++j << 1) { + if (si[j] == 0) { + si[i] = 0; + } else { + s16 tmp; + + tmp = index_of[si[j]]; + tmp = (tmp * 2) % cw_len; + si[i] = alpha_to[tmp]; + } + } +} + +static void atmel_pmecc_get_sigma(struct atmel_pmecc_user *user) +{ + s16 *lmu = user->lmu; + s16 *si = user->si; + s32 *mu = user->mu; + s32 *dmu = user->dmu; + s32 *delta = user->delta; + int degree = get_sectorsize(user) == 512 ? 13 : 14; + int cw_len = BIT(degree) - 1; + int strength = get_strength(user); + int num = 2 * strength + 1; + s16 *index_of = user->gf_tables->index_of; + s16 *alpha_to = user->gf_tables->alpha_to; + int i, j, k; + u32 dmu_0_count, tmp; + s16 *smu = user->smu; + + /* index of largest delta */ + int ro; + int largest; + int diff; + + dmu_0_count = 0; + + /* First Row */ + + /* Mu */ + mu[0] = -1; + + memset(smu, 0, sizeof(s16) * num); + smu[0] = 1; + + /* discrepancy set to 1 */ + dmu[0] = 1; + /* polynom order set to 0 */ + lmu[0] = 0; + delta[0] = (mu[0] * 2 - lmu[0]) >> 1; + + /* Second Row */ + + /* Mu */ + mu[1] = 0; + /* Sigma(x) set to 1 */ + memset(&smu[num], 0, sizeof(s16) * num); + smu[num] = 1; + + /* discrepancy set to S1 */ + dmu[1] = si[1]; + + /* polynom order set to 0 */ + lmu[1] = 0; + + delta[1] = (mu[1] * 2 - lmu[1]) >> 1; + + /* Init the Sigma(x) last row */ + memset(&smu[(strength + 1) * num], 0, sizeof(s16) * num); + + for (i = 1; i <= strength; i++) { + mu[i + 1] = i << 1; + /* Begin Computing Sigma (Mu+1) and L(mu) */ + /* check if discrepancy is set to 0 */ + if (dmu[i] == 0) { + dmu_0_count++; + + tmp = ((strength - (lmu[i] >> 1) - 1) / 2); + if ((strength - (lmu[i] >> 1) - 1) & 0x1) + tmp += 2; + else + tmp += 1; + + if (dmu_0_count == tmp) { + for (j = 0; j <= (lmu[i] >> 1) + 1; j++) + smu[(strength + 1) * num + j] = + smu[i * num + j]; + + lmu[strength + 1] = lmu[i]; + return; + } + + /* copy polynom */ + for (j = 0; j <= lmu[i] >> 1; j++) + smu[(i + 1) * num + j] = smu[i * num + j]; + + /* copy previous polynom order to the next */ + lmu[i + 1] = lmu[i]; + } else { + ro = 0; + largest = -1; + /* find largest delta with dmu != 0 */ + for (j = 0; j < i; j++) { + if ((dmu[j]) && (delta[j] > largest)) { + largest = delta[j]; + ro = j; + } + } + + /* compute difference */ + diff = (mu[i] - mu[ro]); + + /* Compute degree of the new smu polynomial */ + if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff)) + lmu[i + 1] = lmu[i]; + else + lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2; + + /* Init smu[i+1] with 0 */ + for (k = 0; k < num; k++) + smu[(i + 1) * num + k] = 0; + + /* Compute smu[i+1] */ + for (k = 0; k <= lmu[ro] >> 1; k++) { + s16 a, b, c; + + if (!(smu[ro * num + k] && dmu[i])) + continue; + + a = index_of[dmu[i]]; + b = index_of[dmu[ro]]; + c = index_of[smu[ro * num + k]]; + tmp = a + (cw_len - b) + c; + a = alpha_to[tmp % cw_len]; + smu[(i + 1) * num + (k + diff)] = a; + } + + for (k = 0; k <= lmu[i] >> 1; k++) + smu[(i + 1) * num + k] ^= smu[i * num + k]; + } + + /* End Computing Sigma (Mu+1) and L(mu) */ + /* In either case compute delta */ + delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1; + + /* Do not compute discrepancy for the last iteration */ + if (i >= strength) + continue; + + for (k = 0; k <= (lmu[i + 1] >> 1); k++) { + tmp = 2 * (i - 1); + if (k == 0) { + dmu[i + 1] = si[tmp + 3]; + } else if (smu[(i + 1) * num + k] && si[tmp + 3 - k]) { + s16 a, b, c; + + a = index_of[smu[(i + 1) * num + k]]; + b = si[2 * (i - 1) + 3 - k]; + c = index_of[b]; + tmp = a + c; + tmp %= cw_len; + dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1]; + } + } + } +} + +static int atmel_pmecc_err_location(struct atmel_pmecc_user *user) +{ + int sector_size = get_sectorsize(user); + int degree = sector_size == 512 ? 13 : 14; + struct atmel_pmecc *pmecc = user->pmecc; + int strength = get_strength(user); + int ret, roots_nbr, i, err_nbr = 0; + int num = (2 * strength) + 1; + s16 *smu = user->smu; + u32 val; + + writel(PMERRLOC_DISABLE, pmecc->regs.errloc + ATMEL_PMERRLOC_ELDIS); + + for (i = 0; i <= user->lmu[strength + 1] >> 1; i++) { + writel_relaxed(smu[(strength + 1) * num + i], + pmecc->regs.errloc + ATMEL_PMERRLOC_SIGMA(i)); + err_nbr++; + } + + val = (err_nbr - 1) << 16; + if (sector_size == 1024) + val |= 1; + + writel(val, pmecc->regs.errloc + ATMEL_PMERRLOC_ELCFG); + writel((sector_size * 8) + (degree * strength), + pmecc->regs.errloc + ATMEL_PMERRLOC_ELEN); + + ret = readl_relaxed_poll_timeout(pmecc->regs.errloc + + ATMEL_PMERRLOC_ELISR, + val, val & PMERRLOC_CALC_DONE, + PMECC_MAX_TIMEOUT_MS * 1000); + if (ret) { + dev_err(pmecc->dev, + "PMECC: Timeout to calculate error location.\n"); + return ret; + } + + roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8; + /* Number of roots == degree of smu hence <= cap */ + if (roots_nbr == user->lmu[strength + 1] >> 1) + return err_nbr - 1; + + /* + * Number of roots does not match the degree of smu + * unable to correct error. + */ + return -EBADMSG; +} + +int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector, + void *data, void *ecc) +{ + struct atmel_pmecc *pmecc = user->pmecc; + int sectorsize = get_sectorsize(user); + int eccbytes = user->eccbytes; + int i, nerrors; + + if (!(user->isr & BIT(sector))) + return 0; + + atmel_pmecc_gen_syndrome(user, sector); + atmel_pmecc_substitute(user); + atmel_pmecc_get_sigma(user); + + nerrors = atmel_pmecc_err_location(user); + if (nerrors < 0) + return nerrors; + + for (i = 0; i < nerrors; i++) { + const char *area; + int byte, bit; + u32 errpos; + u8 *ptr; + + errpos = readl_relaxed(pmecc->regs.errloc + + ATMEL_PMERRLOC_EL(pmecc->caps->el_offset, i)); + errpos--; + + byte = errpos / 8; + bit = errpos % 8; + + if (byte < sectorsize) { + ptr = data + byte; + area = "data"; + } else if (byte < sectorsize + eccbytes) { + ptr = ecc + byte - sectorsize; + area = "ECC"; + } else { + dev_dbg(pmecc->dev, + "Invalid errpos value (%d, max is %d)\n", + errpos, (sectorsize + eccbytes) * 8); + return -EINVAL; + } + + dev_dbg(pmecc->dev, + "Bit flip in %s area, byte %d: 0x%02x -> 0x%02x\n", + area, byte, *ptr, (unsigned int)(*ptr ^ BIT(bit))); + + *ptr ^= BIT(bit); + } + + return nerrors; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_correct_sector); + +bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user) +{ + return user->pmecc->caps->correct_erased_chunks; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_correct_erased_chunks); + +void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user, + int sector, void *ecc) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u8 *ptr = ecc; + int i; + + for (i = 0; i < user->eccbytes; i++) + ptr[i] = readb_relaxed(pmecc->regs.base + + ATMEL_PMECC_ECC(sector, i)); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_get_generated_eccbytes); + +void atmel_pmecc_reset(struct atmel_pmecc *pmecc) +{ + writel(PMECC_CTRL_RST, pmecc->regs.base + ATMEL_PMECC_CTRL); + writel(PMECC_CTRL_DISABLE, pmecc->regs.base + ATMEL_PMECC_CTRL); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_reset); + +int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u32 cfg; + + if (op != NAND_ECC_READ && op != NAND_ECC_WRITE) { + dev_err(pmecc->dev, "Bad ECC operation!"); + return -EINVAL; + } + + mutex_lock(&user->pmecc->lock); + + cfg = user->cache.cfg; + if (op == NAND_ECC_WRITE) + cfg |= PMECC_CFG_WRITE_OP; + else + cfg |= PMECC_CFG_AUTO_ENABLE; + + writel(cfg, pmecc->regs.base + ATMEL_PMECC_CFG); + writel(user->cache.sarea, pmecc->regs.base + ATMEL_PMECC_SAREA); + writel(user->cache.saddr, pmecc->regs.base + ATMEL_PMECC_SADDR); + writel(user->cache.eaddr, pmecc->regs.base + ATMEL_PMECC_EADDR); + + writel(PMECC_CTRL_ENABLE, pmecc->regs.base + ATMEL_PMECC_CTRL); + writel(PMECC_CTRL_DATA, pmecc->regs.base + ATMEL_PMECC_CTRL); + + return 0; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_enable); + +void atmel_pmecc_disable(struct atmel_pmecc_user *user) +{ + atmel_pmecc_reset(user->pmecc); + mutex_unlock(&user->pmecc->lock); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_disable); + +int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u32 status; + int ret; + + ret = readl_relaxed_poll_timeout(pmecc->regs.base + + ATMEL_PMECC_SR, + status, !(status & PMECC_SR_BUSY), + PMECC_MAX_TIMEOUT_MS * 1000); + if (ret) { + dev_err(pmecc->dev, + "Timeout while waiting for PMECC ready.\n"); + return ret; + } + + user->isr = readl_relaxed(pmecc->regs.base + ATMEL_PMECC_ISR); + + return 0; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_wait_rdy); + +static struct atmel_pmecc *atmel_pmecc_create(struct device *dev, + const struct atmel_pmecc_caps *caps, + int pmecc_res_idx, int errloc_res_idx) +{ + struct atmel_pmecc *pmecc; + + pmecc = kzalloc(sizeof(*pmecc), GFP_KERNEL); + if (!pmecc) + return ERR_PTR(-ENOMEM); + + pmecc->caps = caps; + pmecc->dev = dev; + mutex_init(&pmecc->lock); + + pmecc->regs.base = dev_request_mem_region_err_null(dev, pmecc_res_idx); + if (!pmecc->regs.base) + return ERR_PTR(-EINVAL); + + pmecc->regs.errloc = dev_request_mem_region_err_null(dev, errloc_res_idx); + if (!pmecc->regs.errloc) + return ERR_PTR(-EINVAL); + + /* Disable all interrupts before registering the PMECC handler. */ + writel(0xffffffff, pmecc->regs.base + ATMEL_PMECC_IDR); + atmel_pmecc_reset(pmecc); + + return pmecc; +} + +static struct atmel_pmecc *atmel_pmecc_get_by_node(struct device *userdev, + struct device_node *np) +{ + struct device *dev; + struct atmel_pmecc *pmecc; + int ret; + + dev = of_find_device_by_node(np); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + pmecc = dev->priv; + if (!pmecc) { + ret = -EPROBE_DEFER; + goto err_put_device; + } + + return pmecc; + +err_put_device: + put_device(dev); + return ERR_PTR(ret); +} + +static const int atmel_pmecc_strengths[] = { 2, 4, 8, 12, 24, 32 }; + +static struct atmel_pmecc_caps at91sam9g45_caps = { + .strengths = atmel_pmecc_strengths, + .nstrengths = 5, + .el_offset = 0x8c, +}; + +static struct atmel_pmecc_caps sama5d4_caps = { + .strengths = atmel_pmecc_strengths, + .nstrengths = 5, + .el_offset = 0x8c, + .correct_erased_chunks = true, +}; + +static struct atmel_pmecc_caps sama5d2_caps = { + .strengths = atmel_pmecc_strengths, + .nstrengths = 6, + .el_offset = 0xac, + .correct_erased_chunks = true, +}; + +static const struct of_device_id __maybe_unused atmel_pmecc_legacy_match[] = { + { .compatible = "atmel,sama5d4-nand", &sama5d4_caps }, + { .compatible = "atmel,sama5d2-nand", &sama5d2_caps }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_pmecc_legacy_match); + +struct atmel_pmecc *dev_atmel_pmecc_get(struct device *userdev) +{ + struct atmel_pmecc *pmecc; + struct device_node *np; + + if (!userdev) + return ERR_PTR(-EINVAL); + + if (!userdev->of_node) + return NULL; + + np = of_parse_phandle(userdev->of_node, "ecc-engine", 0); + if (np) { + pmecc = atmel_pmecc_get_by_node(userdev, np); + of_node_put(np); + } else { + /* + * Support old DT bindings: in this case the PMECC iomem + * resources are directly defined in the user dev at position + * 1 and 2. Extract all relevant information from there. + */ + struct device *dev = userdev; + const struct atmel_pmecc_caps *caps; + const struct of_device_id *match; + + /* No PMECC engine available. */ + if (!of_property_read_bool(userdev->of_node, + "atmel,has-pmecc")) + return NULL; + + caps = &at91sam9g45_caps; + + /* Find the caps associated to the NAND dev node. */ + match = of_match_node(atmel_pmecc_legacy_match, + userdev->of_node); + if (match && match->data) + caps = match->data; + + pmecc = atmel_pmecc_create(dev, caps, 1, 2); + } + + return pmecc; +} +EXPORT_SYMBOL(dev_atmel_pmecc_get); + +static const struct of_device_id atmel_pmecc_match[] = { + { .compatible = "atmel,at91sam9g45-pmecc", &at91sam9g45_caps }, + { .compatible = "atmel,sama5d4-pmecc", &sama5d4_caps }, + { .compatible = "atmel,sama5d2-pmecc", &sama5d2_caps }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_pmecc_match); + +static int atmel_pmecc_probe(struct device *dev) +{ + const struct atmel_pmecc_caps *caps; + struct atmel_pmecc *pmecc; + + caps = of_device_get_match_data(dev); + if (!caps) { + dev_err(dev, "Invalid caps\n"); + return -EINVAL; + } + + pmecc = atmel_pmecc_create(dev, caps, 0, 1); + if (IS_ERR(pmecc)) + return PTR_ERR(pmecc); + + dev->priv = pmecc; + + return 0; +} + +static struct driver atmel_pmecc_driver = { + .name = "atmel-pmecc", + .of_match_table = atmel_pmecc_match, + .probe = atmel_pmecc_probe, +}; +device_platform_driver(atmel_pmecc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("PMECC engine driver"); +MODULE_ALIAS("platform:atmel_pmecc"); diff --git a/drivers/mtd/nand/atmel/pmecc.h b/drivers/mtd/nand/atmel/pmecc.h new file mode 100644 index 0000000000..6178a35e9d --- /dev/null +++ b/drivers/mtd/nand/atmel/pmecc.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * © Copyright 2016 ATMEL + * © Copyright 2016 Free Electrons + * + * Author: Boris Brezillon <boris.brezillon@free-electrons.com> + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright © 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright © 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright © 2000 Steven J. Hill (sjhill@cotw.com) + * + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright © 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * © Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * © Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * © Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + */ + +#ifndef ATMEL_PMECC_H +#define ATMEL_PMECC_H + +#define ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH 0 +#define ATMEL_PMECC_SECTOR_SIZE_AUTO 0 +#define ATMEL_PMECC_OOBOFFSET_AUTO -1 + +struct atmel_pmecc_user_req { + int pagesize; + int oobsize; + struct { + int strength; + int bytes; + int sectorsize; + int nsectors; + int ooboffset; + } ecc; +}; + +struct atmel_pmecc *dev_atmel_pmecc_get(struct device *dev); + +struct atmel_pmecc_user * +atmel_pmecc_create_user(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req); +void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user); + +void atmel_pmecc_reset(struct atmel_pmecc *pmecc); +int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op); +void atmel_pmecc_disable(struct atmel_pmecc_user *user); +int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user); +int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector, + void *data, void *ecc); +bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user); +void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user, + int sector, void *ecc); + +#endif /* ATMEL_PMECC_H */ diff --git a/drivers/mtd/nand/bbt.c b/drivers/mtd/nand/bbt.c index 8a78f9046f..ad83f2fad3 100644 --- a/drivers/mtd/nand/bbt.c +++ b/drivers/mtd/nand/bbt.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2017 Free Electrons * diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c index ba22662e2f..52036dd857 100644 --- a/drivers/mtd/nand/core.c +++ b/drivers/mtd/nand/core.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2017 Free Electrons * diff --git a/drivers/mtd/nand/denali.h b/drivers/mtd/nand/denali.h index f9c209d58d..ed489d010b 100644 --- a/drivers/mtd/nand/denali.h +++ b/drivers/mtd/nand/denali.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * NAND Flash Controller Device Driver * Copyright (c) 2009 - 2010, Intel Corporation and its suppliers. @@ -356,7 +356,7 @@ struct denali_chip { */ struct denali_controller { struct nand_controller controller; - struct device_d *dev; + struct device *dev; struct list_head chips; unsigned long clk_rate; unsigned long clk_x_rate; diff --git a/drivers/mtd/nand/internals.h b/drivers/mtd/nand/internals.h index 7716470a56..e6b2282f2c 100644 --- a/drivers/mtd/nand/internals.h +++ b/drivers/mtd/nand/internals.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2018 - Bootlin * diff --git a/drivers/mtd/nand/nand-bb.c b/drivers/mtd/nand/nand-bb.c index c0104c5936..56033022ff 100644 --- a/drivers/mtd/nand/nand-bb.c +++ b/drivers/mtd/nand/nand-bb.c @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * - * 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 <common.h> #include <command.h> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 4c90ad9757..2599e8c8c2 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -36,6 +36,7 @@ #include <asm/byteorder.h> #include <io.h> #include <malloc.h> +#include <linux/gpio/consumer.h> #include <module.h> #include <of_mtd.h> @@ -91,11 +92,16 @@ static int nand_ooblayout_free_sp(struct mtd_info *mtd, int section, return 0; } -const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = { +static const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = { .ecc = nand_ooblayout_ecc_sp, .free = nand_ooblayout_free_sp, }; -EXPORT_SYMBOL_GPL(nand_ooblayout_sp_ops); + +const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void) +{ + return &nand_ooblayout_sp_ops; +} +EXPORT_SYMBOL_GPL(nand_get_small_page_ooblayout); static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion) @@ -127,11 +133,16 @@ static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section, return 0; } -const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = { +static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = { .ecc = nand_ooblayout_ecc_lp, .free = nand_ooblayout_free_lp, }; -EXPORT_SYMBOL_GPL(nand_ooblayout_lp_ops); + +const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void) +{ + return &nand_ooblayout_lp_ops; +} +EXPORT_SYMBOL_GPL(nand_get_large_page_ooblayout); /* * Support the old "large page" layout used for 1-bit Hamming ECC where ECC @@ -785,6 +796,27 @@ int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms) }; EXPORT_SYMBOL_GPL(nand_soft_waitrdy); +/** + * nand_gpio_waitrdy - Poll R/B GPIO pin until ready + * @chip: NAND chip structure + * @gpiod: GPIO descriptor of R/B pin + * @timeout_ms: Timeout in ms + * + * Poll the R/B GPIO pin until it becomes ready. If that does not happen + * whitin the specified timeout, -ETIMEDOUT is returned. + * + * This helper is intended to be used when the controller has access to the + * NAND R/B pin over GPIO. + * + * Return 0 if the R/B pin indicates chip is ready, a negative error otherwise. + */ +int nand_gpio_waitrdy(struct nand_chip *chip, struct gpio_desc *gpio, + unsigned long timeout_ms) +{ + return gpiod_poll_timeout_us(gpio, true, timeout_ms * USEC_PER_MSEC); +}; +EXPORT_SYMBOL_GPL(nand_gpio_waitrdy); + static bool nand_supports_get_features(struct nand_chip *chip, int addr) { return (chip->parameters.supports_set_get_features && @@ -4651,8 +4683,8 @@ static bool find_full_id_nand(struct nand_chip *chip, memorg->pagesize * memorg->pages_per_eraseblock); chip->options |= type->options; - chip->base.eccreq.strength = NAND_ECC_STRENGTH(type); - chip->base.eccreq.step_size = NAND_ECC_STEP(type); + chip->base.ecc.requirements.strength = NAND_ECC_STRENGTH(type); + chip->base.ecc.requirements.step_size = NAND_ECC_STEP(type); chip->parameters.model = strdup(type->name); if (!chip->parameters.model) @@ -4914,9 +4946,9 @@ free_detect_allocation: } static const char * const nand_ecc_algos[] = { - [NAND_ECC_HAMMING] = "hamming", - [NAND_ECC_BCH] = "bch", - [NAND_ECC_RS] = "rs", + [NAND_ECC_ALGO_HAMMING] = "hamming", + [NAND_ECC_ALGO_BCH] = "bch", + [NAND_ECC_ALGO_RS] = "rs", }; static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np) @@ -4927,7 +4959,7 @@ static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np) err = of_property_read_string(np, "nand-ecc-algo", &pm); if (!err) { - for (ecc_algo = NAND_ECC_HAMMING; + for (ecc_algo = NAND_ECC_ALGO_HAMMING; ecc_algo < ARRAY_SIZE(nand_ecc_algos); ecc_algo++) { if (!strcasecmp(pm, nand_ecc_algos[ecc_algo])) @@ -4942,12 +4974,12 @@ static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np) err = of_property_read_string(np, "nand-ecc-mode", &pm); if (!err) { if (!strcasecmp(pm, "soft")) - return NAND_ECC_HAMMING; + return NAND_ECC_ALGO_HAMMING; else if (!strcasecmp(pm, "soft_bch")) - return NAND_ECC_BCH; + return NAND_ECC_ALGO_BCH; } - return NAND_ECC_UNKNOWN; + return NAND_ECC_ALGO_UNKNOWN; } static int nand_dt_init(struct nand_chip *chip) @@ -4976,7 +5008,7 @@ static int nand_dt_init(struct nand_chip *chip) if (ecc_mode >= 0) chip->ecc.mode = ecc_mode; - if (ecc_algo != NAND_ECC_UNKNOWN) + if (ecc_algo != NAND_ECC_ALGO_UNKNOWN) chip->ecc.algo = ecc_algo; if (ecc_strength >= 0) @@ -5105,7 +5137,7 @@ static int nand_set_ecc_soft_ops(struct nand_chip *chip) return -EINVAL; switch (ecc->algo) { - case NAND_ECC_HAMMING: + case NAND_ECC_ALGO_HAMMING: ecc->calculate = nand_calculate_ecc; ecc->correct = nand_correct_data; ecc->read_page = nand_read_page_swecc; @@ -5126,7 +5158,7 @@ static int nand_set_ecc_soft_ops(struct nand_chip *chip) ecc->options |= NAND_ECC_SOFT_HAMMING_SM_ORDER; return 0; - case NAND_ECC_BCH: + case NAND_ECC_ALGO_BCH: if (!mtd_nand_has_bch()) { WARN(1, "CONFIG_MTD_NAND_ECC_SW_BCH not enabled\n"); return -EINVAL; @@ -5270,8 +5302,8 @@ nand_match_ecc_req(struct nand_chip *chip, { struct mtd_info *mtd = nand_to_mtd(chip); const struct nand_ecc_step_info *stepinfo; - int req_step = chip->base.eccreq.step_size; - int req_strength = chip->base.eccreq.strength; + int req_step = chip->base.ecc.requirements.step_size; + int req_strength = chip->base.ecc.requirements.strength; int req_corr, step_size, strength, nsteps, ecc_bytes, ecc_bytes_total; int best_step, best_strength, best_ecc_bytes; int best_ecc_bytes_total = INT_MAX; @@ -5464,7 +5496,7 @@ static bool nand_ecc_strength_good(struct nand_chip *chip) struct nand_ecc_ctrl *ecc = &chip->ecc; int corr, ds_corr; - if (ecc->size == 0 || chip->base.eccreq.step_size == 0) + if (ecc->size == 0 || chip->base.ecc.requirements.step_size == 0) /* Not enough information */ return true; @@ -5473,10 +5505,10 @@ static bool nand_ecc_strength_good(struct nand_chip *chip) * the correction density. */ corr = (mtd->writesize * ecc->strength) / ecc->size; - ds_corr = (mtd->writesize * chip->base.eccreq.strength) / - chip->base.eccreq.step_size; + ds_corr = (mtd->writesize * chip->base.ecc.requirements.strength) / + chip->base.ecc.requirements.step_size; - return corr >= ds_corr && ecc->strength >= chip->base.eccreq.strength; + return corr >= ds_corr && ecc->strength >= chip->base.ecc.requirements.strength; } static int rawnand_erase(struct nand_device *nand, const struct nand_pos *pos) @@ -5566,7 +5598,7 @@ int nand_scan_tail(struct nand_chip *chip) * If no default placement scheme is given, select an appropriate one. */ if (!mtd->ooblayout && - !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_BCH)) { + !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_ALGO_BCH)) { switch (mtd->oobsize) { case 8: case 16: @@ -5662,7 +5694,7 @@ int nand_scan_tail(struct nand_chip *chip) pr_warn("%d byte HW ECC not possible on %d byte page size, fallback to SW ECC\n", ecc->size, mtd->writesize); ecc->mode = NAND_ECC_SOFT; - ecc->algo = NAND_ECC_HAMMING; + ecc->algo = NAND_ECC_ALGO_HAMMING; case NAND_ECC_SOFT: ret = nand_set_ecc_soft_ops(chip); if (ret) { @@ -5752,8 +5784,8 @@ int nand_scan_tail(struct nand_chip *chip) if (!nand_ecc_strength_good(chip)) pr_warn("WARNING: %s: the ECC used on your system (%db/%dB) is too weak compared to the one required by the NAND chip (%db/%dB)\n", mtd->name, chip->ecc.strength, chip->ecc.size, - chip->base.eccreq.strength, - chip->base.eccreq.step_size); + chip->base.ecc.requirements.strength, + chip->base.ecc.requirements.step_size); /* Allow subpage writes up to ecc.steps. Not possible for MLC flash */ if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && nand_is_slc(chip)) { @@ -5913,7 +5945,7 @@ EXPORT_SYMBOL(nand_scan_with_ids); void nand_cleanup(struct nand_chip *chip) { if (chip->ecc.mode == NAND_ECC_SOFT && - chip->ecc.algo == NAND_ECC_BCH) + chip->ecc.algo == NAND_ECC_ALGO_BCH) nand_bch_free((struct nand_bch_control *)chip->ecc.priv); nanddev_cleanup(&chip->base); diff --git a/drivers/mtd/nand/nand_denali.c b/drivers/mtd/nand/nand_denali.c index 1d7d1b62a8..f9896defc8 100644 --- a/drivers/mtd/nand/nand_denali.c +++ b/drivers/mtd/nand/nand_denali.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * NAND Flash Controller Device Driver * Copyright © 2009-2010, Intel Corporation and its suppliers. diff --git a/drivers/mtd/nand/nand_denali_dt.c b/drivers/mtd/nand/nand_denali_dt.c index 8deea0292e..d21cdc9756 100644 --- a/drivers/mtd/nand/nand_denali_dt.c +++ b/drivers/mtd/nand/nand_denali_dt.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * 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 <common.h> @@ -22,6 +14,7 @@ #include <io.h> #include <of_mtd.h> #include <errno.h> +#include <globalvar.h> #include <linux/clk.h> #include <linux/spinlock.h> @@ -51,6 +44,18 @@ static const struct denali_dt_data denali_socfpga_data = { .ecc_caps = &denali_socfpga_ecc_caps, }; +enum of_binding_name { + DENALI_OF_BINDING_CHIP, + DENALI_OF_BINDING_CONTROLLER, + DENALI_OF_BINDING_AUTO, +}; + +static const char *denali_of_binding_names[] = { + "chip", "controller", "auto" +}; + +static int denali_of_binding; + /* * Older versions of the kernel driver require the partition nodes * to be direct subnodes of the controller node. Starting with Kernel @@ -78,28 +83,50 @@ static int denali_partition_fixup(struct mtd_info *mtd, struct device_node *root struct denali_controller, controller); struct device_node *np, *mtdnp = mtd_get_of_node(mtd); + struct device_node *chip_np, *controller_np; char *name; name = of_get_reproducible_name(mtdnp); - np = of_find_node_by_reproducible_name(root, name); + chip_np = of_find_node_by_reproducible_name(root, name); free(name); - if (np) { - dev_info(denali->dev, "Fixing up chip node %s\n", - np->full_name); - } else { - name = of_get_reproducible_name(mtdnp->parent); - np = of_find_node_by_reproducible_name(root, name); - free(name); - - if (np) - dev_info(denali->dev, "Fixing up controller node %s\n", - np->full_name); - } + name = of_get_reproducible_name(mtdnp->parent); + controller_np = of_find_node_by_reproducible_name(root, name); + free(name); + + if (!controller_np) + return -EINVAL; + + switch (denali_of_binding) { + case DENALI_OF_BINDING_CHIP: + if (chip_np) { + np = chip_np; + } else { + np = of_new_node(controller_np, mtdnp->name); + of_property_write_u32(np, "reg", 0); + chip_np = np; + } + break; + case DENALI_OF_BINDING_CONTROLLER: + np = controller_np; + break; + case DENALI_OF_BINDING_AUTO: + default: + np = chip_np ? chip_np : controller_np; + break; + }; if (!np) return -EINVAL; + dev_info(denali->dev, "Fixing up %s node %pOF\n", + chip_np ? "chip" : "controller", np); + + if (!chip_np) { + of_property_write_bool(np, "#size-cells", false); + of_property_write_bool(np, "#address-cells", false); + } + return of_fixup_partitions(np, &mtd->cdev); } @@ -133,10 +160,18 @@ static int denali_dt_chip_init(struct denali_controller *denali, nand_set_flash_node(&dchip->chip, chip_np); } - return denali_chip_init(denali, dchip); + ret = denali_chip_init(denali, dchip); + if (ret) + return ret; + + dev_add_param_enum(&dchip->chip.base.mtd.dev, "denali_partition_binding", + NULL, NULL, &denali_of_binding, denali_of_binding_names, + ARRAY_SIZE(denali_of_binding_names), NULL); + + return 0; } -static int denali_dt_probe(struct device_d *ofdev) +static int denali_dt_probe(struct device *ofdev) { struct resource *iores; struct denali_dt *dt; @@ -197,7 +232,7 @@ static int denali_dt_probe(struct device_d *ofdev) if (ret) goto out_disable_clk; - for_each_child_of_node(ofdev->device_node, np) { + for_each_child_of_node(ofdev->of_node, np) { ret = denali_dt_chip_init(denali, np); if (ret) goto out_disable_clk; @@ -219,8 +254,9 @@ static __maybe_unused struct of_device_id denali_nand_compatible[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, denali_nand_compatible); -static struct driver_d denali_dt_driver = { +static struct driver denali_dt_driver = { .name = "denali-nand-dt", .probe = denali_dt_probe, .of_compatible = DRV_OF_COMPAT(denali_nand_compatible) diff --git a/drivers/mtd/nand/nand_esmt.c b/drivers/mtd/nand/nand_esmt.c index 3338c68aaa..cd635c27ef 100644 --- a/drivers/mtd/nand/nand_esmt.c +++ b/drivers/mtd/nand/nand_esmt.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2018 Toradex AG * @@ -14,20 +14,20 @@ static void esmt_nand_decode_id(struct nand_chip *chip) /* Extract ECC requirements from 5th id byte. */ if (chip->id.len >= 5 && nand_is_slc(chip)) { - chip->base.eccreq.step_size = 512; + chip->base.ecc.requirements.step_size = 512; switch (chip->id.data[4] & 0x3) { case 0x0: - chip->base.eccreq.strength = 4; + chip->base.ecc.requirements.strength = 4; break; case 0x1: - chip->base.eccreq.strength = 2; + chip->base.ecc.requirements.strength = 2; break; case 0x2: - chip->base.eccreq.strength = 1; + chip->base.ecc.requirements.strength = 1; break; default: WARN(1, "Could not get ECC info"); - chip->base.eccreq.step_size = 0; + chip->base.ecc.requirements.step_size = 0; break; } } diff --git a/drivers/mtd/nand/nand_fsl_ifc.c b/drivers/mtd/nand/nand_fsl_ifc.c index 64dc9c225f..3b14b4ae15 100644 --- a/drivers/mtd/nand/nand_fsl_ifc.c +++ b/drivers/mtd/nand/nand_fsl_ifc.c @@ -46,7 +46,7 @@ struct fsl_ifc_ctrl { /* mtd information per set */ struct fsl_ifc_mtd { - struct device_d *dev; + struct device *dev; struct nand_chip chip; struct fsl_ifc_ctrl *ctrl; uint32_t cs; /* On which chipsel NAND is connected */ @@ -851,7 +851,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) } ctrl = priv->ctrl = ifc_ctrl; - if (priv->dev->device_node) { + if (priv->dev->of_node) { int bank, banks; /* find which chip select it is connected to */ @@ -964,7 +964,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) mtd_set_ooblayout(mtd, &fsl_ifc_ooblayout_ops); } else { nand->ecc.mode = NAND_ECC_SOFT; - nand->ecc.algo = NAND_ECC_HAMMING; + nand->ecc.algo = NAND_ECC_ALGO_HAMMING; } if (ctrl->version >= FSL_IFC_V1_1_0) { @@ -979,7 +979,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) return 0; } -static int fsl_ifc_nand_probe(struct device_d *dev) +static int fsl_ifc_nand_probe(struct device *dev) { struct fsl_ifc_mtd *priv; struct resource *iores; @@ -1025,8 +1025,9 @@ static __maybe_unused struct of_device_id fsl_nand_compatible[] = { }, { } }; +MODULE_DEVICE_TABLE(of, fsl_nand_compatible); -static struct driver_d fsl_ifc_driver = { +static struct driver fsl_ifc_driver = { .name = "fsl_nand", .probe = fsl_ifc_nand_probe, .of_compatible = DRV_OF_COMPAT(fsl_nand_compatible), diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c index 0422ed53aa..fef9207495 100644 --- a/drivers/mtd/nand/nand_hynix.c +++ b/drivers/mtd/nand/nand_hynix.c @@ -498,30 +498,30 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip, if (valid_jedecid) { /* Reference: H27UCG8T2E datasheet */ - chip->base.eccreq.step_size = 1024; + chip->base.ecc.requirements.step_size = 1024; switch (ecc_level) { case 0: - chip->base.eccreq.step_size = 0; - chip->base.eccreq.strength = 0; + chip->base.ecc.requirements.step_size = 0; + chip->base.ecc.requirements.strength = 0; break; case 1: - chip->base.eccreq.strength = 4; + chip->base.ecc.requirements.strength = 4; break; case 2: - chip->base.eccreq.strength = 24; + chip->base.ecc.requirements.strength = 24; break; case 3: - chip->base.eccreq.strength = 32; + chip->base.ecc.requirements.strength = 32; break; case 4: - chip->base.eccreq.strength = 40; + chip->base.ecc.requirements.strength = 40; break; case 5: - chip->base.eccreq.strength = 50; + chip->base.ecc.requirements.strength = 50; break; case 6: - chip->base.eccreq.strength = 60; + chip->base.ecc.requirements.strength = 60; break; default: /* @@ -542,14 +542,14 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip, if (nand_tech < 3) { /* > 26nm, reference: H27UBG8T2A datasheet */ if (ecc_level < 5) { - chip->base.eccreq.step_size = 512; - chip->base.eccreq.strength = 1 << ecc_level; + chip->base.ecc.requirements.step_size = 512; + chip->base.ecc.requirements.strength = 1 << ecc_level; } else if (ecc_level < 7) { if (ecc_level == 5) - chip->base.eccreq.step_size = 2048; + chip->base.ecc.requirements.step_size = 2048; else - chip->base.eccreq.step_size = 1024; - chip->base.eccreq.strength = 24; + chip->base.ecc.requirements.step_size = 1024; + chip->base.ecc.requirements.strength = 24; } else { /* * We should never reach this case, but if that @@ -562,14 +562,14 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip, } else { /* <= 26nm, reference: H27UBG8T2B datasheet */ if (!ecc_level) { - chip->base.eccreq.step_size = 0; - chip->base.eccreq.strength = 0; + chip->base.ecc.requirements.step_size = 0; + chip->base.ecc.requirements.strength = 0; } else if (ecc_level < 5) { - chip->base.eccreq.step_size = 512; - chip->base.eccreq.strength = 1 << (ecc_level - 1); + chip->base.ecc.requirements.step_size = 512; + chip->base.ecc.requirements.strength = 1 << (ecc_level - 1); } else { - chip->base.eccreq.step_size = 1024; - chip->base.eccreq.strength = 24 + + chip->base.ecc.requirements.step_size = 1024; + chip->base.ecc.requirements.strength = 24 + (8 * (ecc_level - 5)); } } diff --git a/drivers/mtd/nand/nand_imx.c b/drivers/mtd/nand/nand_imx.c index 0f20e9d394..23b9c52e0f 100644 --- a/drivers/mtd/nand/nand_imx.c +++ b/drivers/mtd/nand/nand_imx.c @@ -1,18 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> */ /* - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ - -/* * MX21 Hardware contains a bug which causes HW ECC to fail for two * consecutive read pages containing 1bit Errors (See MX21 Chip Erata, * Erratum 16). Use software ECC for this chip. @@ -26,8 +18,8 @@ #include <linux/mtd/nand.h> #include <linux/mtd/rawnand.h> #include <linux/clk.h> -#include <mach/generic.h> -#include <mach/imx-nand.h> +#include <mach/imx/generic.h> +#include <mach/imx/imx-nand.h> #include <io.h> #include <of_mtd.h> #include <errno.h> @@ -35,7 +27,7 @@ struct imx_nand_host { struct nand_chip nand; struct mtd_partition *parts; - struct device_d *dev; + struct device *dev; void *spare0; void *main_area0; @@ -1114,7 +1106,7 @@ static struct nand_bbt_descr bbt_mirror_descr = { static int __init mxcnd_probe_dt(struct imx_nand_host *host) { - struct device_node *np = host->dev->device_node; + struct device_node *np = host->dev->of_node; int buswidth; if (!IS_ENABLED(CONFIG_OFDEVICE)) @@ -1248,7 +1240,7 @@ out: * @return The function always returns 0. */ -static int __init imxnd_probe(struct device_d *dev) +static int __init imxnd_probe(struct device *dev) { struct resource *iores; struct nand_chip *this; @@ -1488,8 +1480,9 @@ static __maybe_unused struct of_device_id imx_nand_compatible[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, imx_nand_compatible); -static struct driver_d imx_nand_driver = { +static struct driver imx_nand_driver = { .name = "imx_nand", .probe = imxnd_probe, .of_compatible = DRV_OF_COMPAT(imx_nand_compatible), diff --git a/drivers/mtd/nand/nand_jedec.c b/drivers/mtd/nand/nand_jedec.c index 5632d2c73f..2b21e2d5b5 100644 --- a/drivers/mtd/nand/nand_jedec.c +++ b/drivers/mtd/nand/nand_jedec.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) * 2002-2006 Thomas Gleixner (tglx@linutronix.de) @@ -121,8 +121,8 @@ int nand_jedec_detect(struct nand_chip *chip) ecc = &p->ecc_info[0]; if (ecc->codeword_size >= 9) { - chip->base.eccreq.strength = ecc->ecc_bits; - chip->base.eccreq.step_size = 1 << ecc->codeword_size; + chip->base.ecc.requirements.strength = ecc->ecc_bits; + chip->base.ecc.requirements.step_size = 1 << ecc->codeword_size; } else { pr_warn("Invalid codeword size\n"); } diff --git a/drivers/mtd/nand/nand_legacy.c b/drivers/mtd/nand/nand_legacy.c index 0fcafe38f9..074a34e7f8 100644 --- a/drivers/mtd/nand/nand_legacy.c +++ b/drivers/mtd/nand/nand_legacy.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) * 2002-2006 Thomas Gleixner (tglx@linutronix.de) diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c index d59be7ca7b..758316e681 100644 --- a/drivers/mtd/nand/nand_micron.c +++ b/drivers/mtd/nand/nand_micron.c @@ -426,7 +426,8 @@ static int micron_supports_on_die_ecc(struct nand_chip *chip) /* * We only support on-die ECC of 4/512 or 8/512 */ - if (chip->base.eccreq.strength != 4 && chip->base.eccreq.strength != 8) + if (chip->base.ecc.requirements.strength != 4 && + chip->base.ecc.requirements.strength != 8) return MICRON_ON_DIE_UNSUPPORTED; /* 0x2 means on-die ECC is available. */ @@ -467,7 +468,8 @@ static int micron_supports_on_die_ecc(struct nand_chip *chip) /* * We only support on-die ECC of 4/512 or 8/512 */ - if (chip->base.eccreq.strength != 4 && chip->base.eccreq.strength != 8) + if (chip->base.ecc.requirements.strength != 4 && + chip->base.ecc.requirements.strength != 8) return MICRON_ON_DIE_UNSUPPORTED; return MICRON_ON_DIE_SUPPORTED; @@ -524,7 +526,7 @@ static int micron_nand_init(struct nand_chip *chip) * That's not needed for 8-bit ECC, because the status expose * a better approximation of the number of bitflips in a page. */ - if (chip->base.eccreq.strength == 4) { + if (chip->base.ecc.requirements.strength == 4) { micron->ecc.rawbuf = kmalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); @@ -534,17 +536,17 @@ static int micron_nand_init(struct nand_chip *chip) } } - if (chip->base.eccreq.strength == 4) + if (chip->base.ecc.requirements.strength == 4) mtd_set_ooblayout(mtd, µn_nand_on_die_4_ooblayout_ops); else mtd_set_ooblayout(mtd, µn_nand_on_die_8_ooblayout_ops); - chip->ecc.bytes = chip->base.eccreq.strength * 2; + chip->ecc.bytes = chip->base.ecc.requirements.strength * 2; chip->ecc.size = 512; - chip->ecc.strength = chip->base.eccreq.strength; - chip->ecc.algo = NAND_ECC_BCH; + chip->ecc.strength = chip->base.ecc.requirements.strength; + chip->ecc.algo = NAND_ECC_ALGO_BCH; chip->ecc.read_page = micron_nand_read_page_on_die_ecc; chip->ecc.write_page = micron_nand_write_page_on_die_ecc; diff --git a/drivers/mtd/nand/nand_mrvl_nfc.c b/drivers/mtd/nand/nand_mrvl_nfc.c index 1f3e152375..27ca4456c8 100644 --- a/drivers/mtd/nand/nand_mrvl_nfc.c +++ b/drivers/mtd/nand/nand_mrvl_nfc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/mtd/nand/mrvl_nand.c * @@ -5,10 +6,6 @@ * Copyright © 2006 Marvell International Ltd. * Copyright (C) 2014 Robert Jarzmik * - * 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. - * * See Documentation/mtd/nand/pxa3xx-nand.txt for more details. */ #include <common.h> @@ -140,7 +137,7 @@ struct mrvl_nand_variant { struct mrvl_nand_host { struct nand_chip chip; struct mtd_partition *parts; - struct device_d *dev; + struct device *dev; struct clk *core_clk; /* calculated from mrvl_nand_flash data */ @@ -302,6 +299,7 @@ static struct of_device_id mrvl_nand_dt_ids[] = { }, {} }; +MODULE_DEVICE_TABLE(of, mrvl_nand_dt_ids); /* convert nano-seconds to nand flash controller clock cycles */ static int ns2cycle(int ns, unsigned long clk_rate) @@ -1126,7 +1124,7 @@ static int mrvl_nand_scan(struct nand_chip *chip) return nand_scan_tail(chip); } -static struct mrvl_nand_host *alloc_nand_resource(struct device_d *dev) +static struct mrvl_nand_host *alloc_nand_resource(struct device *dev) { struct resource *iores; struct mrvl_nand_platform_data *pdata; @@ -1191,7 +1189,7 @@ static struct mrvl_nand_host *alloc_nand_resource(struct device_d *dev) static int mrvl_nand_probe_dt(struct mrvl_nand_host *host) { - struct device_node *np = host->dev->device_node; + struct device_node *np = host->dev->of_node; const struct of_device_id *match; const struct mrvl_nand_variant *variant; @@ -1222,7 +1220,7 @@ static int mrvl_nand_probe_dt(struct mrvl_nand_host *host) return 0; } -static int mrvl_nand_probe(struct device_d *dev) +static int mrvl_nand_probe(struct device *dev) { struct mrvl_nand_host *host; struct nand_chip *chip; @@ -1253,7 +1251,7 @@ static int mrvl_nand_probe(struct device_d *dev) return ret; } -static struct driver_d mrvl_nand_driver = { +static struct driver mrvl_nand_driver = { .name = "mrvl_nand", .probe = mrvl_nand_probe, .of_compatible = DRV_OF_COMPAT(mrvl_nand_dt_ids), diff --git a/drivers/mtd/nand/nand_mxs.c b/drivers/mtd/nand/nand_mxs.c index 0540ba0216..c2a7d036d6 100644 --- a/drivers/mtd/nand/nand_mxs.c +++ b/drivers/mtd/nand/nand_mxs.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Freescale i.MX28 NAND flash driver * @@ -11,11 +12,6 @@ * * Copyright (C) 2010 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. */ #include <linux/mtd/mtd.h> @@ -25,6 +21,7 @@ #include <linux/types.h> #include <linux/clk.h> #include <linux/err.h> +#include <linux/bitfield.h> #include <of_mtd.h> #include <common.h> #include <dma.h> @@ -35,130 +32,11 @@ #include <io.h> #include <dma/apbh-dma.h> #include <stmp-device.h> -#include <mach/generic.h> +#include <mach/imx/generic.h> +#include <soc/imx/gpmi-nand.h> #include "internals.h" -#define MX28_BLOCK_SFTRST (1 << 31) -#define MX28_BLOCK_CLKGATE (1 << 30) - -#define GPMI_CTRL0 0x00000000 -#define GPMI_CTRL0_RUN (1 << 29) -#define GPMI_CTRL0_DEV_IRQ_EN (1 << 28) -/* Disable for now since we don't need it and it is different on MX23. -#define GPMI_CTRL0_LOCK_CS (1 << 27) -*/ -#define GPMI_CTRL0_UDMA (1 << 26) -#define GPMI_CTRL0_COMMAND_MODE_MASK (0x3 << 24) -#define GPMI_CTRL0_COMMAND_MODE_OFFSET 24 -#define GPMI_CTRL0_COMMAND_MODE_WRITE (0x0 << 24) -#define GPMI_CTRL0_COMMAND_MODE_READ (0x1 << 24) -#define GPMI_CTRL0_COMMAND_MODE_READ_AND_COMPARE (0x2 << 24) -#define GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY (0x3 << 24) -#define GPMI_CTRL0_WORD_LENGTH (1 << 23) -/* Careful: Is 0x3 on MX23 -#define GPMI_CTRL0_CS_MASK (0x7 << 20) -*/ -#define GPMI_CTRL0_CS_OFFSET 20 -#define GPMI_CTRL0_ADDRESS_MASK (0x7 << 17) -#define GPMI_CTRL0_ADDRESS_OFFSET 17 -#define GPMI_CTRL0_ADDRESS_NAND_DATA (0x0 << 17) -#define GPMI_CTRL0_ADDRESS_NAND_CLE (0x1 << 17) -#define GPMI_CTRL0_ADDRESS_NAND_ALE (0x2 << 17) -#define GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16) -#define GPMI_CTRL0_XFER_COUNT_MASK 0xffff -#define GPMI_CTRL0_XFER_COUNT_OFFSET 0 - -#define GPMI_CTRL1 0x00000060 -#define GPMI_CTRL1_SET 0x00000064 -#define GPMI_CTRL1_CLR 0x00000068 -#define GPMI_CTRL1_DECOUPLE_CS (1 << 24) -#define GPMI_CTRL1_WRN_DLY(d) (((d) & 0x3) << 22) -#define GPMI_CTRL1_TIMEOUT_IRQ_EN (1 << 20) -#define GPMI_CTRL1_GANGED_RDYBUSY (1 << 19) -#define GPMI_CTRL1_BCH_MODE (1 << 18) -#define GPMI_CTRL1_DLL_ENABLE (1 << 17) -#define GPMI_CTRL1_HALF_PERIOD (1 << 16) -#define GPMI_CTRL1_RDN_DELAY(d) (((d) & 0xf) << 12) -#define GPMI_CTRL1_DMA2ECC_MODE (1 << 11) -#define GPMI_CTRL1_DEV_IRQ (1 << 10) -#define GPMI_CTRL1_TIMEOUT_IRQ (1 << 9) -#define GPMI_CTRL1_BURST_EN (1 << 8) -#define GPMI_CTRL1_ABORT_WAIT_REQUEST (1 << 7) -#define GPMI_CTRL1_ABORT_WAIT_FOR_READY_CHANNEL_MASK (0x7 << 4) -#define GPMI_CTRL1_ABORT_WAIT_FOR_READY_CHANNEL_OFFSET 4 -#define GPMI_CTRL1_DEV_RESET (1 << 3) -#define GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) -#define GPMI_CTRL1_CAMERA_MODE (1 << 1) -#define GPMI_CTRL1_GPMI_MODE (1 << 0) - -#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 GPMI_TIMING0 0x00000070 - -#define GPMI_TIMING0_ADDRESS_SETUP(d) (((d) & 0xff) << 16) -#define GPMI_TIMING0_DATA_HOLD(d) (((d) & 0xff) << 8) -#define GPMI_TIMING0_DATA_SETUP(d) (((d) & 0xff) << 0) - -#define GPMI_TIMING1 0x00000080 -#define GPMI_TIMING1_BUSY_TIMEOUT(d) (((d) & 0xffff) << 16) - -#define GPMI_ECCCTRL_HANDLE_MASK (0xffff << 16) -#define GPMI_ECCCTRL_HANDLE_OFFSET 16 -#define GPMI_ECCCTRL_ECC_CMD_MASK (0x3 << 13) -#define GPMI_ECCCTRL_ECC_CMD_OFFSET 13 -#define GPMI_ECCCTRL_ECC_CMD_DECODE (0x0 << 13) -#define GPMI_ECCCTRL_ECC_CMD_ENCODE (0x1 << 13) -#define GPMI_ECCCTRL_ENABLE_ECC (1 << 12) -#define GPMI_ECCCTRL_BUFFER_MASK_MASK 0x1ff -#define GPMI_ECCCTRL_BUFFER_MASK_OFFSET 0 -#define GPMI_ECCCTRL_BUFFER_MASK_BCH_AUXONLY 0x100 -#define GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE 0x1ff - -#define GPMI_STAT 0x000000b0 -#define GPMI_STAT_READY_BUSY_OFFSET 24 - -#define GPMI_DEBUG 0x000000c0 -#define GPMI_DEBUG_READY0_OFFSET 28 - -#define GPMI_VERSION 0x000000d0 -#define GPMI_VERSION_MINOR_OFFSET 16 -#define GPMI_VERSION_TYPE_MX23 0x0300 - -#define BCH_CTRL 0x00000000 -#define BCH_CTRL_COMPLETE_IRQ (1 << 0) -#define BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) - -#define BCH_LAYOUTSELECT 0x00000070 - -#define BCH_FLASH0LAYOUT0 0x00000080 -#define BCH_FLASHLAYOUT0_NBLOCKS_MASK (0xff << 24) -#define BCH_FLASHLAYOUT0_NBLOCKS_OFFSET 24 -#define BCH_FLASHLAYOUT0_META_SIZE_MASK (0xff << 16) -#define BCH_FLASHLAYOUT0_META_SIZE_OFFSET 16 -#define BCH_FLASHLAYOUT0_ECC0_MASK (0xf << 12) -#define BCH_FLASHLAYOUT0_ECC0_OFFSET 12 -#define IMX6_BCH_FLASHLAYOUT0_ECC0_OFFSET 11 - -#define BCH_FLASH0LAYOUT1 0x00000090 -#define BCH_FLASHLAYOUT1_PAGE_SIZE_MASK (0xffff << 16) -#define BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET 16 -#define BCH_FLASHLAYOUT1_ECCN_MASK (0xf << 12) -#define BCH_FLASHLAYOUT1_ECCN_OFFSET 12 -#define IMX6_BCH_FLASHLAYOUT1_ECCN_OFFSET 11 - -#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4 - -#define MXS_NAND_CHUNK_DATA_CHUNK_SIZE 512 -#define MXS_NAND_METADATA_SIZE 10 - -#define MXS_NAND_COMMAND_BUFFER_SIZE 32 - -#define MXS_NAND_BCH_TIMEOUT 10000 - enum gpmi_type { GPMI_MXS, GPMI_IMX6, @@ -198,7 +76,7 @@ struct nand_timing { }; struct mxs_nand_info { - struct device_d *dev; + struct device *dev; struct nand_chip nand_chip; void __iomem *io_base; void __iomem *bch_base; @@ -224,7 +102,7 @@ struct mxs_nand_info { loff_t to, struct mtd_oob_ops *ops); /* DMA descriptors */ - struct mxs_dma_desc **desc; + struct mxs_dma_cmd *desc; uint32_t desc_index; #define GPMI_ASYNC_EDO_ENABLED (1 << 0) @@ -240,16 +118,16 @@ static inline int mxs_nand_is_imx6(struct mxs_nand_info *info) return info->type == GPMI_IMX6; } -static struct mxs_dma_desc *mxs_nand_get_dma_desc(struct mxs_nand_info *info) +static struct mxs_dma_cmd *mxs_nand_get_dma_desc(struct mxs_nand_info *info) { - struct mxs_dma_desc *desc; + struct mxs_dma_cmd *desc; if (info->desc_index >= MXS_NAND_DMA_DESCRIPTOR_COUNT) { printf("MXS NAND: Too many DMA descriptors requested\n"); return NULL; } - desc = info->desc[info->desc_index]; + desc = &info->desc[info->desc_index]; info->desc_index++; return desc; @@ -258,12 +136,11 @@ static struct mxs_dma_desc *mxs_nand_get_dma_desc(struct mxs_nand_info *info) static void mxs_nand_return_dma_descs(struct mxs_nand_info *info) { int i; - struct mxs_dma_desc *desc; + struct mxs_dma_cmd *desc; for (i = 0; i < info->desc_index; i++) { - desc = info->desc[i]; - memset(desc, 0, sizeof(struct mxs_dma_desc)); - desc->address = (dma_addr_t)desc; + desc = &info->desc[i]; + memset(desc, 0, sizeof(struct mxs_dma_cmd)); } info->desc_index = 0; @@ -446,7 +323,7 @@ static int mxs_nand_wait_for_bch_complete(struct mxs_nand_info *nand_info) static void mxs_nand_cmd_ctrl(struct nand_chip *chip, int data, unsigned int ctrl) { struct mxs_nand_info *nand_info = chip->priv; - struct mxs_dma_desc *d; + struct mxs_dma_cmd *d; uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; int ret; @@ -486,26 +363,24 @@ static void mxs_nand_cmd_ctrl(struct nand_chip *chip, int data, unsigned int ctr /* Compile the DMA descriptor -- a descriptor that sends command. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_DEC_SEM | - MXS_DMA_DESC_WAIT4END | (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | - (nand_info->cmd_queue_len << MXS_DMA_DESC_BYTES_OFFSET); + MXS_DMA_DESC_WAIT4END | MXS_DMA_DESC_PIO_WORDS(3) | + MXS_DMA_DESC_XFER_COUNT(nand_info->cmd_queue_len); - d->cmd.address = (dma_addr_t)nand_info->cmd_buf; + d->address = (dma_addr_t)nand_info->cmd_buf; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_CLE | GPMI_CTRL0_ADDRESS_INCREMENT | nand_info->cmd_queue_len; - mxs_dma_desc_append(channel, d); - /* Execute the DMA chain. */ - ret = mxs_dma_go(channel); + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); if (ret) printf("MXS NAND: Error sending command (%d)\n", ret); @@ -601,7 +476,7 @@ static void mxs_nand_swap_block_mark(struct nand_chip *chip, static void mxs_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int length) { struct mxs_nand_info *nand_info = chip->priv; - struct mxs_dma_desc *d; + struct mxs_dma_cmd *d; uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; int ret; @@ -617,23 +492,21 @@ static void mxs_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int length) /* Compile the DMA descriptor - a descriptor that reads data. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_DMA_WRITE | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | - (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | - (length << MXS_DMA_DESC_BYTES_OFFSET); + MXS_DMA_DESC_PIO_WORDS(1) | + MXS_DMA_DESC_XFER_COUNT(length); - d->cmd.address = (dma_addr_t)nand_info->data_buf; + d->address = (dma_addr_t)nand_info->data_buf; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA | length; - mxs_dma_desc_append(channel, d); - /* * A DMA descriptor that waits for the command to end and the chip to * become ready. @@ -643,23 +516,21 @@ static void mxs_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int length) * did that and no one has re-thought it yet. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_DEC_SEM | - MXS_DMA_DESC_WAIT4END | (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + MXS_DMA_DESC_WAIT4END | MXS_DMA_DESC_PIO_WORDS(4); - d->cmd.address = 0; + d->address = 0; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA; - mxs_dma_desc_append(channel, d); - /* Execute the DMA chain. */ - ret = mxs_dma_go(channel); + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); if (ret) { printf("MXS NAND: DMA read error\n"); goto rtn; @@ -678,7 +549,7 @@ static void mxs_nand_write_buf(struct nand_chip *chip, const uint8_t *buf, int length) { struct mxs_nand_info *nand_info = chip->priv; - struct mxs_dma_desc *d; + struct mxs_dma_cmd *d; uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; int ret; @@ -696,25 +567,23 @@ static void mxs_nand_write_buf(struct nand_chip *chip, const uint8_t *buf, /* Compile the DMA descriptor - a descriptor that writes data. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | - (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | - (length << MXS_DMA_DESC_BYTES_OFFSET); + MXS_DMA_DESC_PIO_WORDS(4) | + MXS_DMA_DESC_XFER_COUNT(length); - d->cmd.address = (dma_addr_t)nand_info->data_buf; + d->address = (dma_addr_t)nand_info->data_buf; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA | length; - mxs_dma_desc_append(channel, d); - /* Execute the DMA chain. */ - ret = mxs_dma_go(channel); + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); if (ret) printf("MXS NAND: DMA write error\n"); @@ -736,138 +605,155 @@ static void mxs_nand_config_bch(struct nand_chip *chip, int readlen) struct mxs_nand_info *nand_info = chip->priv; int chunk_size; void __iomem *bch_regs = nand_info->bch_base; + u32 fl0, fl1; if (mxs_nand_is_imx6(nand_info)) chunk_size = MXS_NAND_CHUNK_DATA_CHUNK_SIZE >> 2; else chunk_size = MXS_NAND_CHUNK_DATA_CHUNK_SIZE; - writel((mxs_nand_ecc_chunk_cnt(readlen) - 1) - << BCH_FLASHLAYOUT0_NBLOCKS_OFFSET | - MXS_NAND_METADATA_SIZE << BCH_FLASHLAYOUT0_META_SIZE_OFFSET | - (chip->ecc.strength >> 1) - << IMX6_BCH_FLASHLAYOUT0_ECC0_OFFSET | - chunk_size, - bch_regs + BCH_FLASH0LAYOUT0); - - writel(readlen << BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET | - (chip->ecc.strength >> 1) - << IMX6_BCH_FLASHLAYOUT1_ECCN_OFFSET | - chunk_size, - bch_regs + BCH_FLASH0LAYOUT1); + fl0 = FIELD_PREP(BCH_FLASHLAYOUT0_NBLOCKS, mxs_nand_ecc_chunk_cnt(readlen) - 1); + fl0 |= FIELD_PREP(BCH_FLASHLAYOUT0_META_SIZE, MXS_NAND_METADATA_SIZE); + if (mxs_nand_is_imx6(nand_info)) + fl0 |= FIELD_PREP(IMX6_BCH_FLASHLAYOUT0_ECC0, chip->ecc.strength >> 1); + else + fl0 |= FIELD_PREP(BCH_FLASHLAYOUT0_ECC0, chip->ecc.strength >> 1); + fl0 |= FIELD_PREP(BCH_FLASHLAYOUT0_DATA0_SIZE, chunk_size); + writel(fl0, bch_regs + BCH_FLASH0LAYOUT0); + + fl1 = FIELD_PREP(BCH_FLASHLAYOUT1_PAGE_SIZE, readlen); + if (mxs_nand_is_imx6(nand_info)) + fl1 |= FIELD_PREP(IMX6_BCH_FLASHLAYOUT1_ECCN, chip->ecc.strength >> 1); + else + fl1 |= FIELD_PREP(BCH_FLASHLAYOUT1_ECCN, chip->ecc.strength >> 1); + + fl1 |= FIELD_PREP(BCH_FLASHLAYOUT1_DATAN_SIZE, chunk_size); + writel(fl1, bch_regs + BCH_FLASH0LAYOUT1); } -/* - * Read a page from NAND. - */ -static int __mxs_nand_ecc_read_page(struct nand_chip *chip, - uint8_t *buf, int oob_required, int page, - int readlen) +static int mxs_nand_do_bch_read(struct nand_chip *chip, int channel, int readtotal, + bool randomizer, int page) { - struct mtd_info *mtd = nand_to_mtd(chip); struct mxs_nand_info *nand_info = chip->priv; - struct mxs_dma_desc *d; - uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; - uint32_t corrected = 0, failed = 0; - uint8_t *status; - unsigned int max_bitflips = 0; - int i, ret, readtotal, nchunks; - - nand_read_page_op(chip, page, 0, NULL, 0); - - readlen = roundup(readlen, MXS_NAND_CHUNK_DATA_CHUNK_SIZE); - nchunks = mxs_nand_ecc_chunk_cnt(readlen); - readtotal = MXS_NAND_METADATA_SIZE; - readtotal += MXS_NAND_CHUNK_DATA_CHUNK_SIZE * nchunks; - readtotal += DIV_ROUND_UP(13 * chip->ecc.strength * nchunks, 8); - - mxs_nand_config_bch(chip, readtotal); + struct mxs_dma_cmd *d; + int ret; /* Compile the DMA descriptor - wait for ready. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END | - (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + MXS_DMA_DESC_PIO_WORDS(1); - d->cmd.address = 0; + d->address = 0; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA; - mxs_dma_desc_append(channel, d); - /* Compile the DMA descriptor - enable the BCH block and read. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | - MXS_DMA_DESC_WAIT4END | (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + MXS_DMA_DESC_WAIT4END | MXS_DMA_DESC_PIO_WORDS(6); - d->cmd.address = 0; + d->address = 0; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_READ | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA | readtotal; - d->cmd.pio_words[1] = 0; - d->cmd.pio_words[2] = + d->pio_words[1] = 0; + d->pio_words[2] = GPMI_ECCCTRL_ENABLE_ECC | GPMI_ECCCTRL_ECC_CMD_DECODE | GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE; - d->cmd.pio_words[3] = readtotal; - d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf; - d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf; - - mxs_dma_desc_append(channel, d); + d->pio_words[3] = readtotal; + d->pio_words[4] = (dma_addr_t)nand_info->data_buf; + d->pio_words[5] = (dma_addr_t)nand_info->oob_buf; + + if (randomizer) { + d->pio_words[2] |= GPMI_ECCCTRL_RANDOMIZER_ENABLE | + GPMI_ECCCTRL_RANDOMIZER_TYPE2; + d->pio_words[3] |= (page % 256) << 16; + } /* Compile the DMA descriptor - disable the BCH block. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END | - (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + MXS_DMA_DESC_PIO_WORDS(3); - d->cmd.address = 0; + d->address = 0; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA | readtotal; - d->cmd.pio_words[1] = 0; - d->cmd.pio_words[2] = 0; - - mxs_dma_desc_append(channel, d); + d->pio_words[1] = 0; + d->pio_words[2] = 0; /* Compile the DMA descriptor - deassert the NAND lock and interrupt. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM; - d->cmd.address = 0; - - mxs_dma_desc_append(channel, d); + d->address = 0; /* Execute the DMA chain. */ - ret = mxs_dma_go(channel); + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); if (ret) { - printf("MXS NAND: DMA read error (ecc)\n"); - goto rtn; + dev_err(nand_info->dev, "MXS NAND: DMA read error (ecc)\n"); + goto out; } ret = mxs_nand_wait_for_bch_complete(nand_info); if (ret) { - printf("MXS NAND: BCH read timeout\n"); - goto rtn; + dev_err(nand_info->dev, "MXS NAND: BCH read timeout\n"); + goto out; } +out: + mxs_nand_return_dma_descs(nand_info); + + return ret; +} + +/* + * Read a page from NAND. + */ +static int __mxs_nand_ecc_read_page(struct nand_chip *chip, + uint8_t *buf, int oob_required, int page, + int readlen) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxs_nand_info *nand_info = chip->priv; + uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; + uint32_t corrected = 0, failed = 0; + uint8_t *status; + unsigned int max_bitflips = 0; + int i, ret, readtotal, nchunks; + + nand_read_page_op(chip, page, 0, NULL, 0); + + readlen = roundup(readlen, MXS_NAND_CHUNK_DATA_CHUNK_SIZE); + nchunks = mxs_nand_ecc_chunk_cnt(readlen); + readtotal = MXS_NAND_METADATA_SIZE; + readtotal += MXS_NAND_CHUNK_DATA_CHUNK_SIZE * nchunks; + readtotal += DIV_ROUND_UP(13 * chip->ecc.strength * nchunks, 8); + + mxs_nand_config_bch(chip, readtotal); + + mxs_nand_do_bch_read(chip, channel, readtotal, false, page); + /* Read DMA completed, now do the mark swapping. */ mxs_nand_swap_block_mark(chip, nand_info->data_buf, nand_info->oob_buf); @@ -947,7 +833,7 @@ static int __mxs_nand_ecc_read_page(struct nand_chip *chip, chip->oob_poi[0] = nand_info->oob_buf[0]; ret = 0; -rtn: + mxs_nand_return_dma_descs(nand_info); mxs_nand_config_bch(chip, mtd->writesize + mtd->oobsize); @@ -988,7 +874,7 @@ static int mxs_nand_ecc_write_page(struct nand_chip *chip, const uint8_t *buf, { struct mtd_info *mtd = nand_to_mtd(chip); struct mxs_nand_info *nand_info = chip->priv; - struct mxs_dma_desc *d; + struct mxs_dma_cmd *d; uint32_t channel = nand_info->dma_channel_base + nand_info->cur_chip; int ret = 0; @@ -1002,31 +888,29 @@ static int mxs_nand_ecc_write_page(struct nand_chip *chip, const uint8_t *buf, /* Compile the DMA descriptor - write data. */ d = mxs_nand_get_dma_desc(nand_info); - d->cmd.data = + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | - (6 << MXS_DMA_DESC_PIO_WORDS_OFFSET); + MXS_DMA_DESC_PIO_WORDS(6); - d->cmd.address = 0; + d->address = 0; - d->cmd.pio_words[0] = + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE | GPMI_CTRL0_WORD_LENGTH | - (nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) | + FIELD_PREP(GPMI_CTRL0_CS, nand_info->cur_chip) | GPMI_CTRL0_ADDRESS_NAND_DATA; - d->cmd.pio_words[1] = 0; - d->cmd.pio_words[2] = + d->pio_words[1] = 0; + d->pio_words[2] = GPMI_ECCCTRL_ENABLE_ECC | GPMI_ECCCTRL_ECC_CMD_ENCODE | GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE; - d->cmd.pio_words[3] = (mtd->writesize + mtd->oobsize); - d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf; - d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf; - - mxs_dma_desc_append(channel, d); + d->pio_words[3] = (mtd->writesize + mtd->oobsize); + d->pio_words[4] = (dma_addr_t)nand_info->data_buf; + d->pio_words[5] = (dma_addr_t)nand_info->oob_buf; /* Execute the DMA chain. */ - ret = mxs_dma_go(channel); + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); if (ret) { printf("MXS NAND: DMA write error\n"); goto rtn; @@ -1239,6 +1123,160 @@ static int mxs_nand_block_markbad(struct nand_chip *chip , loff_t ofs) return 0; } +int mxs_nand_read_fcb_bch62(unsigned int block, void *buf, size_t size) +{ + struct nand_chip *chip; + struct mxs_nand_info *nand_info; + struct mtd_info *mtd = mxs_nand_mtd; + int ret; + int page; + int flips = 0; + uint8_t *status; + int i; + + if (!mtd) + return -ENODEV; + + chip = mtd_to_nand(mtd); + nand_info = chip->priv; + + nand_select_target(chip, 0); + + page = block * (mtd->erasesize / mtd->writesize); + + mxs_nand_mode_fcb_62bit(nand_info->bch_base); + + nand_read_page_op(chip, page, 0, NULL, 0); + + ret = mxs_nand_do_bch_read(chip, 0, BCH62_PAGESIZE, true, page); + if (ret) + goto out; + + /* Read DMA completed, now do the mark swapping. */ + mxs_nand_swap_block_mark(chip, nand_info->data_buf, nand_info->oob_buf); + + /* Loop over status bytes, accumulating ECC status. */ + status = nand_info->oob_buf + 32; + + for (i = 0; i < 8; i++) { + switch (status[i]) { + case 0x0: + break; + case 0xff: + /* + * A status of 0xff means the chunk is erased, but due to + * the randomizer we see this as random data. Explicitly + * memset it. + */ + memset(nand_info->data_buf + 0x80 * i, 0xff, 0x80); + break; + case 0xfe: + ret = -EBADMSG; + goto out; + default: + flips += status[0]; + break; + } + } + + memcpy(buf, nand_info->data_buf, size); + +out: + mxs_nand_config_bch(chip, mtd->writesize + mtd->oobsize); + nand_deselect_target(chip); + + return ret; +} + +int mxs_nand_write_fcb_bch62(unsigned int block, void *buf, size_t size) +{ + struct nand_chip *chip; + struct mtd_info *mtd = mxs_nand_mtd; + struct mxs_nand_info *nand_info; + struct mxs_dma_cmd *d; + uint32_t channel; + int ret = 0; + int page; + + if (!mtd) + return -ENODEV; + + if (size > BCH62_WRITESIZE) + return -EINVAL; + + chip = mtd_to_nand(mtd); + nand_info = chip->priv; + channel = nand_info->dma_channel_base; + + mxs_nand_mode_fcb_62bit(nand_info->bch_base); + + nand_select_target(chip, 0); + + page = block * (mtd->erasesize / mtd->writesize); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + memset(nand_info->data_buf, 0x0, BCH62_WRITESIZE); + memcpy(nand_info->data_buf, buf, size); + + /* Handle block mark swapping. */ + mxs_nand_swap_block_mark(chip, nand_info->data_buf, nand_info->oob_buf); + + /* Compile the DMA descriptor - write data. */ + d = mxs_nand_get_dma_desc(nand_info); + d->data = MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ | + MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END | + MXS_DMA_DESC_PIO_WORDS(6); + + d->address = 0; + + d->pio_words[0] = GPMI_CTRL0_COMMAND_MODE_WRITE | + GPMI_CTRL0_WORD_LENGTH | + GPMI_CTRL0_ADDRESS_NAND_DATA; + d->pio_words[1] = 0; + d->pio_words[2] = GPMI_ECCCTRL_ENABLE_ECC | + GPMI_ECCCTRL_ECC_CMD_ENCODE | + GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE; + d->pio_words[3] = BCH62_PAGESIZE; + d->pio_words[4] = (dma_addr_t)nand_info->data_buf; + d->pio_words[5] = (dma_addr_t)nand_info->oob_buf; + + d->pio_words[2] |= GPMI_ECCCTRL_RANDOMIZER_ENABLE | + GPMI_ECCCTRL_RANDOMIZER_TYPE2; + /* + * Write NAND page number needed to be randomized + * to GPMI_ECCCOUNT register. + * + * The value is between 0-255. For additional details + * check 9.6.6.4 of i.MX7D Applications Processor reference + */ + d->pio_words[3] |= (page % 256) << 16; + + /* Execute the DMA chain. */ + ret = mxs_dma_go(channel, nand_info->desc, nand_info->desc_index); + if (ret) { + dev_err(nand_info->dev, "MXS NAND: DMA write error: %d\n", ret); + goto out; + } + + ret = mxs_nand_wait_for_bch_complete(nand_info); + if (ret) { + dev_err(nand_info->dev, "MXS NAND: BCH write timeout\n"); + goto out; + } + +out: + mxs_nand_return_dma_descs(nand_info); + + if (!ret) + ret = nand_prog_page_end_op(chip); + + mxs_nand_config_bch(chip, mtd->writesize + mtd->oobsize); + nand_deselect_target(chip); + + return ret; +} + /* * Nominally, the purpose of this function is to look for or create the bad * block table. In fact, since the we call this function at the very end of @@ -1327,20 +1365,13 @@ static int mxs_nand_hw_init(struct mxs_nand_info *info) { void __iomem *gpmi_regs = info->io_base; void __iomem *bch_regs = info->bch_base; - int i = 0, ret; + int ret; u32 val; - info->desc = malloc(sizeof(struct mxs_dma_desc *) * - MXS_NAND_DMA_DESCRIPTOR_COUNT); + info->desc = dma_alloc_coherent(sizeof(struct mxs_dma_cmd) * MXS_NAND_DMA_DESCRIPTOR_COUNT, + DMA_ADDRESS_BROKEN); if (!info->desc) - goto err1; - - /* Allocate the DMA descriptors. */ - for (i = 0; i < MXS_NAND_DMA_DESCRIPTOR_COUNT; i++) { - info->desc[i] = mxs_dma_desc_alloc(); - if (!info->desc[i]) - goto err2; - } + return -ENOMEM; /* Reset the GPMI block. */ ret = stmp_reset_block(gpmi_regs + GPMI_CTRL0, 0); @@ -1367,24 +1398,17 @@ static int mxs_nand_hw_init(struct mxs_nand_info *info) writel(val, gpmi_regs + GPMI_CTRL1); return 0; - -err2: - free(info->desc); -err1: - for (--i; i >= 0; i--) - mxs_dma_desc_free(info->desc[i]); - printf("MXS NAND: Unable to allocate DMA descriptors\n"); - return -ENOMEM; } -static void mxs_nand_probe_dt(struct device_d *dev, struct mxs_nand_info *nand_info) +static void mxs_nand_probe_dt(struct device *dev, + struct mxs_nand_info *nand_info) { struct nand_chip *chip = &nand_info->nand_chip; if (!IS_ENABLED(CONFIG_OFTREE)) return; - if (of_get_nand_on_flash_bbt(dev->device_node)) + if (of_get_nand_on_flash_bbt(dev->of_node)) chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; } @@ -2134,7 +2158,7 @@ static void mxs_nand_setup_timing(struct mxs_nand_info *info) } } -static int mxs_nand_probe(struct device_d *dev) +static int mxs_nand_probe(struct device *dev) { struct resource *iores; struct mxs_nand_info *nand_info; @@ -2276,11 +2300,15 @@ static __maybe_unused struct of_device_id gpmi_dt_ids[] = { .compatible = "fsl,imx6q-gpmi-nand", .data = (void *)GPMI_IMX6, }, { + .compatible = "fsl,imx7d-gpmi-nand", + .data = (void *)GPMI_IMX6, + }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, gpmi_dt_ids); -static struct driver_d mxs_nand_driver = { +static struct driver mxs_nand_driver = { .name = "mxs_nand", .probe = mxs_nand_probe, .of_compatible = DRV_OF_COMPAT(gpmi_dt_ids), diff --git a/drivers/mtd/nand/nand_omap_bch_decoder.c b/drivers/mtd/nand/nand_omap_bch_decoder.c index 4dd28a7704..eb51e608e4 100644 --- a/drivers/mtd/nand/nand_omap_bch_decoder.c +++ b/drivers/mtd/nand/nand_omap_bch_decoder.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/mtd/nand/omap_omap_bch_decoder.c * @@ -8,9 +9,6 @@ * Author: Sukumar Ghorai <s-ghorai@xxxxxx * Michael Fillinger <m-fillinger@xxxxxx> * - * 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 <common.h> diff --git a/drivers/mtd/nand/nand_omap_bch_decoder.h b/drivers/mtd/nand/nand_omap_bch_decoder.h index 74d24be028..a8c71f77f8 100644 --- a/drivers/mtd/nand/nand_omap_bch_decoder.h +++ b/drivers/mtd/nand/nand_omap_bch_decoder.h @@ -1,6 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef MTD_OMAP_GPMC_DECODE_BCH_H #define MTD_OMAP_GPMC_DECODE_BCH_H int omap_gpmc_decode_bch(int select_4_8, unsigned char *ecc, unsigned int *err_loc); -#endif /* MTD_OMAP_GPMC_DECODE_BCH_H */
\ No newline at end of file +#endif /* MTD_OMAP_GPMC_DECODE_BCH_H */ diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 0f3ffa1c0e..ab36183005 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /** * @file * @brief Provide Generic GPMC NAND implementation for OMAP platforms @@ -11,7 +12,7 @@ * A typical device registration is as follows: * * @code - * static struct device_d my_nand_device = { + * static struct device my_nand_device = { * .name = "gpmc_nand", * .id = some identifier you need to show.. e.g. "gpmc_nand0" * .resource[0].start = GPMC base address @@ -53,9 +54,7 @@ * Copyright (c) 2004 Micron Technology Inc. * Copyright (c) 2004 David Brownell * - * 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 <common.h> @@ -69,8 +68,8 @@ #include <linux/mtd/rawnand.h> #include <linux/mtd/nand_ecc.h> #include <io.h> -#include <mach/gpmc.h> -#include <mach/gpmc_nand.h> +#include <mach/omap/gpmc.h> +#include <mach/omap/gpmc_nand.h> #include <platform_data/elm.h> #include "nand_omap_bch_decoder.h" @@ -110,7 +109,7 @@ static const char *ecc_mode_strings[] = { /** internal structure maintained for nand information */ struct gpmc_nand_info { - struct device_d *pdev; + struct device *pdev; struct gpmc_nand_platform_data *pdata; struct nand_chip nand; int gpmc_cs; @@ -1185,7 +1184,7 @@ static int gpmc_set_buswidth(struct nand_chip *chip, int buswidth) * * @return -failure reason or give 0 */ -static int gpmc_nand_probe(struct device_d *pdev) +static int gpmc_nand_probe(struct device *pdev) { struct resource *iores; struct gpmc_nand_info *oinfo; @@ -1344,7 +1343,7 @@ out_release_mem: } /** GMPC nand driver -> device registered by platforms */ -static struct driver_d gpmc_nand_driver = { +static struct driver gpmc_nand_driver = { .name = "gpmc_nand", .probe = gpmc_nand_probe, }; diff --git a/drivers/mtd/nand/nand_onfi.c b/drivers/mtd/nand/nand_onfi.c index 5f4c5c3437..5dd29ba6ba 100644 --- a/drivers/mtd/nand/nand_onfi.c +++ b/drivers/mtd/nand/nand_onfi.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) * 2002-2006 Thomas Gleixner (tglx@linutronix.de) @@ -95,8 +95,8 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip, goto ext_out; } - chip->base.eccreq.strength = ecc->ecc_bits; - chip->base.eccreq.step_size = 1 << ecc->codeword_size; + chip->base.ecc.requirements.strength = ecc->ecc_bits; + chip->base.ecc.requirements.step_size = 1 << ecc->codeword_size; ret = 0; ext_out: @@ -266,8 +266,8 @@ int nand_onfi_detect(struct nand_chip *chip) chip->options |= NAND_BUSWIDTH_16; if (p->ecc_bits != 0xff) { - chip->base.eccreq.strength = p->ecc_bits; - chip->base.eccreq.step_size = 512; + chip->base.ecc.requirements.strength = p->ecc_bits; + chip->base.ecc.requirements.step_size = 512; } else if (onfi_version >= 21 && (le16_to_cpu(p->features) & ONFI_FEATURE_EXT_PARAM_PAGE)) { diff --git a/drivers/mtd/nand/nand_orion.c b/drivers/mtd/nand/nand_orion.c index 3899a67b56..ff3642939a 100644 --- a/drivers/mtd/nand/nand_orion.c +++ b/drivers/mtd/nand/nand_orion.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * (C) Copyright 2014, Ezequiel Garcia <ezequiel.garcia@free-electrons.com> * * Based on Orion NAND driver from Linux (drivers/mtd/nand/orion_nand.c): * Author: 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 <common.h> @@ -76,10 +73,10 @@ static void orion_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int len) buf[i++] = readb(io_base); } -static int orion_nand_probe(struct device_d *dev) +static int orion_nand_probe(struct device *dev) { struct resource *iores; - struct device_node *dev_node = dev->device_node; + struct device_node *dev_node = dev->of_node; struct orion_nand *priv; struct mtd_info *mtd; struct nand_chip *chip; @@ -150,8 +147,9 @@ static __maybe_unused struct of_device_id orion_nand_compatible[] = { { .compatible = "marvell,orion-nand", }, {}, }; +MODULE_DEVICE_TABLE(of, orion_nand_compatible); -static struct driver_d orion_nand_driver = { +static struct driver orion_nand_driver = { .name = "orion_nand", .probe = orion_nand_probe, .of_compatible = DRV_OF_COMPAT(orion_nand_compatible), diff --git a/drivers/mtd/nand/nand_s3c24xx.c b/drivers/mtd/nand/nand_s3c24xx.c deleted file mode 100644 index f1d1441f50..0000000000 --- a/drivers/mtd/nand/nand_s3c24xx.c +++ /dev/null @@ -1,660 +0,0 @@ -/* linux/drivers/mtd/nand/s3c2410.c - * - * Copyright (C) 2009 Juergen Beisert, Pengutronix - * - * Copyright © 2004-2008 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * Ben Dooks <ben@simtec.co.uk> - * - * Samsung S3C2410 NAND 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. - * - * 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 <config.h> -#include <common.h> -#include <driver.h> -#include <malloc.h> -#include <init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/rawnand.h> -#include <linux/mtd/nand.h> -#include <mach/s3c-generic.h> -#include <mach/s3c-iomap.h> -#include <mach/s3c24xx-nand.h> -#include <io.h> -#include <errno.h> -#include <asm/sections.h> - -#ifdef CONFIG_S3C_NAND_BOOT -# define __nand_boot_init __bare_init -# ifndef BOARD_DEFAULT_NAND_TIMING -# define BOARD_DEFAULT_NAND_TIMING 0x0737 -# endif -#else -# define __nand_boot_init -#endif - -/** - * Define this symbol for testing purpose. It will add a command to read an - * image from the NAND like it the boot strap code will do. - */ -#define CONFIG_NAND_S3C_BOOT_DEBUG - -/* NAND controller's register */ - -#define NFCONF 0x00 - -#ifdef CONFIG_CPU_S3C2410 - -#define NFCMD 0x04 -#define NFADDR 0x08 -#define NFDATA 0x0c -#define NFSTAT 0x10 -#define NFECC 0x14 - -/* S3C2410 specific bits */ -#define NFSTAT_BUSY (1) -#define NFCONF_nFCE (1 << 11) -#define NFCONF_INITECC (1 << 12) -#define NFCONF_EN (1 << 15) - -#endif /* CONFIG_CPU_S3C2410 */ - -#ifdef CONFIG_CPU_S3C2440 - -#define NFCONT 0x04 -#define NFCMD 0x08 -#define NFADDR 0x0C -#define NFDATA 0x10 -#define NFSTAT 0x20 -#define NFECC 0x2C - -/* S3C2440 specific bits */ -#define NFSTAT_BUSY (1) -#define NFCONT_nFCE (1 << 1) -#define NFCONT_INITECC (1 << 4) -#define NFCONT_EN (1) - -#endif /* CONFIG_CPU_S3C2440 */ - - -struct s3c24x0_nand_host { - struct nand_chip nand; - struct mtd_partition *parts; - struct device_d *dev; - - void __iomem *base; -}; - -/** - * oob placement block for use with hardware ecc generation on small page - */ -static struct nand_ecclayout nand_hw_eccoob = { - .eccbytes = 3, - .eccpos = { 0, 1, 2}, - .oobfree = { - { - .offset = 8, - .length = 8 - } - } -}; - -/* - Functions shared between the boot strap code and the regular driver - */ - -/** - * Issue the specified command to the NAND device - * @param[in] host Base address of the NAND controller - * @param[in] cmd Command for NAND flash - */ -static void __nand_boot_init send_cmd(void __iomem *host, uint8_t cmd) -{ - writeb(cmd, host + NFCMD); -} - -/** - * Issue the specified address to the NAND device - * @param[in] host Base address of the NAND controller - * @param[in] addr Address for the NAND flash - */ -static void __nand_boot_init send_addr(void __iomem *host, uint8_t addr) -{ - writeb(addr, host + NFADDR); -} - -/** - * Enable the NAND flash access - * @param[in] host Base address of the NAND controller - */ -static void __nand_boot_init enable_cs(void __iomem *host) -{ -#ifdef CONFIG_CPU_S3C2410 - writew(readw(host + NFCONF) & ~NFCONF_nFCE, host + NFCONF); -#endif -#ifdef CONFIG_CPU_S3C2440 - writew(readw(host + NFCONT) & ~NFCONT_nFCE, host + NFCONT); -#endif -} - -/** - * Disable the NAND flash access - * @param[in] host Base address of the NAND controller - */ -static void __nand_boot_init disable_cs(void __iomem *host) -{ -#ifdef CONFIG_CPU_S3C2410 - writew(readw(host + NFCONF) | NFCONF_nFCE, host + NFCONF); -#endif -#ifdef CONFIG_CPU_S3C2440 - writew(readw(host + NFCONT) | NFCONT_nFCE, host + NFCONT); -#endif -} - -/** - * Enable the NAND flash controller - * @param[in] host Base address of the NAND controller - * @param[in] timing Timing to access the NAND memory - */ -static void __nand_boot_init enable_nand_controller(void __iomem *host, uint32_t timing) -{ -#ifdef CONFIG_CPU_S3C2410 - writew(timing + NFCONF_EN + NFCONF_nFCE, host + NFCONF); -#endif -#ifdef CONFIG_CPU_S3C2440 - writew(NFCONT_EN + NFCONT_nFCE, host + NFCONT); - writew(timing, host + NFCONF); -#endif -} - -/** - * Diable the NAND flash controller - * @param[in] host Base address of the NAND controller - */ -static void __nand_boot_init disable_nand_controller(void __iomem *host) -{ -#ifdef CONFIG_CPU_S3C2410 - writew(NFCONF_nFCE, host + NFCONF); -#endif -#ifdef CONFIG_CPU_S3C2440 - writew(NFCONT_nFCE, host + NFCONT); -#endif -} - -/* ----------------------------------------------------------------------- */ - -#ifdef CONFIG_CPU_S3C2440 -/** - * Read one block of data from the NAND port - * @param[in] mtd Instance data - * @param[out] buf buffer to write data to - * @param[in] len byte count - * - * This is a special block read variant for the S3C2440 CPU. - */ -static void s3c2440_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int len) -{ - struct s3c24x0_nand_host *host = chip->priv; - - readsl(host->base + NFDATA, buf, len >> 2); - - /* cleanup any fractional read */ - if (len & 3) { - buf += len & ~3; - - for (; len & 3; len--) - *buf++ = readb(host->base + NFDATA); - } -} - -/** - * Write one block of data to the NAND port - * @param[in] mtd Instance data - * @param[out] buf buffer to read data from - * @param[in] len byte count - * - * This is a special block write variant for the S3C2440 CPU. - */ -static void s3c2440_nand_write_buf(struct nand_chip *chip, const uint8_t *buf, - int len) -{ - struct s3c24x0_nand_host *host = chip->priv; - - writesl(host->base + NFDATA, buf, len >> 2); - - /* cleanup any fractional write */ - if (len & 3) { - buf += len & ~3; - - for (; len & 3; len--, buf++) - writeb(*buf, host->base + NFDATA); - } -} -#endif - -/** - * Check the ECC and try to repair the data if possible - * @param[in] mtd_info Not used - * @param[inout] dat Pointer to the data buffer that might contain a bit error - * @param[in] read_ecc ECC data from the OOB space - * @param[in] calc_ecc ECC data calculated from the data - * @return 0 no error, 1 repaired error, -1 no way... - * - * @note: This routine works always on a 24 bit ECC - */ -static int s3c2410_nand_correct_data(struct nand_chip *chip, uint8_t *dat, - uint8_t *read_ecc, uint8_t *calc_ecc) -{ - unsigned int diff0, diff1, diff2; - unsigned int bit, byte; - - diff0 = read_ecc[0] ^ calc_ecc[0]; - diff1 = read_ecc[1] ^ calc_ecc[1]; - diff2 = read_ecc[2] ^ calc_ecc[2]; - - if (diff0 == 0 && diff1 == 0 && diff2 == 0) - return 0; /* ECC is ok */ - - /* sometimes people do not think about using the ECC, so check - * to see if we have an 0xff,0xff,0xff read ECC and then ignore - * the error, on the assumption that this is an un-eccd page. - */ - if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff) - return 0; - - /* Can we correct this ECC (ie, one row and column change). - * Note, this is similar to the 256 error code on smartmedia */ - - if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 && - ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 && - ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) { - /* calculate the bit position of the error */ - - bit = ((diff2 >> 3) & 1) | - ((diff2 >> 4) & 2) | - ((diff2 >> 5) & 4); - - /* calculate the byte position of the error */ - - byte = ((diff2 << 7) & 0x100) | - ((diff1 << 0) & 0x80) | - ((diff1 << 1) & 0x40) | - ((diff1 << 2) & 0x20) | - ((diff1 << 3) & 0x10) | - ((diff0 >> 4) & 0x08) | - ((diff0 >> 3) & 0x04) | - ((diff0 >> 2) & 0x02) | - ((diff0 >> 1) & 0x01); - - dat[byte] ^= (1 << bit); - return 1; - } - - /* if there is only one bit difference in the ECC, then - * one of only a row or column parity has changed, which - * means the error is most probably in the ECC itself */ - - diff0 |= (diff1 << 8); - diff0 |= (diff2 << 16); - - if ((diff0 & ~(1<<fls(diff0))) == 0) - return 1; - - return -1; -} - -static void s3c2410_nand_enable_hwecc(struct nand_chip *chip, int mode) -{ - struct s3c24x0_nand_host *host = chip->priv; - -#ifdef CONFIG_CPU_S3C2410 - writel(readl(host->base + NFCONF) | NFCONF_INITECC , host->base + NFCONF); -#endif -#ifdef CONFIG_CPU_S3C2440 - writel(readl(host->base + NFCONT) | NFCONT_INITECC , host->base + NFCONT); -#endif -} - -static int s3c2410_nand_calculate_ecc(struct nand_chip *chip, const uint8_t *dat, uint8_t *ecc_code) -{ - struct s3c24x0_nand_host *host = chip->priv; - -#ifdef CONFIG_CPU_S3C2410 - ecc_code[0] = readb(host->base + NFECC); - ecc_code[1] = readb(host->base + NFECC + 1); - ecc_code[2] = readb(host->base + NFECC + 2); -#endif -#ifdef CONFIG_CPU_S3C2440 - unsigned long ecc = readl(host->base + NFECC); - - ecc_code[0] = ecc; - ecc_code[1] = ecc >> 8; - ecc_code[2] = ecc >> 16; -#endif - return 0; -} - -static void s3c24x0_nand_select_chip(struct nand_chip *chip, int num) -{ - struct s3c24x0_nand_host *host = chip->priv; - - if (num == -1) - disable_cs(host->base); - else - enable_cs(host->base); -} - -static int s3c24x0_nand_devready(struct nand_chip *chip) -{ - struct s3c24x0_nand_host *host = chip->priv; - - return readw(host->base + NFSTAT) & NFSTAT_BUSY; -} - -static void s3c24x0_nand_hwcontrol(struct nand_chip *chip, int cmd, - unsigned int ctrl) -{ - struct s3c24x0_nand_host *host = chip->priv; - - if (cmd == NAND_CMD_NONE) - return; - /* - * If the CLE should be active, this call is a NAND command - */ - if (ctrl & NAND_CLE) - send_cmd(host->base, cmd); - /* - * If the ALE should be active, this call is a NAND address - */ - if (ctrl & NAND_ALE) - send_addr(host->base, cmd); -} - -static int s3c24x0_nand_inithw(struct s3c24x0_nand_host *host) -{ - struct s3c24x0_nand_platform_data *pdata = host->dev->platform_data; - uint32_t tmp; - - /* reset the NAND controller */ - disable_nand_controller(host->base); - - if (pdata != NULL) - tmp = pdata->nand_timing; - else - /* else slowest possible timing */ - tmp = CALC_NFCONF_TIMING(4, 8, 8); - - /* reenable the NAND controller */ - enable_nand_controller(host->base, tmp); - - return 0; -} - -static int s3c24x0_nand_probe(struct device_d *dev) -{ - struct resource *iores; - struct nand_chip *chip; - struct s3c24x0_nand_platform_data *pdata = dev->platform_data; - struct mtd_info *mtd; - struct s3c24x0_nand_host *host; - int ret; - - /* Allocate memory for MTD device structure and private data */ - host = kzalloc(sizeof(struct s3c24x0_nand_host), GFP_KERNEL); - if (!host) - return -ENOMEM; - - host->dev = dev; - iores = dev_request_mem_resource(dev, 0); - if (IS_ERR(iores)) - return PTR_ERR(iores); - host->base = IOMEM(iores->start); - - /* structures must be linked */ - chip = &host->nand; - mtd = nand_to_mtd(chip); - mtd->dev.parent = dev; - - /* init the default settings */ - - /* 50 us command delay time */ - chip->legacy.chip_delay = 50; - chip->priv = host; - - chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W = host->base + NFDATA; - -#ifdef CONFIG_CPU_S3C2440 - chip->legacy.read_buf = s3c2440_nand_read_buf; - chip->legacy.write_buf = s3c2440_nand_write_buf; -#endif - chip->legacy.cmd_ctrl = s3c24x0_nand_hwcontrol; - chip->legacy.dev_ready = s3c24x0_nand_devready; - chip->legacy.select_chip = s3c24x0_nand_select_chip; - - /* we are using the hardware ECC feature of this device */ - chip->ecc.calculate = s3c2410_nand_calculate_ecc; - chip->ecc.correct = s3c2410_nand_correct_data; - chip->ecc.hwctl = s3c2410_nand_enable_hwecc; - - /* - * Setup ECC handling in accordance to the kernel - * - 1 times 512 bytes with 24 bit ECC for small page - * - 8 times 256 bytes with 24 bit ECC each for large page - */ - chip->ecc.mode = NAND_ECC_HW; - chip->ecc.bytes = 3; /* always 24 bit ECC per turn */ - chip->ecc.strength = 1; - -#ifdef CONFIG_CPU_S3C2440 - if (readl(host->base) & 0x8) { - /* large page (2048 bytes per page) */ - chip->ecc.size = 256; - } else -#endif - { - /* small page (512 bytes per page) */ - chip->ecc.size = 512; - mtd_set_ecclayout(mtd, &nand_hw_eccoob); - } - - if (pdata->flash_bbt) { - /* use a flash based bbt */ - chip->bbt_options |= NAND_BBT_USE_FLASH; - } - - ret = s3c24x0_nand_inithw(host); - if (ret != 0) - goto on_error; - - /* Scan to find existence of the device */ - ret = nand_scan(chip, 1); - if (ret != 0) { - ret = -ENXIO; - goto on_error; - } - - return add_mtd_nand_device(mtd, "nand"); - -on_error: - free(host); - return ret; -} - -static struct driver_d s3c24x0_nand_driver = { - .name = "s3c24x0_nand", - .probe = s3c24x0_nand_probe, -}; -device_platform_driver(s3c24x0_nand_driver); - -#ifdef CONFIG_S3C_NAND_BOOT - -static void __nand_boot_init wait_for_completion(void __iomem *host) -{ - while (!(readw(host + NFSTAT) & NFSTAT_BUSY)) - ; -} - -/** - * Convert a page offset into a page address for the NAND - * @param host Where to write the address to - * @param offs Page's offset in the NAND - * @param ps Page size (512 or 2048) - * @param c Address cycle count (3, 4 or 5) - * - * Uses the offset of the page to generate an page address into the NAND. This - * differs when using a 512 byte or 2048 bytes per page NAND. - * The column part of the page address to be generated is always forced to '0'. - */ -static void __nand_boot_init nfc_addr(void __iomem *host, uint32_t offs, - int ps, int c) -{ - send_addr(host, 0); /* column part 1 */ - - if (ps == 512) { - send_addr(host, offs >> 9); - send_addr(host, offs >> 17); - if (c > 3) - send_addr(host, offs >> 25); - } else { - send_addr(host, 0); /* column part 2 */ - send_addr(host, offs >> 11); - send_addr(host, offs >> 19); - if (c > 4) - send_addr(host, offs >> 27); - send_cmd(host, NAND_CMD_READSTART); - } -} - -/** - * Load a sequential count of pages from the NAND into memory - * @param[out] dest Pointer to target area (in SDRAM) - * @param[in] size Bytes to read from NAND device - * @param[in] page Start page to read from - * - * This function must be located in the first 4kiB of the barebox image - * (guess why). - */ -void __nand_boot_init s3c24x0_nand_load_image(void *dest, int size, int page) -{ - void __iomem *host = (void __iomem *)S3C24X0_NAND_BASE; - unsigned pagesize; - int i, cycle; - - /* - * Reenable the NFC and use the default (but slow) access - * timing or the board specific setting if provided. - */ - enable_nand_controller(host, BOARD_DEFAULT_NAND_TIMING); - - /* use the current NAND hardware configuration */ - switch (readl(S3C24X0_NAND_BASE) & 0xf) { - case 0x6: /* 8 bit, 4 addr cycles, 512 bpp, normal NAND */ - pagesize = 512; - cycle = 4; - break; - case 0xc: /* 8 bit, 4 addr cycles, 2048 bpp, advanced NAND */ - pagesize = 2048; - cycle = 4; - break; - case 0xe: /* 8 bit, 5 addr cycles, 2048 bpp, advanced NAND */ - pagesize = 2048; - cycle = 5; - break; - default: - /* we cannot output an error message here :-( */ - disable_nand_controller(host); - return; - } - - enable_cs(host); - - /* Reset the NAND device */ - send_cmd(host, NAND_CMD_RESET); - wait_for_completion(host); - disable_cs(host); - - do { - enable_cs(host); - send_cmd(host, NAND_CMD_READ0); - nfc_addr(host, page * pagesize, pagesize, cycle); - wait_for_completion(host); - /* copy one page (do *not* use readsb() here!)*/ - for (i = 0; i < pagesize; i++) - writeb(readb(host + NFDATA), (void __iomem *)(dest + i)); - disable_cs(host); - - page++; - dest += pagesize; - size -= pagesize; - } while (size >= 0); - - /* disable the controller again */ - disable_nand_controller(host); -} - -#include <asm/sections.h> - -void __nand_boot_init nand_boot(void) -{ - void *dest = _text; - int size = barebox_image_size; - int page = 0; - - s3c24x0_nand_load_image(dest, size, page); -} -#ifdef CONFIG_NAND_S3C_BOOT_DEBUG -#include <command.h> - -static int do_nand_boot_test(int argc, char *argv[]) -{ - void *dest; - int size; - - if (argc < 3) - return COMMAND_ERROR_USAGE; - - dest = (void *)strtoul_suffix(argv[1], NULL, 0); - size = strtoul_suffix(argv[2], NULL, 0); - - s3c24x0_nand_load_image(dest, size, 0); - - /* re-enable the controller again, as this was a test only */ - enable_nand_controller((void *)S3C24X0_NAND_BASE, - BOARD_DEFAULT_NAND_TIMING); - - return 0; -} - -BAREBOX_CMD_START(nand_boot_test) - .cmd = do_nand_boot_test, - BAREBOX_CMD_DESC("load an image from NAND") - BAREBOX_CMD_OPTS("DEST SIZE") - BAREBOX_CMD_GROUP(CMD_GRP_BOOT) -BAREBOX_CMD_END -#endif - -#endif /* CONFIG_S3C_NAND_BOOT */ - -/** - * @file - * @brief Support for various kinds of NAND devices - * - * ECC handling in this driver (in accordance to the current 2.6.38 kernel): - * - for small page NANDs it generates 3 ECC bytes out of 512 data bytes - * - for large page NANDs it generates 24 ECC bytes out of 2048 data bytes - * - * As small page NANDs are using 48 bits ECC per default, this driver uses a - * local OOB layout description, to shrink it down to 24 bits. This is a bad - * idea, but we cannot change it here, as the kernel is using this layout. - * - * For large page NANDs this driver uses the default layout, as the kernel does. - */ diff --git a/drivers/mtd/nand/nand_samsung.c b/drivers/mtd/nand/nand_samsung.c index 3a4a19e808..ee993af1e5 100644 --- a/drivers/mtd/nand/nand_samsung.c +++ b/drivers/mtd/nand/nand_samsung.c @@ -71,23 +71,23 @@ static void samsung_nand_decode_id(struct nand_chip *chip) /* Extract ECC requirements from 5th id byte*/ extid = (chip->id.data[4] >> 4) & 0x07; if (extid < 5) { - chip->base.eccreq.step_size = 512; - chip->base.eccreq.strength = 1 << extid; + chip->base.ecc.requirements.step_size = 512; + chip->base.ecc.requirements.strength = 1 << extid; } else { - chip->base.eccreq.step_size = 1024; + chip->base.ecc.requirements.step_size = 1024; switch (extid) { case 5: - chip->base.eccreq.strength = 24; + chip->base.ecc.requirements.strength = 24; break; case 6: - chip->base.eccreq.strength = 40; + chip->base.ecc.requirements.strength = 40; break; case 7: - chip->base.eccreq.strength = 60; + chip->base.ecc.requirements.strength = 60; break; default: WARN(1, "Could not decode ECC info"); - chip->base.eccreq.step_size = 0; + chip->base.ecc.requirements.step_size = 0; } } } else { @@ -97,8 +97,8 @@ static void samsung_nand_decode_id(struct nand_chip *chip) switch (chip->id.data[1]) { /* K9F4G08U0D-S[I|C]B0(T00) */ case 0xDC: - chip->base.eccreq.step_size = 512; - chip->base.eccreq.strength = 1; + chip->base.ecc.requirements.step_size = 512; + chip->base.ecc.requirements.strength = 1; break; /* K9F1G08U0E 21nm chips do not support subpage write */ diff --git a/drivers/mtd/nand/nand_toshiba.c b/drivers/mtd/nand/nand_toshiba.c index 21a5dbc7e0..3fe0347bfd 100644 --- a/drivers/mtd/nand/nand_toshiba.c +++ b/drivers/mtd/nand/nand_toshiba.c @@ -140,7 +140,7 @@ static void toshiba_nand_benand_init(struct nand_chip *chip) chip->options |= NAND_SUBPAGE_READ; - mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops); + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); } static void toshiba_nand_decode_id(struct nand_chip *chip) @@ -175,20 +175,20 @@ static void toshiba_nand_decode_id(struct nand_chip *chip) * - 24nm: 8 bit ECC for each 512Byte is required. */ if (chip->id.len >= 6 && nand_is_slc(chip)) { - chip->base.eccreq.step_size = 512; + chip->base.ecc.requirements.step_size = 512; switch (chip->id.data[5] & 0x7) { case 0x4: - chip->base.eccreq.strength = 1; + chip->base.ecc.requirements.strength = 1; break; case 0x5: - chip->base.eccreq.strength = 4; + chip->base.ecc.requirements.strength = 4; break; case 0x6: - chip->base.eccreq.strength = 8; + chip->base.ecc.requirements.strength = 8; break; default: WARN(1, "Could not get ECC info"); - chip->base.eccreq.step_size = 0; + chip->base.ecc.requirements.step_size = 0; break; } } diff --git a/drivers/mtd/nand/nomadik_nand.c b/drivers/mtd/nand/nomadik_nand.c index b5ef39223e..940ed9809e 100644 --- a/drivers/mtd/nand/nomadik_nand.c +++ b/drivers/mtd/nand/nomadik_nand.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * drivers/mtd/nand/nomadik_nand.c * @@ -9,16 +10,6 @@ * * Copyright (C) 2009 Alessandro Rubini * - * 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. - * */ #include <common.h> @@ -33,8 +24,8 @@ #include <linux/mtd/rawnand.h> #include <io.h> -#include <mach/nand.h> -#include <mach/fsmc.h> +#include <mach/nomadik/nand.h> +#include <mach/nomadik/fsmc.h> #include <errno.h> @@ -167,7 +158,7 @@ static void nomadik_cmd_ctrl(struct nand_chip *nand, int cmd, unsigned int ctrl) writeb(cmd, host->addr_va); } -static int nomadik_nand_probe(struct device_d *dev) +static int nomadik_nand_probe(struct device *dev) { struct nomadik_nand_platform_data *pdata = dev->platform_data; struct nomadik_nand_host *host; @@ -236,7 +227,7 @@ err: return ret; } -static struct driver_d nomadik_nand_driver = { +static struct driver nomadik_nand_driver = { .probe = nomadik_nand_probe, .name = "nomadik_nand", }; diff --git a/drivers/mtd/nand/omap_elm.c b/drivers/mtd/nand/omap_elm.c index 583235fc78..da731e44f3 100644 --- a/drivers/mtd/nand/omap_elm.c +++ b/drivers/mtd/nand/omap_elm.c @@ -66,7 +66,7 @@ struct elm_registers { }; struct elm_info { - struct device_d *dev; + struct device *dev; void __iomem *elm_base; struct list_head list; enum bch_ecc bch_type; @@ -376,7 +376,7 @@ int elm_decode_bch_error_page(u8 *ecc_calc, struct elm_errorvec *err_vec) return 0; } -static int elm_probe(struct device_d *dev) +static int elm_probe(struct device *dev) { struct resource *res; struct elm_info *info; @@ -404,8 +404,9 @@ static struct of_device_id elm_compatible[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, elm_compatible); -static struct driver_d omap_elm_driver = { +static struct driver omap_elm_driver = { .name = "omap-elm", .probe = elm_probe, .of_compatible = DRV_OF_COMPAT(elm_compatible) diff --git a/drivers/mtd/nand/stm32_fmc2_nand.c b/drivers/mtd/nand/stm32_fmc2_nand.c new file mode 100644 index 0000000000..47b012cc9e --- /dev/null +++ b/drivers/mtd/nand/stm32_fmc2_nand.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2018 + * Author: Christophe Kerello <christophe.kerello@st.com> + */ + +#include <common.h> +#include <init.h> +#include <of_address.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/iopoll.h> +#include <linux/reset.h> +#include <mfd/syscon.h> + +#include "internals.h" + +/* Bad block marker length */ +#define FMC2_BBM_LEN 2 + +/* ECC step size */ +#define FMC2_ECC_STEP_SIZE 512 + +/* Max requests done for a 8k nand page size */ +#define FMC2_MAX_SG 16 + +/* Max chip enable */ +#define FMC2_MAX_CE 2 + +#define FMC2_TIMEOUT_MS 5000 + +/* Timings */ +#define FMC2_THIZ 1 +#define FMC2_TIO 8000 +#define FMC2_TSYNC 3000 +#define FMC2_PCR_TIMING_MASK 0xf +#define FMC2_PMEM_PATT_TIMING_MASK 0xff + +/* FMC2 Controller Registers */ +#define FMC2_BCR1 0x0 +#define FMC2_PCR 0x80 +#define FMC2_SR 0x84 +#define FMC2_PMEM 0x88 +#define FMC2_PATT 0x8c +#define FMC2_HECCR 0x94 +#define FMC2_ISR 0x184 +#define FMC2_ICR 0x188 +#define FMC2_CSQCR 0x200 +#define FMC2_CSQCFGR1 0x204 +#define FMC2_CSQCFGR2 0x208 +#define FMC2_CSQCFGR3 0x20c +#define FMC2_CSQAR1 0x210 +#define FMC2_CSQAR2 0x214 +#define FMC2_CSQIER 0x220 +#define FMC2_CSQISR 0x224 +#define FMC2_CSQICR 0x228 +#define FMC2_CSQEMSR 0x230 +#define FMC2_BCHIER 0x250 +#define FMC2_BCHISR 0x254 +#define FMC2_BCHICR 0x258 +#define FMC2_BCHPBR1 0x260 +#define FMC2_BCHPBR2 0x264 +#define FMC2_BCHPBR3 0x268 +#define FMC2_BCHPBR4 0x26c +#define FMC2_BCHDSR0 0x27c +#define FMC2_BCHDSR1 0x280 +#define FMC2_BCHDSR2 0x284 +#define FMC2_BCHDSR3 0x288 +#define FMC2_BCHDSR4 0x28c + +/* Register: FMC2_BCR1 */ +#define FMC2_BCR1_FMC2EN BIT(31) + +/* Register: FMC2_PCR */ +#define FMC2_PCR_PWAITEN BIT(1) +#define FMC2_PCR_PBKEN BIT(2) +#define FMC2_PCR_PWID GENMASK(5, 4) +#define FMC2_PCR_PWID_BUSWIDTH_8 0 +#define FMC2_PCR_PWID_BUSWIDTH_16 1 +#define FMC2_PCR_ECCEN BIT(6) +#define FMC2_PCR_ECCALG BIT(8) +#define FMC2_PCR_TCLR GENMASK(12, 9) +#define FMC2_PCR_TCLR_DEFAULT 0xf +#define FMC2_PCR_TAR GENMASK(16, 13) +#define FMC2_PCR_TAR_DEFAULT 0xf +#define FMC2_PCR_ECCSS GENMASK(19, 17) +#define FMC2_PCR_ECCSS_512 1 +#define FMC2_PCR_ECCSS_2048 3 +#define FMC2_PCR_BCHECC BIT(24) +#define FMC2_PCR_WEN BIT(25) + +/* Register: FMC2_SR */ +#define FMC2_SR_NWRF BIT(6) + +/* Register: FMC2_PMEM */ +#define FMC2_PMEM_MEMSET GENMASK(7, 0) +#define FMC2_PMEM_MEMWAIT GENMASK(15, 8) +#define FMC2_PMEM_MEMHOLD GENMASK(23, 16) +#define FMC2_PMEM_MEMHIZ GENMASK(31, 24) +#define FMC2_PMEM_DEFAULT 0x0a0a0a0a + +/* Register: FMC2_PATT */ +#define FMC2_PATT_ATTSET GENMASK(7, 0) +#define FMC2_PATT_ATTWAIT GENMASK(15, 8) +#define FMC2_PATT_ATTHOLD GENMASK(23, 16) +#define FMC2_PATT_ATTHIZ GENMASK(31, 24) +#define FMC2_PATT_DEFAULT 0x0a0a0a0a + +/* Register: FMC2_ISR */ +#define FMC2_ISR_IHLF BIT(1) + +/* Register: FMC2_BCHISR */ +#define FMC2_BCHISR_DERF BIT(1) +#define FMC2_BCHISR_EPBRF BIT(4) + +/* Register: FMC2_ICR */ +#define FMC2_ICR_CIHLF BIT(1) + +/* Register: FMC2_CSQCR */ +#define FMC2_CSQCR_CSQSTART BIT(0) + +/* Register: FMC2_CSQCFGR1 */ +#define FMC2_CSQCFGR1_CMD2EN BIT(1) +#define FMC2_CSQCFGR1_DMADEN BIT(2) +#define FMC2_CSQCFGR1_ACYNBR GENMASK(6, 4) +#define FMC2_CSQCFGR1_CMD1 GENMASK(15, 8) +#define FMC2_CSQCFGR1_CMD2 GENMASK(23, 16) +#define FMC2_CSQCFGR1_CMD1T BIT(24) +#define FMC2_CSQCFGR1_CMD2T BIT(25) + +/* Register: FMC2_CSQCFGR2 */ +#define FMC2_CSQCFGR2_SQSDTEN BIT(0) +#define FMC2_CSQCFGR2_RCMD2EN BIT(1) +#define FMC2_CSQCFGR2_DMASEN BIT(2) +#define FMC2_CSQCFGR2_RCMD1 GENMASK(15, 8) +#define FMC2_CSQCFGR2_RCMD2 GENMASK(23, 16) +#define FMC2_CSQCFGR2_RCMD1T BIT(24) +#define FMC2_CSQCFGR2_RCMD2T BIT(25) + +/* Register: FMC2_CSQCFGR3 */ +#define FMC2_CSQCFGR3_SNBR GENMASK(13, 8) +#define FMC2_CSQCFGR3_AC1T BIT(16) +#define FMC2_CSQCFGR3_AC2T BIT(17) +#define FMC2_CSQCFGR3_AC3T BIT(18) +#define FMC2_CSQCFGR3_AC4T BIT(19) +#define FMC2_CSQCFGR3_AC5T BIT(20) +#define FMC2_CSQCFGR3_SDT BIT(21) +#define FMC2_CSQCFGR3_RAC1T BIT(22) +#define FMC2_CSQCFGR3_RAC2T BIT(23) + +/* Register: FMC2_CSQCAR1 */ +#define FMC2_CSQCAR1_ADDC1 GENMASK(7, 0) +#define FMC2_CSQCAR1_ADDC2 GENMASK(15, 8) +#define FMC2_CSQCAR1_ADDC3 GENMASK(23, 16) +#define FMC2_CSQCAR1_ADDC4 GENMASK(31, 24) + +/* Register: FMC2_CSQCAR2 */ +#define FMC2_CSQCAR2_ADDC5 GENMASK(7, 0) +#define FMC2_CSQCAR2_NANDCEN GENMASK(11, 10) +#define FMC2_CSQCAR2_SAO GENMASK(31, 16) + +/* Register: FMC2_CSQIER */ +#define FMC2_CSQIER_TCIE BIT(0) + +/* Register: FMC2_CSQICR */ +#define FMC2_CSQICR_CLEAR_IRQ GENMASK(4, 0) + +/* Register: FMC2_CSQEMSR */ +#define FMC2_CSQEMSR_SEM GENMASK(15, 0) + +/* Register: FMC2_BCHIER */ +#define FMC2_BCHIER_DERIE BIT(1) +#define FMC2_BCHIER_EPBRIE BIT(4) + +/* Register: FMC2_BCHICR */ +#define FMC2_BCHICR_CLEAR_IRQ GENMASK(4, 0) + +/* Register: FMC2_BCHDSR0 */ +#define FMC2_BCHDSR0_DUE BIT(0) +#define FMC2_BCHDSR0_DEF BIT(1) +#define FMC2_BCHDSR0_DEN GENMASK(7, 4) + +/* Register: FMC2_BCHDSR1 */ +#define FMC2_BCHDSR1_EBP1 GENMASK(12, 0) +#define FMC2_BCHDSR1_EBP2 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR2 */ +#define FMC2_BCHDSR2_EBP3 GENMASK(12, 0) +#define FMC2_BCHDSR2_EBP4 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR3 */ +#define FMC2_BCHDSR3_EBP5 GENMASK(12, 0) +#define FMC2_BCHDSR3_EBP6 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR4 */ +#define FMC2_BCHDSR4_EBP7 GENMASK(12, 0) +#define FMC2_BCHDSR4_EBP8 GENMASK(28, 16) + +enum stm32_fmc2_ecc { + FMC2_ECC_HAM = 1, + FMC2_ECC_BCH4 = 4, + FMC2_ECC_BCH8 = 8 +}; + +struct stm32_fmc2_timings { + u8 tclr; + u8 tar; + u8 thiz; + u8 twait; + u8 thold_mem; + u8 tset_mem; + u8 thold_att; + u8 tset_att; +}; + +struct stm32_fmc2_nand { + struct nand_chip chip; + struct gpio_desc *wp_gpio; + struct stm32_fmc2_timings timings; + int ncs; + int cs_used[FMC2_MAX_CE]; +}; + +static inline struct stm32_fmc2_nand *to_fmc2_nand(struct nand_chip *chip) +{ + return container_of(chip, struct stm32_fmc2_nand, chip); +} + +struct stm32_fmc2_nfc { + struct nand_controller base; + struct stm32_fmc2_nand nand; + struct device *dev; + struct device *cdev; + struct regmap *regmap; + void __iomem *data_base[FMC2_MAX_CE]; + void __iomem *cmd_base[FMC2_MAX_CE]; + void __iomem *addr_base[FMC2_MAX_CE]; + struct clk *clk; + + u8 cs_assigned; + int cs_sel; +}; + +static inline struct stm32_fmc2_nfc *to_stm32_nfc(struct nand_controller *base) +{ + return container_of(base, struct stm32_fmc2_nfc, base); +} + +static void stm32_fmc2_nfc_timings_init(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + struct stm32_fmc2_timings *timings = &nand->timings; + u32 pmem, patt; + + /* Set tclr/tar timings */ + regmap_update_bits(nfc->regmap, FMC2_PCR, + FMC2_PCR_TCLR | FMC2_PCR_TAR, + FIELD_PREP(FMC2_PCR_TCLR, timings->tclr) | + FIELD_PREP(FMC2_PCR_TAR, timings->tar)); + + /* Set tset/twait/thold/thiz timings in common bank */ + pmem = FIELD_PREP(FMC2_PMEM_MEMSET, timings->tset_mem); + pmem |= FIELD_PREP(FMC2_PMEM_MEMWAIT, timings->twait); + pmem |= FIELD_PREP(FMC2_PMEM_MEMHOLD, timings->thold_mem); + pmem |= FIELD_PREP(FMC2_PMEM_MEMHIZ, timings->thiz); + regmap_write(nfc->regmap, FMC2_PMEM, pmem); + + /* Set tset/twait/thold/thiz timings in attribut bank */ + patt = FIELD_PREP(FMC2_PATT_ATTSET, timings->tset_att); + patt |= FIELD_PREP(FMC2_PATT_ATTWAIT, timings->twait); + patt |= FIELD_PREP(FMC2_PATT_ATTHOLD, timings->thold_att); + patt |= FIELD_PREP(FMC2_PATT_ATTHIZ, timings->thiz); + regmap_write(nfc->regmap, FMC2_PATT, patt); +} + +static void stm32_fmc2_nfc_setup(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 pcr = 0, pcr_mask; + + /* Configure ECC algorithm (default configuration is Hamming) */ + pcr_mask = FMC2_PCR_ECCALG; + pcr_mask |= FMC2_PCR_BCHECC; + if (chip->ecc.strength == FMC2_ECC_BCH8) { + pcr |= FMC2_PCR_ECCALG; + pcr |= FMC2_PCR_BCHECC; + } else if (chip->ecc.strength == FMC2_ECC_BCH4) { + pcr |= FMC2_PCR_ECCALG; + } + + /* Set buswidth */ + pcr_mask |= FMC2_PCR_PWID; + if (chip->options & NAND_BUSWIDTH_16) + pcr |= FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_16); + + /* Set ECC sector size */ + pcr_mask |= FMC2_PCR_ECCSS; + pcr |= FIELD_PREP(FMC2_PCR_ECCSS, FMC2_PCR_ECCSS_512); + + regmap_update_bits(nfc->regmap, FMC2_PCR, pcr_mask, pcr); +} + +static void stm32_fmc2_nfc_select_chip(struct nand_chip *chip, int chipnr) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + + if (nand->cs_used[chipnr] == nfc->cs_sel) + return; + + nfc->cs_sel = nand->cs_used[chipnr]; + stm32_fmc2_nfc_setup(chip); + stm32_fmc2_nfc_timings_init(chip); +} + +static void stm32_fmc2_nfc_set_buswidth_16(struct stm32_fmc2_nfc *nfc, bool set) +{ + u32 pcr; + + pcr = set ? FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_16) : + FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_8); + + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_PWID, pcr); +} + +static void stm32_fmc2_nfc_set_ecc(struct stm32_fmc2_nfc *nfc, bool enable) +{ + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_ECCEN, + enable ? FMC2_PCR_ECCEN : 0); +} + +static void stm32_fmc2_nfc_clear_bch_irq(struct stm32_fmc2_nfc *nfc) +{ + regmap_write(nfc->regmap, FMC2_BCHICR, FMC2_BCHICR_CLEAR_IRQ); +} + +/* + * Enable ECC logic and reset syndrome/parity bits previously calculated + * Syndrome/parity bits is cleared by setting the ECCEN bit to 0 + */ +static void stm32_fmc2_nfc_hwctl(struct nand_chip *chip, int mode) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + + stm32_fmc2_nfc_set_ecc(nfc, false); + + if (chip->ecc.strength != FMC2_ECC_HAM) { + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_WEN, + mode == NAND_ECC_WRITE ? FMC2_PCR_WEN : 0); + + stm32_fmc2_nfc_clear_bch_irq(nfc); + } + + stm32_fmc2_nfc_set_ecc(nfc, true); +} + +/* + * ECC Hamming calculation + * ECC is 3 bytes for 512 bytes of data (supports error correction up to + * max of 1-bit) + */ +static void stm32_fmc2_nfc_ham_set_ecc(const u32 ecc_sta, u8 *ecc) +{ + ecc[0] = ecc_sta; + ecc[1] = ecc_sta >> 8; + ecc[2] = ecc_sta >> 16; +} + +static int stm32_fmc2_nfc_ham_calculate(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 sr, heccr; + int ret; + + ret = regmap_read_poll_timeout(nfc->regmap, FMC2_SR, sr, + sr & FMC2_SR_NWRF, + 1000 * FMC2_TIMEOUT_MS); + if (ret) { + dev_err(nfc->dev, "ham timeout\n"); + return ret; + } + + regmap_read(nfc->regmap, FMC2_HECCR, &heccr); + stm32_fmc2_nfc_ham_set_ecc(heccr, ecc); + stm32_fmc2_nfc_set_ecc(nfc, false); + + return 0; +} + +static int stm32_fmc2_nfc_ham_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + u8 bit_position = 0, b0, b1, b2; + u32 byte_addr = 0, b; + u32 i, shifting = 1; + + /* Indicate which bit and byte is faulty (if any) */ + b0 = read_ecc[0] ^ calc_ecc[0]; + b1 = read_ecc[1] ^ calc_ecc[1]; + b2 = read_ecc[2] ^ calc_ecc[2]; + b = b0 | (b1 << 8) | (b2 << 16); + + /* No errors */ + if (likely(!b)) + return 0; + + /* Calculate bit position */ + for (i = 0; i < 3; i++) { + switch (b % 4) { + case 2: + bit_position += shifting; + break; + case 1: + break; + default: + return -EBADMSG; + } + shifting <<= 1; + b >>= 2; + } + + /* Calculate byte position */ + shifting = 1; + for (i = 0; i < 9; i++) { + switch (b % 4) { + case 2: + byte_addr += shifting; + break; + case 1: + break; + default: + return -EBADMSG; + } + shifting <<= 1; + b >>= 2; + } + + /* Flip the bit */ + dat[byte_addr] ^= (1 << bit_position); + + return 1; +} + +/* + * ECC BCH calculation and correction + * ECC is 7/13 bytes for 512 bytes of data (supports error correction up to + * max of 4-bit/8-bit) + */ +static int stm32_fmc2_nfc_bch_calculate(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 bchisr, bchpbr; + int ret; + + /* Wait until the BCH code is ready */ + ret = regmap_read_poll_timeout(nfc->regmap, FMC2_BCHISR, bchisr, + bchisr & FMC2_BCHISR_EPBRF, + 1000 * FMC2_TIMEOUT_MS); + if (ret) { + dev_err(nfc->dev, "bch timeout\n"); + return -ETIMEDOUT; + } + + /* Read parity bits */ + regmap_read(nfc->regmap, FMC2_BCHPBR1, &bchpbr); + ecc[0] = bchpbr; + ecc[1] = bchpbr >> 8; + ecc[2] = bchpbr >> 16; + ecc[3] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR2, &bchpbr); + ecc[4] = bchpbr; + ecc[5] = bchpbr >> 8; + ecc[6] = bchpbr >> 16; + + if (chip->ecc.strength == FMC2_ECC_BCH8) { + ecc[7] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR3, &bchpbr); + ecc[8] = bchpbr; + ecc[9] = bchpbr >> 8; + ecc[10] = bchpbr >> 16; + ecc[11] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR4, &bchpbr); + ecc[12] = bchpbr; + } + + stm32_fmc2_nfc_set_ecc(nfc, false); + + return 0; +} + +static int stm32_fmc2_nfc_bch_decode(int eccsize, u8 *dat, u32 *ecc_sta) +{ + u32 bchdsr0 = ecc_sta[0]; + u32 bchdsr1 = ecc_sta[1]; + u32 bchdsr2 = ecc_sta[2]; + u32 bchdsr3 = ecc_sta[3]; + u32 bchdsr4 = ecc_sta[4]; + u16 pos[8]; + int i, den; + unsigned int nb_errs = 0; + + /* No errors found */ + if (likely(!(bchdsr0 & FMC2_BCHDSR0_DEF))) + return 0; + + /* Too many errors detected */ + if (unlikely(bchdsr0 & FMC2_BCHDSR0_DUE)) + return -EBADMSG; + + pos[0] = FIELD_GET(FMC2_BCHDSR1_EBP1, bchdsr1); + pos[1] = FIELD_GET(FMC2_BCHDSR1_EBP2, bchdsr1); + pos[2] = FIELD_GET(FMC2_BCHDSR2_EBP3, bchdsr2); + pos[3] = FIELD_GET(FMC2_BCHDSR2_EBP4, bchdsr2); + pos[4] = FIELD_GET(FMC2_BCHDSR3_EBP5, bchdsr3); + pos[5] = FIELD_GET(FMC2_BCHDSR3_EBP6, bchdsr3); + pos[6] = FIELD_GET(FMC2_BCHDSR4_EBP7, bchdsr4); + pos[7] = FIELD_GET(FMC2_BCHDSR4_EBP8, bchdsr4); + + den = FIELD_GET(FMC2_BCHDSR0_DEN, bchdsr0); + for (i = 0; i < den; i++) { + if (pos[i] < eccsize * 8) { + change_bit(pos[i], (unsigned long *)dat); + nb_errs++; + } + } + + return nb_errs; +} + +static int stm32_fmc2_nfc_bch_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 bchisr, ecc_sta[5]; + int ret; + + /* Wait until the decoding error is ready */ + ret = regmap_read_poll_timeout(nfc->regmap, FMC2_BCHISR, bchisr, + bchisr & FMC2_BCHISR_DERF, + 1000 * FMC2_TIMEOUT_MS); + if (ret) { + dev_err(nfc->dev, "bch timeout\n"); + return -ETIMEDOUT; + } + + regmap_bulk_read(nfc->regmap, FMC2_BCHDSR0, ecc_sta, ARRAY_SIZE(ecc_sta)); + + stm32_fmc2_nfc_set_ecc(nfc, false); + + return stm32_fmc2_nfc_bch_decode(chip->ecc.size, dat, ecc_sta); +} + +static int stm32_fmc2_nfc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret, i, s, stat, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + int eccstrength = chip->ecc.strength; + u8 *p = buf; + u8 *ecc_calc = chip->ecc.calc_buf; + u8 *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = mtd->writesize + FMC2_BBM_LEN, s = 0; s < eccsteps; + s++, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(chip, NAND_ECC_READ); + + /* Read the nand page sector (512 bytes) */ + ret = nand_change_read_column_op(chip, s * eccsize, p, + eccsize, false); + if (ret) + return ret; + + /* Read the corresponding ECC bytes */ + ret = nand_change_read_column_op(chip, i, ecc_code, + eccbytes, false); + if (ret) + return ret; + + /* Correct the data */ + stat = chip->ecc.correct(chip, p, ecc_code, ecc_calc); + if (stat == -EBADMSG) + /* Check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, eccsize, + ecc_code, eccbytes, + NULL, 0, + eccstrength); + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + /* Read oob */ + if (oob_required) { + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } + + return max_bitflips; +} + +static void stm32_fmc2_nfc_read_data(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + void __iomem *io_addr_r = nfc->data_base[nfc->cs_sel]; + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 8-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, false); + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32))) { + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u16)) && len) { + *(u8 *)buf = readb_relaxed(io_addr_r); + buf += sizeof(u8); + len -= sizeof(u8); + } + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + len >= sizeof(u16)) { + *(u16 *)buf = readw_relaxed(io_addr_r); + buf += sizeof(u16); + len -= sizeof(u16); + } + } + + /* Buf is aligned */ + while (len >= sizeof(u32)) { + *(u32 *)buf = readl_relaxed(io_addr_r); + buf += sizeof(u32); + len -= sizeof(u32); + } + + /* Read remaining bytes */ + if (len >= sizeof(u16)) { + *(u16 *)buf = readw_relaxed(io_addr_r); + buf += sizeof(u16); + len -= sizeof(u16); + } + + if (len) + *(u8 *)buf = readb_relaxed(io_addr_r); + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 16-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, true); +} + +static void stm32_fmc2_nfc_write_data(struct nand_chip *chip, const void *buf, + unsigned int len, bool force_8bit) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + void __iomem *io_addr_w = nfc->data_base[nfc->cs_sel]; + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 8-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, false); + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32))) { + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u16)) && len) { + writeb_relaxed(*(u8 *)buf, io_addr_w); + buf += sizeof(u8); + len -= sizeof(u8); + } + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + len >= sizeof(u16)) { + writew_relaxed(*(u16 *)buf, io_addr_w); + buf += sizeof(u16); + len -= sizeof(u16); + } + } + + /* Buf is aligned */ + while (len >= sizeof(u32)) { + writel_relaxed(*(u32 *)buf, io_addr_w); + buf += sizeof(u32); + len -= sizeof(u32); + } + + /* Write remaining bytes */ + if (len >= sizeof(u16)) { + writew_relaxed(*(u16 *)buf, io_addr_w); + buf += sizeof(u16); + len -= sizeof(u16); + } + + if (len) + writeb_relaxed(*(u8 *)buf, io_addr_w); + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 16-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, true); +} + +static int stm32_fmc2_nfc_waitrdy(struct nand_chip *chip, + unsigned long timeout_ms) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + const struct nand_sdr_timings *timings; + u32 isr, sr; + + /* Check if there is no pending requests to the NAND flash */ + if (regmap_read_poll_timeout(nfc->regmap, FMC2_SR, sr, + sr & FMC2_SR_NWRF, + 1000 * FMC2_TIMEOUT_MS)) + dev_warn(nfc->dev, "Waitrdy timeout\n"); + + /* Wait tWB before R/B# signal is low */ + timings = nand_get_sdr_timings(nand_get_interface_config(chip)); + ndelay(PSEC_TO_NSEC(timings->tWB_max)); + + /* R/B# signal is low, clear high level flag */ + regmap_write(nfc->regmap, FMC2_ICR, FMC2_ICR_CIHLF); + + /* Wait R/B# signal is high */ + return regmap_read_poll_timeout(nfc->regmap, FMC2_ISR, isr, + isr & FMC2_ISR_IHLF, + 1000 * FMC2_TIMEOUT_MS); +} + +static int stm32_fmc2_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + const struct nand_op_instr *instr = NULL; + unsigned int op_id, i, timeout; + int ret = 0; + + if (check_only) + return 0; + + stm32_fmc2_nfc_select_chip(chip, op->cs); + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb_relaxed(instr->ctx.cmd.opcode, + nfc->cmd_base[nfc->cs_sel]); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb_relaxed(instr->ctx.addr.addrs[i], + nfc->addr_base[nfc->cs_sel]); + break; + + case NAND_OP_DATA_IN_INSTR: + stm32_fmc2_nfc_read_data(chip, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_DATA_OUT_INSTR: + stm32_fmc2_nfc_write_data(chip, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_WAITRDY_INSTR: + timeout = instr->ctx.waitrdy.timeout_ms; + ret = stm32_fmc2_nfc_waitrdy(chip, timeout); + break; + } + } + + return ret; +} + +static void stm32_fmc2_nfc_init(struct stm32_fmc2_nfc *nfc) +{ + u32 pcr; + + regmap_read(nfc->regmap, FMC2_PCR, &pcr); + + /* Set CS used to undefined */ + nfc->cs_sel = -1; + + /* Enable wait feature and nand flash memory bank */ + pcr |= FMC2_PCR_PWAITEN; + pcr |= FMC2_PCR_PBKEN; + + /* Set buswidth to 8 bits mode for identification */ + pcr &= ~FMC2_PCR_PWID; + + /* ECC logic is disabled */ + pcr &= ~FMC2_PCR_ECCEN; + + /* Default mode */ + pcr &= ~FMC2_PCR_ECCALG; + pcr &= ~FMC2_PCR_BCHECC; + pcr &= ~FMC2_PCR_WEN; + + /* Set default ECC sector size */ + pcr &= ~FMC2_PCR_ECCSS; + pcr |= FIELD_PREP(FMC2_PCR_ECCSS, FMC2_PCR_ECCSS_2048); + + /* Set default tclr/tar timings */ + pcr &= ~FMC2_PCR_TCLR; + pcr |= FIELD_PREP(FMC2_PCR_TCLR, FMC2_PCR_TCLR_DEFAULT); + pcr &= ~FMC2_PCR_TAR; + pcr |= FIELD_PREP(FMC2_PCR_TAR, FMC2_PCR_TAR_DEFAULT); + + /* Enable FMC2 controller */ + if (nfc->dev == nfc->cdev) + regmap_update_bits(nfc->regmap, FMC2_BCR1, + FMC2_BCR1_FMC2EN, FMC2_BCR1_FMC2EN); + + regmap_write(nfc->regmap, FMC2_PCR, pcr); + regmap_write(nfc->regmap, FMC2_PMEM, FMC2_PMEM_DEFAULT); + regmap_write(nfc->regmap, FMC2_PATT, FMC2_PATT_DEFAULT); +} + +static void stm32_fmc2_nfc_calc_timings(struct nand_chip *chip, + const struct nand_sdr_timings *sdrt) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + struct stm32_fmc2_timings *tims = &nand->timings; + unsigned long hclk = clk_get_rate(nfc->clk); + unsigned long hclkp = NSEC_PER_SEC / (hclk / 1000); + unsigned long timing, tar, tclr, thiz, twait; + unsigned long tset_mem, tset_att, thold_mem, thold_att; + + tar = max_t(unsigned long, hclkp, sdrt->tAR_min); + timing = DIV_ROUND_UP(tar, hclkp) - 1; + tims->tar = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK); + + tclr = max_t(unsigned long, hclkp, sdrt->tCLR_min); + timing = DIV_ROUND_UP(tclr, hclkp) - 1; + tims->tclr = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK); + + tims->thiz = FMC2_THIZ; + thiz = (tims->thiz + 1) * hclkp; + + /* + * tWAIT > tRP + * tWAIT > tWP + * tWAIT > tREA + tIO + */ + twait = max_t(unsigned long, hclkp, sdrt->tRP_min); + twait = max_t(unsigned long, twait, sdrt->tWP_min); + twait = max_t(unsigned long, twait, sdrt->tREA_max + FMC2_TIO); + timing = DIV_ROUND_UP(twait, hclkp); + tims->twait = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tSETUP_MEM > tCS - tWAIT + * tSETUP_MEM > tALS - tWAIT + * tSETUP_MEM > tDS - (tWAIT - tHIZ) + */ + tset_mem = hclkp; + if (sdrt->tCS_min > twait && (tset_mem < sdrt->tCS_min - twait)) + tset_mem = sdrt->tCS_min - twait; + if (sdrt->tALS_min > twait && (tset_mem < sdrt->tALS_min - twait)) + tset_mem = sdrt->tALS_min - twait; + if (twait > thiz && (sdrt->tDS_min > twait - thiz) && + (tset_mem < sdrt->tDS_min - (twait - thiz))) + tset_mem = sdrt->tDS_min - (twait - thiz); + timing = DIV_ROUND_UP(tset_mem, hclkp); + tims->tset_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tHOLD_MEM > tCH + * tHOLD_MEM > tREH - tSETUP_MEM + * tHOLD_MEM > max(tRC, tWC) - (tSETUP_MEM + tWAIT) + */ + thold_mem = max_t(unsigned long, hclkp, sdrt->tCH_min); + if (sdrt->tREH_min > tset_mem && + (thold_mem < sdrt->tREH_min - tset_mem)) + thold_mem = sdrt->tREH_min - tset_mem; + if ((sdrt->tRC_min > tset_mem + twait) && + (thold_mem < sdrt->tRC_min - (tset_mem + twait))) + thold_mem = sdrt->tRC_min - (tset_mem + twait); + if ((sdrt->tWC_min > tset_mem + twait) && + (thold_mem < sdrt->tWC_min - (tset_mem + twait))) + thold_mem = sdrt->tWC_min - (tset_mem + twait); + timing = DIV_ROUND_UP(thold_mem, hclkp); + tims->thold_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tSETUP_ATT > tCS - tWAIT + * tSETUP_ATT > tCLS - tWAIT + * tSETUP_ATT > tALS - tWAIT + * tSETUP_ATT > tRHW - tHOLD_MEM + * tSETUP_ATT > tDS - (tWAIT - tHIZ) + */ + tset_att = hclkp; + if (sdrt->tCS_min > twait && (tset_att < sdrt->tCS_min - twait)) + tset_att = sdrt->tCS_min - twait; + if (sdrt->tCLS_min > twait && (tset_att < sdrt->tCLS_min - twait)) + tset_att = sdrt->tCLS_min - twait; + if (sdrt->tALS_min > twait && (tset_att < sdrt->tALS_min - twait)) + tset_att = sdrt->tALS_min - twait; + if (sdrt->tRHW_min > thold_mem && + (tset_att < sdrt->tRHW_min - thold_mem)) + tset_att = sdrt->tRHW_min - thold_mem; + if (twait > thiz && (sdrt->tDS_min > twait - thiz) && + (tset_att < sdrt->tDS_min - (twait - thiz))) + tset_att = sdrt->tDS_min - (twait - thiz); + timing = DIV_ROUND_UP(tset_att, hclkp); + tims->tset_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tHOLD_ATT > tALH + * tHOLD_ATT > tCH + * tHOLD_ATT > tCLH + * tHOLD_ATT > tCOH + * tHOLD_ATT > tDH + * tHOLD_ATT > tWB + tIO + tSYNC - tSETUP_MEM + * tHOLD_ATT > tADL - tSETUP_MEM + * tHOLD_ATT > tWH - tSETUP_MEM + * tHOLD_ATT > tWHR - tSETUP_MEM + * tHOLD_ATT > tRC - (tSETUP_ATT + tWAIT) + * tHOLD_ATT > tWC - (tSETUP_ATT + tWAIT) + */ + thold_att = max_t(unsigned long, hclkp, sdrt->tALH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCLH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCOH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tDH_min); + if ((sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC > tset_mem) && + (thold_att < sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem)) + thold_att = sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem; + if (sdrt->tADL_min > tset_mem && + (thold_att < sdrt->tADL_min - tset_mem)) + thold_att = sdrt->tADL_min - tset_mem; + if (sdrt->tWH_min > tset_mem && + (thold_att < sdrt->tWH_min - tset_mem)) + thold_att = sdrt->tWH_min - tset_mem; + if (sdrt->tWHR_min > tset_mem && + (thold_att < sdrt->tWHR_min - tset_mem)) + thold_att = sdrt->tWHR_min - tset_mem; + if ((sdrt->tRC_min > tset_att + twait) && + (thold_att < sdrt->tRC_min - (tset_att + twait))) + thold_att = sdrt->tRC_min - (tset_att + twait); + if ((sdrt->tWC_min > tset_att + twait) && + (thold_att < sdrt->tWC_min - (tset_att + twait))) + thold_att = sdrt->tWC_min - (tset_att + twait); + timing = DIV_ROUND_UP(thold_att, hclkp); + tims->thold_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); +} + +static int stm32_fmc2_nfc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + const struct nand_sdr_timings *sdrt; + + sdrt = nand_get_sdr_timings(conf); + if (IS_ERR(sdrt)) + return PTR_ERR(sdrt); + + if (conf->timings.mode > 3) + return -EOPNOTSUPP; + + if (chipnr == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + stm32_fmc2_nfc_calc_timings(chip, sdrt); + stm32_fmc2_nfc_timings_init(chip); + + return 0; +} + +static void stm32_fmc2_nfc_nand_callbacks_setup(struct nand_chip *chip) +{ + /* + * Specific callbacks to read/write a page depending on + * the mode (polling/sequencer) and the algo used (Hamming, BCH). + */ + chip->ecc.hwctl = stm32_fmc2_nfc_hwctl; + if (chip->ecc.strength == FMC2_ECC_HAM) { + /* Hamming is used */ + chip->ecc.calculate = stm32_fmc2_nfc_ham_calculate; + chip->ecc.correct = stm32_fmc2_nfc_ham_correct; + chip->ecc.options |= NAND_ECC_GENERIC_ERASED_CHECK; + } else { + /* BCH is used */ + chip->ecc.calculate = stm32_fmc2_nfc_bch_calculate; + chip->ecc.correct = stm32_fmc2_nfc_bch_correct; + chip->ecc.read_page = stm32_fmc2_nfc_read_page; + } + + /* Specific configurations depending on the algo used */ + if (chip->ecc.strength == FMC2_ECC_HAM) + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 4 : 3; + else if (chip->ecc.strength == FMC2_ECC_BCH8) + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 14 : 13; + else + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 8 : 7; +} + +static int stm32_fmc2_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = ecc->total; + oobregion->offset = FMC2_BBM_LEN; + + return 0; +} + +static int stm32_fmc2_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = mtd->oobsize - ecc->total - FMC2_BBM_LEN; + oobregion->offset = ecc->total + FMC2_BBM_LEN; + + return 0; +} + +static const struct mtd_ooblayout_ops stm32_fmc2_nfc_ooblayout_ops = { + .ecc = stm32_fmc2_nfc_ooblayout_ecc, + .free = stm32_fmc2_nfc_ooblayout_free, +}; + +static int stm32_fmc2_nfc_calc_ecc_bytes(int step_size, int strength) +{ + /* Hamming */ + if (strength == FMC2_ECC_HAM) + return 4; + + /* BCH8 */ + if (strength == FMC2_ECC_BCH8) + return 14; + + /* BCH4 */ + return 8; +} + +NAND_ECC_CAPS_SINGLE(stm32_fmc2_nfc_ecc_caps, stm32_fmc2_nfc_calc_ecc_bytes, + FMC2_ECC_STEP_SIZE, + FMC2_ECC_HAM, FMC2_ECC_BCH4, FMC2_ECC_BCH8); + +static int stm32_fmc2_nfc_attach_chip(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* Default ECC settings in case they are not set in the device tree */ + if (!chip->ecc.size) + chip->ecc.size = FMC2_ECC_STEP_SIZE; + + if (!chip->ecc.strength) + chip->ecc.strength = FMC2_ECC_BCH8; + + ret = nand_ecc_choose_conf(chip, &stm32_fmc2_nfc_ecc_caps, + mtd->oobsize - FMC2_BBM_LEN); + if (ret) { + dev_err(nfc->dev, "no valid ECC settings set\n"); + return ret; + } + + if (mtd->writesize / chip->ecc.size > FMC2_MAX_SG) { + dev_err(nfc->dev, "nand page size is not supported\n"); + return -EINVAL; + } + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + stm32_fmc2_nfc_nand_callbacks_setup(chip); + + mtd_set_ooblayout(mtd, &stm32_fmc2_nfc_ooblayout_ops); + + stm32_fmc2_nfc_setup(chip); + + return 0; +} + +static const struct nand_controller_ops stm32_fmc2_nfc_controller_ops = { + .attach_chip = stm32_fmc2_nfc_attach_chip, + .exec_op = stm32_fmc2_nfc_exec_op, + .setup_interface = stm32_fmc2_nfc_setup_interface, +}; + +static void stm32_fmc2_nfc_wp_enable(struct stm32_fmc2_nand *nand) +{ + if (nand->wp_gpio) + gpiod_set_value(nand->wp_gpio, 1); +} + +static void stm32_fmc2_nfc_wp_disable(struct stm32_fmc2_nand *nand) +{ + if (nand->wp_gpio) + gpiod_set_value(nand->wp_gpio, 0); +} + +static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc, + struct device_node *dn) +{ + struct stm32_fmc2_nand *nand = &nfc->nand; + u32 cs; + int ret, i; + + if (!of_get_property(dn, "reg", &nand->ncs)) + return -EINVAL; + + nand->ncs /= sizeof(u32); + if (!nand->ncs) { + dev_err(nfc->dev, "invalid reg property size\n"); + return -EINVAL; + } + + for (i = 0; i < nand->ncs; i++) { + ret = of_property_read_u32_index(dn, "reg", i, &cs); + if (ret) { + dev_err(nfc->dev, "could not retrieve reg property: %d\n", + ret); + return ret; + } + + if (cs >= FMC2_MAX_CE) { + dev_err(nfc->dev, "invalid reg value: %d\n", cs); + return -EINVAL; + } + + if (nfc->cs_assigned & BIT(cs)) { + dev_err(nfc->dev, "cs already assigned: %d\n", cs); + return -EINVAL; + } + + nfc->cs_assigned |= BIT(cs); + nand->cs_used[i] = cs; + } + + nand->wp_gpio = dev_gpiod_get(nfc->dev, dn, "wp", GPIOD_OUT_HIGH, "wp"); + if (IS_ERR(nand->wp_gpio)) { + ret = PTR_ERR(nand->wp_gpio); + if (ret != -ENOENT) + return dev_err_probe(nfc->dev, ret, + "failed to request WP GPIO\n"); + + nand->wp_gpio = NULL; + } + + nand_set_flash_node(&nand->chip, dn); + + return 0; +} + +static int stm32_fmc2_nfc_parse_dt(struct stm32_fmc2_nfc *nfc) +{ + struct device_node *dn = nfc->dev->of_node; + struct device_node *child; + int nchips = of_get_child_count(dn); + int ret = 0; + + if (!nchips) { + dev_err(nfc->dev, "NAND chip not defined\n"); + return -EINVAL; + } + + if (nchips > 1) { + dev_err(nfc->dev, "too many NAND chips defined\n"); + return -EINVAL; + } + + for_each_child_of_node(dn, child) { + ret = stm32_fmc2_nfc_parse_child(nfc, child); + if (ret < 0) { + of_node_put(child); + return ret; + } + } + + return ret; +} + +static int stm32_fmc2_nfc_set_cdev(struct stm32_fmc2_nfc *nfc) +{ + struct device *dev = nfc->dev; + bool ebi_found = false; + + if (dev->parent && of_device_is_compatible(dev->parent->of_node, + "st,stm32mp1-fmc2-ebi")) + ebi_found = true; + + if (of_device_is_compatible(dev->of_node, "st,stm32mp1-fmc2-nfc")) { + if (ebi_found) { + nfc->cdev = dev->parent; + + return 0; + } + + return -EINVAL; + } + + if (ebi_found) + return -EINVAL; + + nfc->cdev = dev; + + return 0; +} + +static int __init stm32_fmc2_nfc_probe(struct device *dev) +{ + struct stm32_fmc2_nfc *nfc; + struct stm32_fmc2_nand *nand; + struct mtd_info *mtd; + struct nand_chip *chip; + struct resource cres; + int chip_cs, mem_region, ret; + int start_region = 0; + + nfc = kzalloc(sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + nand_controller_init(&nfc->base); + nfc->base.ops = &stm32_fmc2_nfc_controller_ops; + + ret = stm32_fmc2_nfc_set_cdev(nfc); + if (ret) + return ret; + + ret = stm32_fmc2_nfc_parse_dt(nfc); + if (ret) + return ret; + + ret = of_address_to_resource(nfc->cdev->of_node, 0, &cres); + if (ret) + return ret; + + nfc->regmap = device_node_to_regmap(nfc->cdev->of_node); + if (IS_ERR(nfc->regmap)) + return PTR_ERR(nfc->regmap); + + if (nfc->dev == nfc->cdev) + start_region = 1; + + for (chip_cs = 0, mem_region = start_region; chip_cs < FMC2_MAX_CE; + chip_cs++, mem_region += 3) { + if (!(nfc->cs_assigned & BIT(chip_cs))) + continue; + + nfc->data_base[chip_cs] = of_iomap(dev->of_node, mem_region); + if (IS_ERR(nfc->data_base[chip_cs])) + return PTR_ERR(nfc->data_base[chip_cs]); + + nfc->cmd_base[chip_cs] = of_iomap(dev->of_node, mem_region + 1); + if (IS_ERR(nfc->cmd_base[chip_cs])) + return PTR_ERR(nfc->cmd_base[chip_cs]); + + nfc->addr_base[chip_cs] = of_iomap(dev->of_node, mem_region + 2); + if (IS_ERR(nfc->addr_base[chip_cs])) + return PTR_ERR(nfc->addr_base[chip_cs]); + } + + nfc->clk = clk_get(nfc->cdev, NULL); + if (IS_ERR(nfc->clk)) + return PTR_ERR(nfc->clk); + + ret = clk_prepare_enable(nfc->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + + ret = device_reset_us(dev, 2); + if (ret) + goto err_clk_disable; + + stm32_fmc2_nfc_init(nfc); + + nand = &nfc->nand; + chip = &nand->chip; + mtd = nand_to_mtd(chip); + mtd->dev.parent = dev; + + chip->controller = &nfc->base; + chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE; + + /* Default ECC settings */ + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = FMC2_ECC_STEP_SIZE; + chip->ecc.strength = FMC2_ECC_BCH8; + + stm32_fmc2_nfc_wp_disable(nand); + + /* Scan to find existence of the device */ + ret = nand_scan(chip, nand->ncs); + if (ret) + goto err_wp_enable; + + ret = add_mtd_nand_device(mtd, "nand"); + if (ret) + goto err_nand_cleanup; + + return 0; + +err_nand_cleanup: + nand_cleanup(chip); + +err_wp_enable: + stm32_fmc2_nfc_wp_enable(nand); + +err_clk_disable: + clk_disable_unprepare(nfc->clk); + + return ret; +} + +static __maybe_unused struct of_device_id stm32_fmc2_nfc_match[] = { + { .compatible = "st,stm32mp15-fmc2", }, + { .compatible = "st,stm32mp1-fmc2-nfc", }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_fmc2_nfc_match); + +static struct driver stm32_fmc2_nfc_driver = { + .name = "stm32_fmc2_nfc", + .probe = stm32_fmc2_nfc_probe, + .of_compatible = DRV_OF_COMPAT(stm32_fmc2_nfc_match), +}; +coredevice_platform_driver(stm32_fmc2_nfc_driver); diff --git a/drivers/mtd/nor/Kconfig b/drivers/mtd/nor/Kconfig index 44a418405b..36ffdc39af 100644 --- a/drivers/mtd/nor/Kconfig +++ b/drivers/mtd/nor/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig DRIVER_CFI bool "CFI NOR flash support" help diff --git a/drivers/mtd/nor/Makefile b/drivers/mtd/nor/Makefile index d2550436d2..91fc09f782 100644 --- a/drivers/mtd/nor/Makefile +++ b/drivers/mtd/nor/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_DRIVER_CFI) += cfi_flash.o obj-$(CONFIG_DRIVER_CFI_INTEL) += cfi_flash_intel.o obj-$(CONFIG_DRIVER_CFI_AMD) += cfi_flash_amd.o diff --git a/drivers/mtd/nor/cfi_flash.c b/drivers/mtd/nor/cfi_flash.c index ffd29d80a7..ac46575004 100644 --- a/drivers/mtd/nor/cfi_flash.c +++ b/drivers/mtd/nor/cfi_flash.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright 2002-2004 * Brad Kemp, Seranoa Networks, Brad.Kemp@seranoa.com @@ -10,18 +11,6 @@ * * Copyright (C) 2006 * Tolunay Orkun <listmember@orkun.us> - * - * 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. - * - * */ /* The DEBUG define must be before common to enable debugging */ @@ -769,7 +758,7 @@ static void cfi_info_one(struct flash_info *info) return; } -static void cfi_info(struct device_d *dev) +static void cfi_info(struct device *dev) { struct cfi_priv *priv = dev->priv; int i; @@ -976,7 +965,10 @@ static int cfi_probe_one(struct flash_info *info, int num) return PTR_ERR(iores); info->base = IOMEM(iores->start); - /* TODO: either remap memory region or disable NULL pointer page */ + /* + * Platforms hitting this should remap memory region, e.g. via virtual-reg + * device tree property or disable MMU. + */ if (IS_ENABLED(CONFIG_MMU) && iores->start == 0) return -EPERM; @@ -995,7 +987,7 @@ static int cfi_probe_one(struct flash_info *info, int num) return 0; } -static int cfi_probe(struct device_d *dev) +static int cfi_probe(struct device *dev) { struct cfi_priv *priv; int i, ret; @@ -1008,7 +1000,7 @@ static int cfi_probe(struct device_d *dev) priv->infos = xzalloc(sizeof(*priv->infos) * priv->num_devs); priv->mtds = xzalloc(sizeof(*priv->mtds) * priv->num_devs); - of_property_read_string(dev->device_node, "linux,mtd-name", &mtd_name); + of_property_read_string(dev->of_node, "linux,mtd-name", &mtd_name); if (!mtd_name) mtd_name = dev_name(dev); @@ -1057,8 +1049,9 @@ static __maybe_unused struct of_device_id cfi_dt_ids[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, cfi_dt_ids); -static struct driver_d cfi_driver = { +static struct driver cfi_driver = { .name = "cfi_flash", .probe = cfi_probe, .of_compatible = DRV_OF_COMPAT(cfi_dt_ids), diff --git a/drivers/mtd/nor/cfi_flash.h b/drivers/mtd/nor/cfi_flash.h index cea6a8712c..5d3053f971 100644 --- a/drivers/mtd/nor/cfi_flash.h +++ b/drivers/mtd/nor/cfi_flash.h @@ -1,20 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __CFI_FLASH_H #define __CFI_FLASH_H /* * (C) Copyright 2000-2005 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. - * - * 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. - * */ #include <driver.h> @@ -45,7 +35,7 @@ struct cfi_cmd_set; */ struct flash_info { - struct device_d *dev; + struct device *dev; unsigned long size; /* total bank size in bytes */ unsigned int sector_count; /* number of erase units */ unsigned long flash_id; /* combined device & manufacturer code */ diff --git a/drivers/mtd/nor/cfi_flash_amd.c b/drivers/mtd/nor/cfi_flash_amd.c index 9c44561d45..08cf499a49 100644 --- a/drivers/mtd/nor/cfi_flash_amd.c +++ b/drivers/mtd/nor/cfi_flash_amd.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <stdio.h> #include "cfi_flash.h" diff --git a/drivers/mtd/nor/cfi_flash_intel.c b/drivers/mtd/nor/cfi_flash_intel.c index 6108d7cc62..2eacff6659 100644 --- a/drivers/mtd/nor/cfi_flash_intel.c +++ b/drivers/mtd/nor/cfi_flash_intel.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include "cfi_flash.h" diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c index a426c8bfcf..4ebc5bba41 100644 --- a/drivers/mtd/partition.c +++ b/drivers/mtd/partition.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <errno.h> #include <malloc.h> diff --git a/drivers/mtd/peb.c b/drivers/mtd/peb.c index 03d96c2a5a..a17d42885e 100644 --- a/drivers/mtd/peb.c +++ b/drivers/mtd/peb.c @@ -1,13 +1,4 @@ -/* - * 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. - * - * 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. - */ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <malloc.h> @@ -214,10 +205,12 @@ int mtd_peb_read(struct mtd_info *mtd, void *buf, int pnum, int offset, return -EINVAL; if (offset < 0 || offset + len > mtd->erasesize) return -EINVAL; - if (len <= 0) + if (len < 0) return -EINVAL; if (mtd_peb_is_bad(mtd, pnum)) return -EINVAL; + if (!len) + return 0; /* Deliberately corrupt the buffer */ *((uint8_t *)buf) ^= 0xFF; @@ -397,12 +390,14 @@ int mtd_peb_write(struct mtd_info *mtd, const void *buf, int pnum, int offset, return -EINVAL; if (offset < 0 || offset + len > mtd->erasesize) return -EINVAL; - if (len <= 0) + if (len < 0) return -EINVAL; if (len % (mtd->writesize >> mtd->subpage_sft)) return -EINVAL; if (mtd_peb_is_bad(mtd, pnum)) return -EINVAL; + if (!len) + return 0; if (mtd_peb_emulate_write_failure()) { dev_err(&mtd->dev, "Cannot write %d bytes to PEB %d:%d (emulated)\n", @@ -518,6 +513,51 @@ out: } /** + * mtd_peb_read_file - read data from a mtd device + * @mtd: mtd device + * @peb_start: The first PEB where to start reading + * @peb_last: last PEB where to read from + * @buf: buffer to read to + * @len: how many bytes to read + * + * This function reads @len bytes of data to buffer @buf from the mtd device + * @mtd starting at @peb_start. Reading will stop at @peb_last. This function + * skips all bad blocks and returns 0 on success or a negative error code + * otherwise. + */ +int mtd_peb_read_file(struct mtd_info *mtd, unsigned int peb_start, + unsigned int peb_last, void *buf, size_t len) +{ + int ret, pnum; + + pnum = peb_start; + + while (len) { + size_t now = min_t(size_t, mtd->erasesize, len); + + if (pnum > peb_last) + return -EIO; + + if (mtd_peb_is_bad(mtd, pnum)) { + pnum++; + continue; + } + + ret = mtd_peb_read(mtd, buf, pnum, 0, now); + if (ret) + goto out; + + len -= now; + pnum++; + buf += now; + } + + ret = 0; +out: + return ret; +} + +/** * mtd_peb_erase - erase a physical eraseblock. * @mtd: mtd device * @pnum: physical eraseblock number to erase diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 51cebcf35b..b34c69203e 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig MTD_SPI_NOR tristate "SPI-NOR device support" depends on MTD @@ -25,4 +26,10 @@ config SPI_CADENCE_QUADSPI help This enables support for the Cadence Quad SPI controller and NOR flash. +config SPI_SYNOPSYS_OCTALSPI_NOR + tristate "Synopsys DesignWare Octal SPI controller" + help + This enables support for the Synopsys DesignWare Octal SPI controller + and NOR flash. + endif diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 4e00f38a7d..61cf789182 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,2 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o +obj-$(CONFIG_SPI_SYNOPSYS_OCTALSPI_NOR) += dw-ospi-nor.o diff --git a/drivers/mtd/spi-nor/cadence-quadspi.c b/drivers/mtd/spi-nor/cadence-quadspi.c index ea53d2cd84..763858567b 100644 --- a/drivers/mtd/spi-nor/cadence-quadspi.c +++ b/drivers/mtd/spi-nor/cadence-quadspi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Pengutronix, Steffen Trumtrar <s.trumtrar@pengutronix.de> * @@ -6,18 +7,6 @@ * Driver for Cadence QSPI Controller * * Copyright Altera Corporation (C) 2012-2014. All rights reserved. - * - * 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, see <http://www.gnu.org/licenses/>. */ #include <clock.h> @@ -51,7 +40,7 @@ struct cqspi_flash_pdata { }; struct cqspi_st { - struct device_d *dev; + struct device *dev; struct clk *l4_mp_clk; struct clk *qspi_clk; unsigned int sclk; @@ -347,8 +336,7 @@ static int cqspi_command_read(struct spi_nor *nor, if (!n_rx || n_rx > CQSPI_STIG_DATA_LEN_MAX || rxbuf == NULL) { dev_err(nor->dev, - "Invalid input argument, len %d rxbuf 0x%08x\n", n_rx, - (unsigned int)rxbuf); + "Invalid input argument, len %d rxbuf 0x%p\n", n_rx, rxbuf); return -EINVAL; } @@ -393,8 +381,7 @@ static __maybe_unused int cqspi_command_write(struct spi_nor *nor, if (n_tx > 4 || (n_tx && txbuf == NULL)) { dev_err(nor->dev, - "Invalid input argument, cmdlen %d txbuf 0x%08x\n", - n_tx, (unsigned int)txbuf); + "Invalid input argument, cmdlen %d txbuf 0x%p\n", n_tx, txbuf); return -EINVAL; } @@ -433,7 +420,7 @@ static int cqspi_indirect_read_setup(struct spi_nor *nor, { struct cqspi_flash_pdata *f_pdata; struct cqspi_st *cqspi = nor->priv; - unsigned int ahb_base = (unsigned int) cqspi->ahb_base; + phys_addr_t ahb_base = virt_to_phys(cqspi->ahb_base); void __iomem *reg_base = cqspi->iobase; unsigned int dummy_clk = 0; unsigned int dummy_bytes; @@ -1012,7 +999,7 @@ static int cqspi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) return ret; } -static int cqspi_of_get_flash_pdata(struct device_d *dev, +static int cqspi_of_get_flash_pdata(struct device *dev, struct cqspi_flash_pdata *f_pdata, struct device_node *np) { @@ -1069,8 +1056,8 @@ static int cqspi_of_get_flash_pdata(struct device_d *dev, static int cqspi_parse_dt(struct cqspi_st *cqspi) { - struct device_node *np = cqspi->dev->device_node; - struct device_d *dev = cqspi->dev; + struct device_node *np = cqspi->dev->of_node; + struct device *dev = cqspi->dev; cqspi->is_decoded_cs = of_property_read_bool(np, "cdns,is-decoded-cs"); @@ -1082,7 +1069,7 @@ static int cqspi_parse_dt(struct cqspi_st *cqspi) return 0; } -static int cqspi_setup_flash(struct device_d *dev, +static int cqspi_setup_flash(struct device *dev, struct cqspi_flash_pdata *f_pdata, struct device_node *np) { @@ -1112,7 +1099,7 @@ static int cqspi_setup_flash(struct device_d *dev, dev_set_name(nor->dev, np->name); - nor->dev->device_node = np; + nor->dev->of_node = np; nor->dev->id = DEVICE_ID_SINGLE; nor->dev->parent = dev; ret = register_device(nor->dev); @@ -1163,10 +1150,10 @@ static void cqspi_controller_init(struct cqspi_st *cqspi) cqspi_controller_enable(cqspi); } -static int cqspi_probe(struct device_d *dev) +static int cqspi_probe(struct device *dev) { struct resource *iores; - struct device_node *np = dev->device_node; + struct device_node *np = dev->of_node; struct cqspi_st *cqspi; struct cadence_qspi_platform_data *pdata = dev->platform_data; int ret; @@ -1203,28 +1190,18 @@ static int cqspi_probe(struct device_d *dev) if (IS_ERR(iores)) return PTR_ERR(iores); cqspi->iobase = IOMEM(iores->start); - if (IS_ERR(cqspi->iobase)) { - dev_err(dev, "dev_request_mem_region 0 failed\n"); - ret = PTR_ERR(cqspi->iobase); - goto probe_failed; - } iores = dev_request_mem_resource(dev, 1); if (IS_ERR(iores)) return PTR_ERR(iores); cqspi->ahb_base = IOMEM(iores->start); - if (IS_ERR(cqspi->ahb_base)) { - dev_err(dev, "dev_request_mem_region 0 failed\n"); - ret = PTR_ERR(cqspi->ahb_base); - goto probe_failed; - } cqspi_wait_idle(cqspi); cqspi_controller_init(cqspi); cqspi->current_cs = -1; cqspi->sclk = 0; - if (!dev->device_node) { + if (!dev->of_node) { struct cqspi_flash_pdata *f_pdata; f_pdata = &cqspi->f_pdata[0]; @@ -1234,7 +1211,7 @@ static int cqspi_probe(struct device_d *dev) goto probe_failed; } else { /* Get flash device data */ - for_each_available_child_of_node(dev->device_node, np) { + for_each_available_child_of_node(dev->of_node, np) { struct cqspi_flash_pdata *f_pdata; unsigned int cs; if (of_property_read_u32(np, "reg", &cs)) { @@ -1265,8 +1242,9 @@ static __maybe_unused struct of_device_id cqspi_dt_ids[] = { {.compatible = "cdns,qspi-nor",}, { /* end of table */ } }; +MODULE_DEVICE_TABLE(of, cqspi_dt_ids); -static struct driver_d cqspi_driver = { +static struct driver cqspi_driver = { .name = "cadence_qspi", .probe = cqspi_probe, .of_compatible = DRV_OF_COMPAT(cqspi_dt_ids), diff --git a/drivers/mtd/spi-nor/dw-ospi-nor.c b/drivers/mtd/spi-nor/dw-ospi-nor.c new file mode 100644 index 0000000000..897f4f49a9 --- /dev/null +++ b/drivers/mtd/spi-nor/dw-ospi-nor.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2018 Vincent Chardon, Kalray Inc. +// SPDX-FileCopyrightText: 2023 Jules Maselbas, Kalray Inc. + +#include <clock.h> +#include <common.h> +#include <driver.h> +#include <errno.h> +#include <init.h> +#include <io.h> +#include <linux/clk.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/spi-nor.h> +#include <of.h> +#include <spi/spi.h> + +/* Register offsets */ +#define DW_SPI_CTRL0 0x00 +#define DW_SPI_CTRL1 0x04 +#define DW_SPI_SSIENR 0x08 +#define DW_SPI_MWCR 0x0c +#define DW_SPI_SER 0x10 +#define DW_SPI_BAUDR 0x14 +#define DW_SPI_TXFTLR 0x18 +#define DW_SPI_RXFTLR 0x1c +#define DW_SPI_TXFLR 0x20 +#define DW_SPI_RXFLR 0x24 +#define DW_SPI_SR 0x28 +#define DW_SPI_IMR 0x2c +#define DW_SPI_ISR 0x30 +#define DW_SPI_RISR 0x34 +#define DW_SPI_TXOICR 0x38 +#define DW_SPI_RXOICR 0x3c +#define DW_SPI_RXUICR 0x40 +#define DW_SPI_MSTICR 0x44 +#define DW_SPI_ICR 0x48 +#define DW_SPI_DMACR 0x4c +#define DW_SPI_DMATDLR 0x50 +#define DW_SPI_DMARDLR 0x54 +#define DW_SPI_IDR 0x58 +#define DW_SPI_VERSION 0x5c +#define DW_SPI_DR 0x60 +#define DW_SPI_SPI_CTRL0 0xf4 + +/* Bit fields in CTRLR0 */ +#define SPI_DFS_OFFSET 0 +#define SPI_DFS_MASK 0x1f +#define SPI_DFS_8_BITS 0x7 + +#define SPI_FRF_OFFSET 6 +#define SPI_FRF_SPI 0x0 +#define SPI_FRF_SSP 0x1 +#define SPI_FRF_MICROWIRE 0x2 +#define SPI_FRF_RESV 0x3 + +#define SPI_MODE_OFFSET 8 +#define SPI_SCPH_OFFSET 8 +#define SPI_SCOL_OFFSET 9 + +#define SPI_TMOD_OFFSET 10 +#define SPI_TMOD_MASK (0x3 << SPI_TMOD_OFFSET) +#define SPI_TMOD_TR 0x0 /* xmit & recv */ +#define SPI_TMOD_TO 0x1 /* xmit only */ +#define SPI_TMOD_RO 0x2 /* recv only */ +#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ + +#define SPI_SLVOE_OFFSET 12 +#define SPI_SRL_OFFSET 13 +#define SPI_SSTE_OFFSET 14 + +#define SPI_CFS_OFFSET 16 +#define SPI_CFS_MASK (0xf << SPI_CFS_OFFSET) + +#define SPI_SPI_FRF_OFFSET 22 +#define SPI_SPI_FRF_MASK (0x3 << SPI_SPI_FRF_OFFSET) +#define SPI_STANDARD_FORMAT 0 +#define SPI_DUAL_FORMAT 1 +#define SPI_QUAD_FORMAT 2 +#define SPI_OCTAL_FORMAT 3 + +#define DW_SPI_CTRL1_NDF_MASK 0xffff + +#define SPI_TXFTLR_TXFTHR_OFFSET 16 + +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f +#define SR_BUSY BIT(0) +#define SR_TF_NOT_FULL BIT(1) +#define SR_TF_EMPT BIT(2) +#define SR_RF_NOT_EMPT BIT(3) +#define SR_RF_FULL BIT(4) +#define SR_TX_ERR BIT(5) +#define SR_DCOL BIT(6) + +/* Bit fields in ISR, IMR, RISR, 7 bits */ +#define SPI_INT_TXEI BIT(0) +#define SPI_INT_TXOI BIT(1) +#define SPI_INT_RXUI BIT(2) +#define SPI_INT_RXOI BIT(3) +#define SPI_INT_RXFI BIT(4) +#define SPI_INT_MSTI BIT(5) + +/* Bit fields in DMACR */ +#define SPI_DMA_RDMAE BIT(0) +#define SPI_DMA_TDMAE BIT(1) + +/* Bit fields in SPI_CTRL0 */ +#define SPI_SPI_CTRL0_INST_L8 (0x2 << 8) /* two bit value */ +#define SPI_SPI_CTRL0_WAIT_8_CYCLE (0x8 << 11)/* five bit value */ +#define SPI_SPI_CTRL0_EN_CLK_STRETCH BIT(30) + +#define SPI_SPI_CTRL0_ADDR_L_OFFSET 2 +#define SPI_SPI_CTRL0_ADDR_L_MASK (0xf << SPI_SPI_CTRL0_ADDR_L_OFFSET) +#define SPI_SPI_CTRL0_ADDR_L24 0x6 /* 3 bytes address */ +#define SPI_SPI_CTRL0_ADDR_L32 0x8 /* 4 bytes address */ + +/* TX/RX FIFO maximum size */ +#define TX_FIFO_MAX_SIZE 256 +#define RX_FIFO_MAX_SIZE 256 + +/* TX/RX interrupt level threshold, max is 256 */ +#define SPI_INT_THRESHOLD 32 + +#define DW_SPI_MAX_CHIPSELECT 16 + +struct dw_spi_flash_pdata { + struct mtd_info mtd; + struct spi_nor nor; + u32 clk_rate; + int cs; +}; + +static inline struct dw_spi_flash_pdata *to_flash_pdata(struct spi_nor *nor) +{ + return container_of(nor, struct dw_spi_flash_pdata, nor); +} + +struct dw_spi_nor { + struct device *dev; + struct clk *clk; + unsigned int sclk; + void __iomem *regs; + unsigned int master_ref_clk_hz; + bool clk_strech_en; + unsigned int tx_fifo_len; + int rx_fifo_len; + int supported_cs; + int current_cs; + struct dw_spi_flash_pdata f_pdata[DW_SPI_MAX_CHIPSELECT]; +}; + +static u32 dw_readl(struct dw_spi_nor *dws, u32 offset) +{ + return readl(dws->regs + offset); +} + +static void dw_writel(struct dw_spi_nor *dws, u32 offset, u32 val) +{ + writel(val, dws->regs + offset); +} + +static void dw_spi_enable_chip(struct dw_spi_nor *dws, int enable) +{ + dw_writel(dws, DW_SPI_SSIENR, (enable ? 1 : 0)); +} + +/* Disable IRQ bits */ +static void dw_spi_mask_intr(struct dw_spi_nor *dws, u32 mask) +{ + u32 new_mask; + + new_mask = dw_readl(dws, DW_SPI_IMR) & ~mask; + dw_writel(dws, DW_SPI_IMR, new_mask); +} + +/* + * This does disable the SPI controller, interrupts, and re-enable the + * controller back. Transmit and receive FIFO buffers are cleared when the + * device is disabled. + */ +static void dw_spi_reset_chip(struct dw_spi_nor *dw_spi) +{ + dw_spi_enable_chip(dw_spi, 0); + dw_spi_mask_intr(dw_spi, 0xff); + dw_spi_enable_chip(dw_spi, 1); +} + +static int dw_spi_set_cs(struct dw_spi_nor *dw_spi, int cs) +{ + if (cs > dw_spi->supported_cs) { + dev_err(dw_spi->dev, "invalid chip select\n"); + return -EINVAL; + } + + dw_spi_enable_chip(dw_spi, 0); + + if (cs == -1) /* no slave */ + dw_writel(dw_spi, DW_SPI_SER, 0); + else + dw_writel(dw_spi, DW_SPI_SER, BIT(cs)); + dw_spi->current_cs = cs; + + dw_spi_enable_chip(dw_spi, 1); + + return 0; +} + +static void dw_spi_hw_init(struct dw_spi_nor *dw_spi) +{ + u32 ctrl0; + u32 spi_ctrl0; + + dw_spi_reset_chip(dw_spi); + dw_spi_enable_chip(dw_spi, 0); + + /* the line will automatically toggle between consecutive data frame */ + ctrl0 = dw_readl(dw_spi, DW_SPI_CTRL0); + ctrl0 &= ~(SPI_DFS_MASK); + ctrl0 |= SPI_DFS_8_BITS; + ctrl0 &= ~(BIT(SPI_SSTE_OFFSET)); + dw_writel(dw_spi, DW_SPI_CTRL0, ctrl0); + + /* SPI_CTRL0 is initializtion */ + spi_ctrl0 = SPI_SPI_CTRL0_INST_L8; + spi_ctrl0 |= SPI_SPI_CTRL0_ADDR_L32 << SPI_SPI_CTRL0_ADDR_L_OFFSET; + spi_ctrl0 |= SPI_SPI_CTRL0_WAIT_8_CYCLE; + spi_ctrl0 |= SPI_SPI_CTRL0_EN_CLK_STRETCH; + + dw_writel(dw_spi, DW_SPI_SPI_CTRL0, spi_ctrl0); + + dw_spi_enable_chip(dw_spi, 1); +} + +static int dw_spi_of_get_flash_pdata(struct device *dev, + struct dw_spi_flash_pdata *f_pdata, + struct device_node *np) +{ + struct dw_spi_nor *dw_spi = dev->priv; + unsigned int max_clk_rate = dw_spi->master_ref_clk_hz / 2; + + if (!np) + return 0; + + if (of_property_read_u32(np, "spi-max-frequency", &f_pdata->clk_rate)) { + dev_err(dev, "couldn't determine spi-max-frequency\n"); + return -ENXIO; + } + + dev_dbg(dev, "spi-max-frequency = %u\n", f_pdata->clk_rate); + + /* SPI clock cannot go higher than half the master ref clock */ + if (f_pdata->clk_rate > max_clk_rate) { + f_pdata->clk_rate = max_clk_rate; + dev_warn(dev, "limiting SPI frequency to %u\n", + f_pdata->clk_rate); + } + + return 0; +} + +static int dw_spi_wait_not_busy(struct dw_spi_nor *dw_spi) +{ + if (wait_on_timeout(100 * MSECOND, + !(dw_readl(dw_spi, DW_SPI_SR) & SR_BUSY))) { + dev_err(dw_spi->dev, "Timeout, wait not busy.\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static bool dw_spi_tx_fifo_not_full(struct dw_spi_nor *dw_spi) +{ + return (dw_readl(dw_spi, DW_SPI_SR) & SR_TF_NOT_FULL) != 0; +} + +static bool dw_spi_tx_fifo_empty(struct dw_spi_nor *dw_spi) +{ + return (dw_readl(dw_spi, DW_SPI_SR) & SR_TF_EMPT) != 0; +} + +static bool dw_spi_rx_fifo_not_empty(struct dw_spi_nor *dw_spi) +{ + return (dw_readl(dw_spi, DW_SPI_SR) & SR_RF_NOT_EMPT) != 0; +} + +static int dw_spi_is_enhanced(enum spi_nor_protocol proto) +{ + return proto != SNOR_PROTO_1_1_1; +} + +static int dw_spi_rx_tx_fifo_overflow(struct dw_spi_nor *dw_spi) +{ + return dw_readl(dw_spi, DW_SPI_RISR) & (SPI_INT_RXOI | SPI_INT_TXOI); +} + +static int dw_spi_config_baudrate_div(struct dw_spi_nor *dws, unsigned int sclk) +{ + unsigned int div; + + dws->sclk = sclk; + div = dws->master_ref_clk_hz / sclk; + /* divisor value must be even */ + div += div % 2; + + dev_dbg(dws->dev, "configure clock divider (%u/%u) -> %u\n", + dws->master_ref_clk_hz, sclk, div); + dw_spi_enable_chip(dws, 0); + dw_writel(dws, DW_SPI_BAUDR, div); + dw_spi_enable_chip(dws, 1); + + if (dw_readl(dws, DW_SPI_BAUDR) != div) { + dev_err(dws->dev, "Unable to configure clock divider\n"); + return -EINVAL; + } + + return 0; +} + +static int dw_spi_prep_slave_cfg(struct spi_nor *nor) +{ + struct dw_spi_nor *dw_spi = nor->priv; + struct dw_spi_flash_pdata *f_pdata = to_flash_pdata(nor); + int ret; + + /* switch chip select */ + if (dw_spi->current_cs != f_pdata->cs) { + ret = dw_spi_set_cs(dw_spi, f_pdata->cs); + if (ret) + return ret; + } + + /* setup baudrate divisor */ + if (dw_spi->sclk != f_pdata->clk_rate) { + ret = dw_spi_config_baudrate_div(dw_spi, f_pdata->clk_rate); + if (ret) + return ret; + } + + return 0; +} + +static void dw_spi_set_ctrl0(struct dw_spi_nor *dw_spi, u8 tmod, u8 frf) +{ + u32 ctrl0; + + /* spi mode configuration */ + ctrl0 = dw_readl(dw_spi, DW_SPI_CTRL0); + ctrl0 &= ~(SPI_TMOD_MASK | SPI_SPI_FRF_MASK); + ctrl0 |= tmod << SPI_TMOD_OFFSET; + ctrl0 |= frf << SPI_SPI_FRF_OFFSET; + + dw_spi_enable_chip(dw_spi, 0); + dev_dbg(dw_spi->dev, "Setting ctrl0 to %x\n", ctrl0); + dw_writel(dw_spi, DW_SPI_CTRL0, ctrl0); + dw_spi_enable_chip(dw_spi, 1); +} + +static int dw_spi_prep(struct spi_nor *nor, u8 tmod, u8 frf) +{ + int ret; + struct dw_spi_nor *dw_spi = nor->priv; + + ret = dw_spi_prep_slave_cfg(nor); + if (ret) + return ret; + + dw_spi_set_ctrl0(dw_spi, tmod, frf); + + return 0; +} + +static int dw_spi_set_addr_len(struct spi_nor *nor) +{ + struct dw_spi_nor *dw_spi = nor->priv; + u32 val, addr_l; + + val = dw_readl(dw_spi, DW_SPI_SPI_CTRL0); + val &= ~SPI_SPI_CTRL0_ADDR_L_MASK; + + if (nor->addr_width == 3) { + addr_l = SPI_SPI_CTRL0_ADDR_L24; + } else if (nor->addr_width == 4) { + addr_l = SPI_SPI_CTRL0_ADDR_L32; + } else { + dev_err(nor->dev, "unsupported addr_width %d\n", + nor->addr_width); + return -EINVAL; + } + + val |= addr_l << SPI_SPI_CTRL0_ADDR_L_OFFSET; + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_SPI_CTRL0, val); + dw_spi_enable_chip(dw_spi, 1); + + return 0; +} + +static int dw_spi_prep_enhanced(struct spi_nor *nor, + enum spi_nor_protocol proto, u8 tmod) +{ + int ret; + u8 frf; + + switch (proto) { + case SNOR_PROTO_1_1_2: + dev_dbg(nor->dev, "dual mode\n"); + frf = SPI_DUAL_FORMAT; + break; + case SNOR_PROTO_1_1_4: + dev_dbg(nor->dev, "quad mode\n"); + frf = SPI_QUAD_FORMAT; + break; + default: + dev_err(nor->dev, "unsupported enhanced mode %d\n", + nor->read_proto); + return -EINVAL; + } + + ret = dw_spi_set_addr_len(nor); + if (ret) + return ret; + + return dw_spi_prep(nor, tmod, frf); +} + +static int dw_spi_prep_std(struct spi_nor *nor, u8 tmod) +{ + return dw_spi_prep(nor, tmod, SPI_STANDARD_FORMAT); +} + +static int dw_spi_read_enhanced(struct spi_nor *nor, const u8 opcode, + int address, u8 *rxbuf, size_t n_rx) +{ + struct dw_spi_nor *dw_spi = nor->priv; + u32 offset = 0; + int ret; + + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_CTRL1, n_rx - 1); + dw_spi_enable_chip(dw_spi, 1); + + ret = dw_spi_wait_not_busy(dw_spi); + if (ret) + return ret; + + /* send the opcode and the address */ + dw_writel(dw_spi, DW_SPI_DR, opcode); + dw_writel(dw_spi, DW_SPI_DR, address); + + while (n_rx) { + if (dw_spi_rx_fifo_not_empty(dw_spi)) { + rxbuf[offset++] = dw_readl(dw_spi, DW_SPI_DR); + n_rx--; + } + + /* check RX/TX overflow */ + if (dw_spi_rx_tx_fifo_overflow(dw_spi)) + return -EIO; + } + + return 0; +} + +static int dw_spi_read_std(struct spi_nor *nor, const u8 opcode, int address, + u8 *rxbuf, unsigned int n_rx) +{ + struct dw_spi_nor *dw_spi = nor->priv; + int tx_cnt = n_rx, rx_cnt = n_rx, skip_rx, cur_rx = 0; + int ret = 0, i, txfhr, rx_free; + u32 tmp_val; + + ret = dw_spi_wait_not_busy(dw_spi); + if (ret) + return ret; + + /* clear interrupts */ + dw_readl(dw_spi, DW_SPI_ICR); + + /* TX fifo must not became empty during the frame transfer: + * use TXFTHR (Transfert Start FIFO level) to avoid the frame + * to start during the first phase computation */ + skip_rx = 1 /* opcode */ + nor->addr_width; + txfhr = min_t(unsigned int, skip_rx + n_rx, dw_spi->tx_fifo_len) - 1; + rx_free = dw_spi->rx_fifo_len - skip_rx; + + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_TXFTLR, txfhr << SPI_TXFTLR_TXFTHR_OFFSET); + dw_writel(dw_spi, DW_SPI_RXFTLR, dw_spi->rx_fifo_len / 2); + dw_spi_enable_chip(dw_spi, 1); + + /* opcode phase */ + dw_writel(dw_spi, DW_SPI_DR, opcode); + + /* address phase in TR mode */ + for (i = nor->addr_width - 1; i >= 0; i--) + dw_writel(dw_spi, DW_SPI_DR, (address >> (8 * i)) & 0xff); + + while (rx_cnt) { + /* push dummy bytes to receive data */ + while (tx_cnt && dw_spi_tx_fifo_not_full(dw_spi) && + rx_free > 0) { + dw_writel(dw_spi, DW_SPI_DR, 0xff); + tx_cnt--; + rx_free--; + } + + if (dw_spi_rx_fifo_not_empty(dw_spi)) { + tmp_val = dw_readl(dw_spi, DW_SPI_DR); + rx_free++; + if (skip_rx) { + skip_rx--; + continue; + } + + rxbuf[cur_rx++] = tmp_val; + rx_cnt--; + } + if (dw_spi_rx_tx_fifo_overflow(dw_spi)) + return -EIO; + } + + return n_rx; +} + +static int dw_spi_wait_tx_end(struct dw_spi_nor *dw_spi) +{ + int res; + + /* As specified in ssi_user_guide p63 the BUSY bit cannot be polled + * immediately. As indicated in ssi_databook p40 the TFE bit shall + * be tested before testing busy bit + */ + res = wait_on_timeout(100 * MSECOND, dw_spi_tx_fifo_empty(dw_spi)); + if (res < 0) { + dev_err(dw_spi->dev, "SPI write failure, TX FIFO is never empty\n"); + return res; + } + + return dw_spi_wait_not_busy(dw_spi); +} + +static int dw_spi_write_enhanced(struct spi_nor *nor, u8 opcode, u32 address, + u8 *txbuf, unsigned int n_tx) +{ + struct dw_spi_nor *dw_spi = nor->priv; + size_t tx_cnt = 0; + + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_CTRL1, n_tx - 1); + dw_spi_enable_chip(dw_spi, 1); + + dw_writel(dw_spi, DW_SPI_DR, opcode); + dw_writel(dw_spi, DW_SPI_DR, address); + + /* send data */ + while (tx_cnt < n_tx) { + if (dw_spi_tx_fifo_not_full(dw_spi)) { + dw_writel(dw_spi, DW_SPI_DR, txbuf[tx_cnt]); + tx_cnt++; + } + } + + return dw_spi_wait_tx_end(dw_spi); +} + +static int dw_spi_write_std(struct spi_nor *nor, const u8 *opbuf, + unsigned int n_op, u8 *txbuf, unsigned int n_tx) +{ + struct dw_spi_nor *dw_spi = nor->priv; + int op_cnt = 0, tx_cnt = 0, txfhr; + + txfhr = min_t(unsigned int, dw_spi->tx_fifo_len, n_op + n_tx) - 1; + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_TXFTLR, txfhr << SPI_TXFTLR_TXFTHR_OFFSET); + dw_spi_enable_chip(dw_spi, 1); + + /* send opcodes */ + while (op_cnt < n_op) { + if (dw_spi_tx_fifo_not_full(dw_spi)) { + dw_writel(dw_spi, DW_SPI_DR, opbuf[op_cnt]); + op_cnt++; + } + } + + /* send data */ + while (tx_cnt < n_tx) { + if (dw_spi_tx_fifo_not_full(dw_spi)) { + dw_writel(dw_spi, DW_SPI_DR, txbuf[tx_cnt]); + tx_cnt++; + } + } + + return dw_spi_wait_tx_end(dw_spi); +} + +static int dw_spi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) +{ + int i, ret; + + ret = dw_spi_prep_std(nor, SPI_TMOD_TR); + if (ret) + return ret; + + ret = dw_spi_read_std(nor, opcode, -1, buf, len); + + dev_dbg(nor->dev, "read_reg opcode 0x%02x: ", opcode); + for (i = 0; i < len; i++) + pr_debug("%02x ", buf[i]); + pr_debug("\n"); + + return ret; +} + +static int dw_spi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) +{ + int i, ret; + + dev_dbg(nor->dev, "write_reg opcode 0x%02x: ", opcode); + for (i = 0; i < len; i++) + pr_debug("%02x ", buf[i]); + pr_debug("\n"); + + ret = dw_spi_prep_std(nor, SPI_TMOD_TO); + if (ret) + return ret; + + return dw_spi_write_std(nor, &opcode, 1, buf, len); +} + +static void dw_spi_write(struct spi_nor *nor, loff_t to, + size_t len, size_t *retlen, const u_char *buf) +{ + u8 opcode[8]; + unsigned int opcode_len = 0; + int i, ret; + struct dw_spi_nor *dw_spi = nor->priv; + + *retlen = 0; + dev_dbg(dw_spi->dev, "write %zu bytes at @0x%llx\n", len, to); + + if (dw_spi_is_enhanced(nor->write_proto)) { + if (dw_spi_prep_enhanced(nor, nor->write_proto, SPI_TMOD_TO)) + return; + + ret = dw_spi_write_enhanced(nor, nor->program_opcode, to, + (u8 *)buf, len); + } else { + if (dw_spi_prep_std(nor, SPI_TMOD_TO)) + return; + + opcode[0] = nor->program_opcode; + opcode_len = 1 + nor->addr_width; + for (i = 0; i < nor->addr_width; i++) + opcode[1 + i] = + (to >> (8 * (nor->addr_width - 1 - i))) & 0xff; + + ret = dw_spi_write_std(nor, opcode, opcode_len, (u8 *)buf, len); + } + + if (ret == 0) + *retlen = len; +} + +static int dw_spi_read(struct spi_nor *nor, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + struct dw_spi_nor *dw_spi = nor->priv; + size_t to_read, orig_len = len; + u8 *ptr = (u8 *)buf; + loff_t offset = from; + int ret = 0, enhanced = dw_spi_is_enhanced(nor->read_proto); + size_t chunk; + + *retlen = 0; + dev_dbg(nor->dev, "read %zu bytes from @0x%llx\n", len, from); + + if (enhanced) + ret = dw_spi_prep_enhanced(nor, nor->read_proto, SPI_TMOD_RO); + else + ret = dw_spi_prep_std(nor, SPI_TMOD_TR); + if (ret) + return ret; + + /* + * If clock stretching is not supported, we have no way to prevent RX + * overflow except reducing the number received data to the size of the + * RX fifo + */ + if (dw_spi->clk_strech_en && enhanced) + chunk = DW_SPI_CTRL1_NDF_MASK; + else + chunk = dw_spi->rx_fifo_len; + + while (len) { + to_read = min(chunk, len); + + if (enhanced) + ret = dw_spi_read_enhanced(nor, nor->read_opcode, + offset, ptr, to_read); + else + ret = dw_spi_read_std(nor, nor->read_opcode, + offset, ptr, to_read); + if (ret < 0) + return ret; + + offset += to_read; + ptr += to_read; + len -= to_read; + } + *retlen = orig_len; + + return ret; +} + +static int dw_spi_erase(struct spi_nor *nor, loff_t offs) +{ + int ret = 0; + int i; + u8 buf[SPI_NOR_MAX_ADDR_WIDTH]; /* addr is 3 or 4 bytes */ + + dev_dbg(nor->dev, "erase(%0x) @0x%llx\n", nor->erase_opcode, offs); + + for (i = nor->addr_width - 1; i >= 0; i--) { + buf[i] = offs & 0xff; + offs >>= 8; + } + + ret = dw_spi_prep_std(nor, SPI_TMOD_TO); + if (ret) + return ret; + + /* Caller is responsible for enabling write, + * only send the erase sector command */ + ret = nor->write_reg(nor, nor->erase_opcode, buf, + nor->addr_width); + + /* Caller is responsible to wait for operation completion */ + return ret; +} + +static int dw_spi_setup_flash(struct device *dev, + struct dw_spi_flash_pdata *f_pdata, + struct device_node *np) +{ + const struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_READ_1_1_2 | + SNOR_HWCAPS_READ_1_1_4 | + SNOR_HWCAPS_PP | + SNOR_HWCAPS_PP_1_1_4, + }; + struct dw_spi_nor *dw_spi = dev->priv; + struct mtd_info *mtd = &f_pdata->mtd; + struct spi_nor *nor = &f_pdata->nor; + int ret; + + ret = dw_spi_of_get_flash_pdata(dev, f_pdata, np); + if (ret) + goto probe_failed; + + nor->dev = kzalloc(sizeof(*nor->dev), GFP_KERNEL); + if (!nor->dev) + return -ENOMEM; + + dev_set_name(nor->dev, np->name); + + nor->dev->of_node = np; + nor->dev->id = DEVICE_ID_SINGLE; + nor->dev->parent = dev; + ret = register_device(nor->dev); + if (ret) + return ret; + + mtd->priv = nor; + mtd->dev.parent = nor->dev; + nor->mtd = mtd; + nor->priv = dw_spi; + + nor->read_reg = dw_spi_read_reg; + nor->write_reg = dw_spi_write_reg; + nor->read = dw_spi_read; + nor->write = dw_spi_write; + nor->erase = dw_spi_erase; + + ret = spi_nor_scan(nor, NULL, &hwcaps, false); + if (ret) + goto probe_failed; + + ret = add_mtd_device(mtd, NULL, DEVICE_ID_DYNAMIC); + if (ret) + goto probe_failed; + + return 0; + +probe_failed: + dev_err(dev, "probing for flashchip failed\n"); + return ret; +} + +static void dw_spi_detect_hw_params(struct dw_spi_nor *dw_spi) +{ + int fifo; + + /* Detect supported slave number */ + dw_spi_enable_chip(dw_spi, 0); + dw_writel(dw_spi, DW_SPI_SER, 0xffff); + dw_spi_enable_chip(dw_spi, 1); + dw_spi->supported_cs = hweight32(dw_readl(dw_spi, DW_SPI_SER)); + + dw_spi_set_cs(dw_spi, -1); + dw_spi->sclk = 0; + + /* Detect the FIFO depth */ + dw_spi_enable_chip(dw_spi, 0); + for (fifo = 1; fifo < TX_FIFO_MAX_SIZE; fifo++) { + dw_writel(dw_spi, DW_SPI_TXFTLR, fifo); + if (fifo != dw_readl(dw_spi, DW_SPI_TXFTLR)) + break; + } + dw_writel(dw_spi, DW_SPI_TXFTLR, 0); + dw_spi->tx_fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dw_spi->dev, "Detected TX FIFO size: %u bytes\n", + dw_spi->tx_fifo_len); + + for (fifo = 1; fifo < RX_FIFO_MAX_SIZE; fifo++) { + dw_writel(dw_spi, DW_SPI_RXFTLR, fifo); + if (fifo != dw_readl(dw_spi, DW_SPI_RXFTLR)) + break; + } + dw_writel(dw_spi, DW_SPI_RXFTLR, 0); + dw_spi->rx_fifo_len = (fifo == 1) ? 0 : fifo; + dev_dbg(dw_spi->dev, "Detected RX FIFO size: %u bytes\n", + dw_spi->tx_fifo_len); + dw_spi_enable_chip(dw_spi, 1); +} + +static int dw_spi_probe(struct device *dev) +{ + struct dw_spi_nor *dw_spi; + struct resource *iores; + struct device_node *np = dev->of_node; + struct dw_spi_flash_pdata *f_pdata = NULL; + int ret; + + dw_spi = kzalloc(sizeof(*dw_spi), GFP_KERNEL); + if (!dw_spi) + return -ENOMEM; + + dw_spi->dev = dev; + dev->priv = dw_spi; + + dw_spi->clk = clk_get(dev, NULL); + if (IS_ERR(dw_spi->clk)) { + dev_err(dev, "unable to get spi clk\n"); + ret = PTR_ERR(dw_spi->clk); + goto probe_failed; + } + + dw_spi->master_ref_clk_hz = clk_get_rate(dw_spi->clk); + if (dw_spi->master_ref_clk_hz == 0) { + dev_err(dev, "unable to get spi clk rate\n"); + ret = PTR_ERR(dw_spi->clk); + goto probe_failed; + } + + clk_enable(dw_spi->clk); + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + dev_err(dev, "dev_request_mem_region failed\n"); + ret = PTR_ERR(iores); + goto probe_failed; + } + dw_spi->regs = IOMEM(iores->start); + + dw_spi_hw_init(dw_spi); + + dw_spi_detect_hw_params(dw_spi); + + /* Get clock stretching mode support from device-tree */ + dw_spi->clk_strech_en = of_property_read_bool(dev->of_node, + "clock-stretching"); + dev_dbg(dev, "clock stretching %s supported\n", + dw_spi->clk_strech_en ? "is" : "is not"); + + /* Get flash device data */ + for_each_available_child_of_node(dev->of_node, np) { + unsigned int cs; + + if (of_property_read_u32(np, "reg", &cs)) { + dev_err(dev, "couldn't determine chip select\n"); + ret = -ENXIO; + goto probe_failed; + } + if (cs > dw_spi->supported_cs) { + dev_err(dev, "chip select %d out of range (%d supported)\n", + cs, dw_spi->supported_cs); + ret = -ENXIO; + goto probe_failed; + } + f_pdata = &dw_spi->f_pdata[cs]; + f_pdata->cs = cs; + + ret = dw_spi_setup_flash(dev, f_pdata, np); + if (ret) + goto probe_failed; + } + + dev_info(dev, "Synopsys Octal SPI NOR flash driver\n"); + return 0; + +probe_failed: + dev_err(dev, "probe failed: %d\n", ret); + return ret; +} + +static __maybe_unused struct of_device_id dw_spi_dt_ids[] = { + { .compatible = "snps,ospi-nor", }, + { /* sentinel */ } +}; + +static struct driver dw_spi_driver = { + .name = "dw_ospi_nor", + .probe = dw_spi_probe, + .of_compatible = DRV_OF_COMPAT(dw_spi_dt_ids), +}; +device_platform_driver(dw_spi_driver); diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 3ced84049d..1773db09a1 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Based on m25p80.c, by Mike Lavender (mike@steroidmicros.com), with * influence from lart.c (Abraham Van Der Merwe) and mtd_dataflash.c * * Copyright (C) 2005, Intec Automation Inc. * Copyright (C) 2014, Freescale Semiconductor, Inc. - * - * This code 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 <clock.h> @@ -22,10 +19,8 @@ #include <linux/mtd/cfi.h> #include <linux/mtd/spi-nor.h> #include <of.h> -#include <spi/flash.h> #define SPI_NOR_MAX_ID_LEN 6 -#define SPI_NOR_MAX_ADDR_WIDTH 4 /* * For everything but full-chip erase; probably could be much smaller, but kept @@ -87,6 +82,7 @@ struct flash_info { #define USE_CLSR BIT(14) /* use CLSR command */ #define SPI_NOR_OCTAL_READ BIT(15) /* Flash supports Octal Read */ #define UNLOCK_GLOBAL_BLOCK BIT(16) /* Unlock global block protection */ +#define SPI_NOR_QUAD_WRITE BIT(17) /* Flash supports Quad Write */ }; enum spi_nor_read_command_index { @@ -683,7 +679,7 @@ static const struct spi_device_id spi_nor_ids[] = { { "is25lp016d", INFO(0x9d6015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "is25lp01g", INFO(0x9d601b, 0, 64 * 1024, 2048, - SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_QUAD_WRITE) }, { "is25lp080d", INFO(0x9d6014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "is25lp032", INFO(0x9d6016, 0, 64 * 1024, 64, @@ -700,6 +696,8 @@ static const struct spi_device_id spi_nor_ids[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "is25wp128", INFO(0x9d7018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "is25wp256", INFO(0x9d7019, 0, 64 * 1024, 512, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "is25lq128", INFO(0x9d6018, 0, 64 * 1024, 256, 0) }, /* Macronix */ @@ -730,6 +728,7 @@ static const struct spi_device_id spi_nor_ids[] = { { "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256, SPI_NOR_QUAD_READ) }, { "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, SPI_NOR_QUAD_READ) }, { "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_QUAD_READ) }, + { "mt25qu256a", INFO(0x20bb19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_QUAD_READ) }, { "n25q512a", INFO(0x20bb20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) }, { "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) }, { "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) }, @@ -868,11 +867,15 @@ static const struct spi_device_id spi_nor_ids[] = { { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) }, { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, { "w25q64dw", INFO(0xef6017, 0, 64 * 1024, 128, SECT_4K) }, + { "w25q512nwq", INFO(0xef6020, 0, 512 * 1024, 128, SECT_4K) }, { "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) }, { "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }, { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, { "w25q128", INFO(0xef7018, 0, 64 * 1024, 256, SECT_4K) }, - { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) }, + { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | + SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_QUAD_WRITE | SPI_NOR_4B_OPCODES) }, + { "w25q256jwm", INFO(0xef8019, 0, 64 * 1024, 512, SECT_4K | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, /* Catalyst / On Semiconductor -- non-JEDEC */ { "cat25c11", CAT25_INFO( 16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, @@ -1167,6 +1170,13 @@ static int spi_nor_init_params(struct spi_nor *nor, spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP], SPINOR_OP_PP, SNOR_PROTO_1_1_1); + if (info->flags & SPI_NOR_QUAD_WRITE) { + params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4; + spi_nor_set_pp_settings( + ¶ms->page_programs[SNOR_CMD_PP_1_1_4], + SPINOR_OP_PP_1_1_4, SNOR_PROTO_1_1_4); + } + if (info->flags & UNLOCK_GLOBAL_BLOCK) { int err; @@ -1370,9 +1380,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, struct spi_nor_flash_parameter params; const struct spi_device_id *id = NULL; struct flash_info *info; - struct device_d *dev = nor->dev; + struct device *dev = nor->dev; struct mtd_info *mtd = nor->mtd; - struct device_node *np = dev->device_node; + struct device_node *np = dev->of_node; int ret; int i; diff --git a/drivers/mtd/ubi/Kconfig b/drivers/mtd/ubi/Kconfig index ed2f13d14c..f6d939e916 100644 --- a/drivers/mtd/ubi/Kconfig +++ b/drivers/mtd/ubi/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig MTD_UBI tristate "Enable UBI - Unsorted block images" select CRC32 diff --git a/drivers/mtd/ubi/Makefile b/drivers/mtd/ubi/Makefile index 33ac39026c..0a9eee1116 100644 --- a/drivers/mtd/ubi/Makefile +++ b/drivers/mtd/ubi/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_MTD_UBI) += ubi.o ubi-y += vtbl.o vmt.o upd.o build.o barebox.o kapi.o eba.o io.o wl.o attach.o diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index eee0e55ba9..0e7c61e053 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/barebox.c b/drivers/mtd/ubi/barebox.c index 781061d9a7..7ae5b4c4b4 100644 --- a/drivers/mtd/ubi/barebox.c +++ b/drivers/mtd/ubi/barebox.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <fcntl.h> #include <fs.h> diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 75fdee3692..94b4231aad 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (c) Nokia Corporation, 2007 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём), * Frank Haverkamp */ diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c index 75d6b69f09..62b988ee80 100644 --- a/drivers/mtd/ubi/debug.c +++ b/drivers/mtd/ubi/debug.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h index 511e454364..feade84d6b 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -1,16 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c index 071bbca236..e626253364 100644 --- a/drivers/mtd/ubi/eba.c +++ b/drivers/mtd/ubi/eba.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/fastmap-wl.c b/drivers/mtd/ubi/fastmap-wl.c index 08593ea23a..33fae1a2f9 100644 --- a/drivers/mtd/ubi/fastmap-wl.c +++ b/drivers/mtd/ubi/fastmap-wl.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012 Linutronix GmbH * Copyright (c) 2014 sigma star gmbh * Author: Richard Weinberger <richard@nod.at> - * - * 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. - * - * 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. - * */ /** diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 32b60ccad8..31c90c3d71 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012 Linutronix GmbH * Author: Richard Weinberger <richard@nod.at> - * - * 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. - * - * 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 "ubi.h" diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 78458b58e1..88df185789 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (c) Nokia Corporation, 2006, 2007 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/kapi.c b/drivers/mtd/ubi/kapi.c index e1781f3f20..ee35c6f7a5 100644 --- a/drivers/mtd/ubi/kapi.c +++ b/drivers/mtd/ubi/kapi.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/misc.c b/drivers/mtd/ubi/misc.c index d9a8d792a4..e5a8be82d3 100644 --- a/drivers/mtd/ubi/misc.c +++ b/drivers/mtd/ubi/misc.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/ubi-barebox.h b/drivers/mtd/ubi/ubi-barebox.h index 5b2d4a72c8..60ad9014e1 100644 --- a/drivers/mtd/ubi/ubi-barebox.h +++ b/drivers/mtd/ubi/ubi-barebox.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Header file for UBI support for U-Boot * @@ -5,10 +6,6 @@ * * Copyright (C) 2005-2007 Samsung Electronics * Kyungmin Park <kyungmin.park@samsung.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. */ #ifndef __UBOOT_UBI_H diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 7d07bbf197..fd64790fb8 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -1,17 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (c) Nokia Corporation, 2006, 2007 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ @@ -328,7 +319,7 @@ struct ubi_eba_leb_desc { * the moment or is damaged because of an unclean reboot. */ struct ubi_volume { - struct device_d dev; + struct device dev; struct cdev cdev; struct ubi_device *ubi; int vol_id; @@ -525,7 +516,7 @@ struct ubi_debug_info { */ struct ubi_device { struct cdev cdev; - struct device_d dev; + struct device dev; int ubi_num; char ubi_name[sizeof(UBI_NAME_STR)+5]; int vol_count; diff --git a/drivers/mtd/ubi/upd.c b/drivers/mtd/ubi/upd.c index 609c789ae4..bbf6179698 100644 --- a/drivers/mtd/ubi/upd.c +++ b/drivers/mtd/ubi/upd.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (c) Nokia Corporation, 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) * * Jan 2007: Alexander Schmidt, hacked per-volume update. diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c index 99da79171b..8199f35a8d 100644 --- a/drivers/mtd/ubi/vmt.c +++ b/drivers/mtd/ubi/vmt.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c index 6959564a13..e5f1d88a7d 100644 --- a/drivers/mtd/ubi/vtbl.c +++ b/drivers/mtd/ubi/vtbl.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (c) Nokia Corporation, 2006, 2007 * - * 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. - * * Author: Artem Bityutskiy (Битюцкий Артём) */ diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 013ba3e1ff..d69039993f 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) International Business Machines Corp., 2006 * - * 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. - * * Authors: Artem Bityutskiy (Битюцкий Артём), Thomas Gleixner */ diff --git a/drivers/mtd/ubi/wl.h b/drivers/mtd/ubi/wl.h index 60168116db..4d34daf017 100644 --- a/drivers/mtd/ubi/wl.h +++ b/drivers/mtd/ubi/wl.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef UBI_WL_H #define UBI_WL_H #ifdef CONFIG_MTD_UBI_FASTMAP |