Mikrotik guys doesn't use a NAND controller embedded in SoC on this board. Instead they control NAND through SoC gpio lines. Also NAND data and control lines are multiplexed by a latch. More painful is that the latch lines that are not used for NAND control lines, are used for two LEDs and a serial register nCS. So, we need a MFD driver that control access to these shared lines.
Signed-off-by: Denis Kalashnikov <[email protected]> --- .../files/drivers/mtd/nand/raw/nand_rb91x.c | 432 ++++++++++++++++++ target/linux/ath79/mikrotik/config-default | 1 + .../patches-5.4/939-mikrotik-rb91x.patch | 21 + 3 files changed, 454 insertions(+) create mode 100644 target/linux/ath79/files/drivers/mtd/nand/raw/nand_rb91x.c diff --git a/target/linux/ath79/files/drivers/mtd/nand/raw/nand_rb91x.c b/target/linux/ath79/files/drivers/mtd/nand/raw/nand_rb91x.c new file mode 100644 index 0000000000..c9d5072547 --- /dev/null +++ b/target/linux/ath79/files/drivers/mtd/nand/raw/nand_rb91x.c @@ -0,0 +1,432 @@ +/* + * NAND flash driver for the MikroTik RouterBOARD 91x series + * + * Main part is copied from original driver written by Gabor Juhos. + * + * Copyright (C) 2013-2014 Gabor Juhos <[email protected]> + * + * 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. + */ + +/* + * WARNING: to speed up NAND reading/writing we are working with SoC GPIO + * controller registers directly -- not through standard GPIO API. + */ + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/mtd/rawnand.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/version.h> + +/* TODO: ar71xx regs (SoC GPIO controller ones) should be get from DTS? */ +#include <asm/mach-ath79/ar71xx_regs.h> +//#include <asm/mach-ath79/ath79.h> + +#include <mfd/rb91x-ngl.h> + +#define DRIVER_NAME "rb91x-nand" + +static void __iomem *ath79_gpio_base; + +#define DRV_DESC "MikrotTik RB91x NAND flash driver" + +#define NAND_LOW_DATA_MASK 0x1f +#define NAND_HIGH_DATA_MASK 0xe0 +#define NAND_HIGH_DATA_SHIFT 8 + +struct drvdata { + struct nand_chip chip; + struct device *dev; + struct rb91x_ngl *ngl; + + /* Bit masks */ + u32 nand_rdy_bit; + u32 nand_nrw_bit; + u32 nand_data_bits; + u32 nand_input_bits; + u32 nand_output_bits; +}; + +static inline struct drvdata *mtd_to_drvdata(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + return container_of(chip, struct drvdata, chip); +} + +static struct mtd_info *drvdata_to_mtd(struct drvdata *p) +{ + return nand_to_mtd(&p->chip); +} + +static int ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + switch (section) { + case 0: + oobregion->offset = 8; + oobregion->length = 3; + return 0; + case 1: + oobregion->offset = 13; + oobregion->length = 3; + return 0; + default: + return -ERANGE; + } +} + +static int ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + switch (section) { + case 0: + oobregion->offset = 0; + oobregion->length = 4; + return 0; + case 1: + oobregion->offset = 4; + oobregion->length = 1; + return 0; + case 2: + oobregion->offset = 6; + oobregion->length = 2; + return 0; + case 3: + oobregion->offset = 11; + oobregion->length = 2; + return 0; + default: + return -ERANGE; + } +} + +static const struct mtd_ooblayout_ops nand_ecclayout_ops = { + .ecc = ooblayout_ecc, + .free = ooblayout_free, +}; + +/* TODO: Should be in DTS */ +static struct mtd_partition nand_partitions[] = { + { + .name = "booter", + .offset = 0, + .size = (256 * 1024), + .mask_flags = MTD_WRITEABLE, + }, { + .name = "kernel", + .offset = (256 * 1024), + .size = (4 * 1024 * 1024) - (256 * 1024), + }, { + .name = "ubi", + .offset = MTDPART_OFS_NXTBLK, + .size = MTDPART_SIZ_FULL, + }, +}; + +static void nand_write(struct drvdata *drvdata, + const u8 *buf, + unsigned len) +{ + void __iomem *base = ath79_gpio_base; + u32 oe_reg; + u32 out_reg; + u32 out; + unsigned i; + struct rb91x_ngl *ngl = drvdata->ngl; + + ngl->latch_lock(ngl); + + oe_reg = __raw_readl(base + AR71XX_GPIO_REG_OE); + out_reg = __raw_readl(base + AR71XX_GPIO_REG_OUT); + + /* set data lines to output mode */ + __raw_writel(oe_reg & ~(drvdata->nand_data_bits | drvdata->nand_nrw_bit), + base + AR71XX_GPIO_REG_OE); + + out = out_reg & ~(drvdata->nand_data_bits | drvdata->nand_nrw_bit); + for (i = 0; i != len; i++) { + u32 data; + + data = (buf[i] & NAND_HIGH_DATA_MASK) << + NAND_HIGH_DATA_SHIFT; + data |= buf[i] & NAND_LOW_DATA_MASK; + data |= out; + __raw_writel(data, base + AR71XX_GPIO_REG_OUT); + + /* deactivate WE line */ + data |= drvdata->nand_nrw_bit; + __raw_writel(data, base + AR71XX_GPIO_REG_OUT); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OUT); + } + + /* restore registers */ + __raw_writel(out_reg, base + AR71XX_GPIO_REG_OUT); + __raw_writel(oe_reg, base + AR71XX_GPIO_REG_OE); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OUT); + + ngl->latch_unlock(ngl); +} + +static void nand_read(struct drvdata *drvdata, + u8 *read_buf, + unsigned len) +{ + void __iomem *base = ath79_gpio_base; + u32 oe_reg; + u32 out_reg; + unsigned i; + struct rb91x_ngl *ngl = drvdata->ngl; + + /* enable read mode */ + ngl->gpio_set_value(ngl, RB91X_NGL_NAND_READ, 1); + + ngl->latch_lock(ngl); + + /* save registers */ + oe_reg = __raw_readl(base + AR71XX_GPIO_REG_OE); + out_reg = __raw_readl(base + AR71XX_GPIO_REG_OUT); + + /* set data lines to input mode */ + __raw_writel(oe_reg | drvdata->nand_data_bits, + base + AR71XX_GPIO_REG_OE); + + for (i = 0; i < len; i++) { + u32 in; + u8 data; + + /* activate RE line */ + __raw_writel(drvdata->nand_nrw_bit, base + AR71XX_GPIO_REG_CLEAR); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_CLEAR); + + /* read input lines */ + in = __raw_readl(base + AR71XX_GPIO_REG_IN); + + /* deactivate RE line */ + __raw_writel(drvdata->nand_nrw_bit, base + AR71XX_GPIO_REG_SET); + + data = (in & NAND_LOW_DATA_MASK); + data |= (in >> NAND_HIGH_DATA_SHIFT) & + NAND_HIGH_DATA_MASK; + + read_buf[i] = data; + } + + /* restore registers */ + __raw_writel(out_reg, base + AR71XX_GPIO_REG_OUT); + __raw_writel(oe_reg, base + AR71XX_GPIO_REG_OE); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OUT); + + + ngl->latch_unlock(ngl); + + /* disable read mode */ + ngl->gpio_set_value(ngl, RB91X_NGL_NAND_READ, 0); + +} + +static int dev_ready(struct nand_chip *chip) +{ + struct rb91x_ngl *ngl = ((struct drvdata *)chip->priv)->ngl; + + return ngl->gpio_get_value(ngl, RB91X_NGL_NAND_RDY); +} + +static void cmd_ctrl(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + struct drvdata *drvdata = chip->priv; + struct rb91x_ngl *ngl = drvdata->ngl; + + if (ctrl & NAND_CTRL_CHANGE) { + ngl->gpio_set_value(ngl, RB91X_NGL_NAND_CLE, + (ctrl & NAND_CLE) ? 1 : 0); + ngl->gpio_set_value(ngl, RB91X_NGL_NAND_ALE, + (ctrl & NAND_ALE) ? 1 : 0); + ngl->gpio_set_value(ngl, RB91X_NGL_NAND_NCE, + (ctrl & NAND_NCE) ? 0 : 1); + } + + if (cmd != NAND_CMD_NONE) { + u8 t = cmd; + + nand_write(drvdata, &t, 1); + } +} + +static u8 read_byte(struct nand_chip *chip) +{ + u8 data = 0xff; + + nand_read(chip->priv, &data, 1); + + return data; +} + +static void read_buf(struct nand_chip *chip, u8 *buf, int len) +{ + nand_read(chip->priv, buf, len); +} + +static void write_buf(struct nand_chip *chip, const u8 *buf, int len) +{ + nand_write(chip->priv, buf, len); +} + +static int probe(struct platform_device *pdev) +{ + struct drvdata *drvdata; + struct mtd_info *mtd; + int ret, i; + struct device *dev = &pdev->dev, *parent = dev->parent; + struct rb91x_ngl *ngl; + + if (!parent) + return -ENODEV; + + pr_info(DRV_DESC "\n"); + + /* TODO: Should be get from DTS */ + ath79_gpio_base = ioremap(AR71XX_GPIO_BASE, AR71XX_GPIO_SIZE); + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + platform_set_drvdata(pdev, drvdata); + + ngl = dev_get_drvdata(parent); + drvdata->ngl = ngl; + + drvdata->dev = &pdev->dev; + + drvdata->chip.priv = drvdata; + + drvdata->chip.legacy.cmd_ctrl = cmd_ctrl; + drvdata->chip.legacy.dev_ready = dev_ready; + drvdata->chip.legacy.read_byte = read_byte; + drvdata->chip.legacy.write_buf = write_buf; + drvdata->chip.legacy.read_buf = read_buf; + + drvdata->chip.legacy.chip_delay = 25; + drvdata->chip.ecc.mode = NAND_ECC_SOFT; + drvdata->chip.ecc.algo = NAND_ECC_HAMMING; + drvdata->chip.options = NAND_NO_SUBPAGE_WRITE; + + /* + * WARNING: To speed up NAND reading/writing, we work with + * SoC GPIO controller registers directly -- not through gpio API. + * So, we suppose that data gpios and nRW and RDY gpios are + * SoC gpios and its base is 0, another words: they are equal + * to bits in the SoC GPIO controller registers. + */ + + if (ngl->gpio[RB91X_NGL_NAND_RDY] >= 32) { + dev_err(dev, "NAND RDY GPIO must be < 32\n"); + return -EINVAL; + } + drvdata->nand_rdy_bit = BIT(ngl->gpio[RB91X_NGL_NAND_RDY]); + + if (ngl->gpio[RB91X_NGL_NAND_NRW] >= 32) { + dev_err(dev, "NAND NRW GPIO must be < 32\n"); + return -EINVAL; + } + drvdata->nand_nrw_bit = BIT(ngl->gpio[RB91X_NGL_NAND_NRW]); + + drvdata->nand_data_bits = 0; + for (i = 0; i < ngl->nand_datas_count(ngl); i++) { + if (ngl->gpio[RB91X_NGL_NAND_DATA0 + i] >= 32) { + dev_err(dev, "NAND Data GPIO must be < 32\n"); + return -EINVAL; + } + drvdata->nand_data_bits |= + BIT(ngl->gpio[RB91X_NGL_NAND_DATA0 + i]); + } + + drvdata->nand_input_bits = drvdata->nand_data_bits | + drvdata->nand_rdy_bit; + drvdata->nand_output_bits = drvdata->nand_data_bits | + drvdata->nand_nrw_bit; + + /* Unlock latch */ + ngl->gpio_direction_output(ngl, RB91X_NGL_NLE, 1); + + ngl->gpio_direction_output(ngl, RB91X_NGL_NAND_NCE, 1); + ngl->gpio_direction_output(ngl, RB91X_NGL_NAND_NRW, 1); + ngl->gpio_direction_output(ngl, RB91X_NGL_NAND_CLE, 0); + ngl->gpio_direction_output(ngl, RB91X_NGL_NAND_ALE, 0); + ngl->gpio_direction_output(ngl, RB91X_NGL_NAND_READ, 0); + ngl->gpio_direction_input(ngl, RB91X_NGL_NAND_RDY); + + mtd = drvdata_to_mtd(drvdata); + mtd->owner = THIS_MODULE; + ret = nand_scan(&drvdata->chip, 1); + if (ret) { + dev_err(drvdata->dev, "nand_scan() failed: %d\n", ret); + return ret; + } + + if (mtd->writesize == 512) + mtd_set_ooblayout(mtd, &nand_ecclayout_ops); + + ret = mtd_device_register(mtd, nand_partitions, + ARRAY_SIZE(nand_partitions)); + if (ret) { + dev_err(drvdata->dev, "mtd_device_register() failed: %d\n", + ret); + goto err_release_nand; + } + + return 0; + +err_release_nand: + nand_release(&drvdata->chip); + return ret; +} + +static int remove(struct platform_device *pdev) +{ + struct drvdata *drvdata = platform_get_drvdata(pdev); + + nand_release(&drvdata->chip); + + return 0; +} + +static const struct of_device_id match[] = { + { .compatible = "mikrotik,rb91x-nand" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, match); + +static struct platform_driver rb91x_nand_driver = { + .probe = probe, + .remove = remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = match, + }, +}; + +module_platform_driver(rb91x_nand_driver); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos <[email protected]>"); +MODULE_AUTHOR("Denis Kalashnikov <[email protected]>"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/ath79/mikrotik/config-default b/target/linux/ath79/mikrotik/config-default index 92b7824eba..df5442d6b3 100644 --- a/target/linux/ath79/mikrotik/config-default +++ b/target/linux/ath79/mikrotik/config-default @@ -17,6 +17,7 @@ CONFIG_MTD_NAND_AR934X=y CONFIG_MTD_NAND_CORE=y CONFIG_MTD_NAND_ECC=y CONFIG_MTD_NAND_RB4XX=y +CONFIG_MTD_NAND_RB91X=y CONFIG_MTD_RAW_NAND=y CONFIG_MTD_ROUTERBOOT_PARTS=y CONFIG_MTD_SPI_NAND=y diff --git a/target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch b/target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch index f2135d486b..726dcdf973 100644 --- a/target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch +++ b/target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch @@ -19,6 +19,27 @@ obj-$(CONFIG_MFD_RB4XX_CPLD) += rb4xx-cpld.o + +obj-$(CONFIG_MFD_RB91X_NGL) += rb91x-ngl.o +--- a/drivers/mtd/nand/raw/Kconfig ++++ b/drivers/mtd/nand/raw/Kconfig +@@ -559,4 +559,8 @@ config MTD_NAND_RB4XX + Enables support for the NAND flash chip on Mikrotik Routerboard + RB4xx series. + ++config MTD_NAND_RB91X ++ tristate "Mikrotik RB91x NAND driver" ++ depends on MFD_RB91X_NGL ++ + endif # MTD_RAW_NAND +--- a/drivers/mtd/nand/raw/Makefile ++++ b/drivers/mtd/nand/raw/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm + obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o + obj-$(CONFIG_MTD_NAND_AR934X) += ar934x_nand.o + obj-$(CONFIG_MTD_NAND_RB4XX) += nand_rb4xx.o ++obj-$(CONFIG_MTD_NAND_RB91X) += nand_rb91x.o + + nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o + nand-objs += nand_onfi.o --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -130,6 +130,10 @@ config GPIO_ATH79 -- 2.26.3 _______________________________________________ openwrt-devel mailing list [email protected] https://lists.openwrt.org/mailman/listinfo/openwrt-devel
