rb91x-ngl (nand-gpio-latch) requests and controls SoC GPIO lines that are used for NAND control and data lines multiplexed with a latch. Lines of the latch that are not used for NAND control lines, are used for power LED and user LED and a Shift Register nCS.
Like rb4xx-cpld driver rb91x-ngl provides API for separate NAND driver and latch-GPIO driver. This driver is used in place of the ar71xx gpio-latch driver. Signed-off-by: Denis Kalashnikov <[email protected]> --- .../linux/ath79/files/drivers/mfd/rb91x-ngl.c | 331 ++++++++++++++++++ .../linux/ath79/files/include/mfd/rb91x-ngl.h | 59 ++++ target/linux/ath79/mikrotik/config-default | 1 + .../patches-5.4/939-mikrotik-rb91x.patch | 21 ++ 4 files changed, 412 insertions(+) create mode 100644 target/linux/ath79/files/drivers/mfd/rb91x-ngl.c create mode 100644 target/linux/ath79/files/include/mfd/rb91x-ngl.h create mode 100644 target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch diff --git a/target/linux/ath79/files/drivers/mfd/rb91x-ngl.c b/target/linux/ath79/files/drivers/mfd/rb91x-ngl.c new file mode 100644 index 0000000000..c6ab4631f5 --- /dev/null +++ b/target/linux/ath79/files/drivers/mfd/rb91x-ngl.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD driver for the MikroTik RouterBoard NAND controlled through GPIO + * multiplexed with latch. Why MFD, not pure NAND driver? Since the latch + * lines, that are not used for NAND control lines, are used for GPIO + * output function -- for leds and other. + * + * Copyright (C) 2021 Denis Kalashnikov <[email protected]> + * + */ +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> + +#include <mfd/rb91x-ngl.h> + +#define DRIVER_NAME "rb91x-nand-gpio-latch" + +#define NAND_DATAS 8 +#define LATCH_GPIOS 3 + +static int nand_datas_count(struct rb91x_ngl *ngl) +{ + return NAND_DATAS; +} + +static int latch_gpios_count(struct rb91x_ngl *ngl) +{ + return LATCH_GPIOS; +} + +static void latch_lock(struct rb91x_ngl *ngl) +{ + mutex_lock(&ngl->mutex); + + gpio_set_value_cansleep(ngl->gpio[RB91X_NGL_NLE], 0); +} + +static void latch_unlock(struct rb91x_ngl *ngl) +{ + gpio_set_value_cansleep(ngl->gpio[RB91X_NGL_NLE], 1); + + mutex_unlock(&ngl->mutex); +} + +#define OFFSET_INVAL(offset) ((offset) < 0 || (offset) >= RB91X_NGL_GPIOS) + +static void rb91x_ngl_gpio_set_value(struct rb91x_ngl *ngl, int offset, int val) +{ + if (OFFSET_INVAL(offset)) + return; + + gpio_set_value_cansleep(ngl->gpio[offset], val); +} + +static void latch_gpio_set_value(struct rb91x_ngl *ngl, int offset, int val) +{ + if (offset >= LATCH_GPIOS) + return; + + mutex_lock(&ngl->mutex); + + gpio_set_value_cansleep(ngl->gpio[RB91X_NGL_LATCH_GPIO0 + offset], val); + + mutex_unlock(&ngl->mutex); +} + +static int rb91x_ngl_gpio_get_value(struct rb91x_ngl *ngl, int offset) +{ + if (OFFSET_INVAL(offset)) + return -EINVAL; + + return gpio_get_value(ngl->gpio[offset]); +} + +static void rb91x_ngl_gpio_direction_output(struct rb91x_ngl *ngl, int offset, + int val) +{ + if (OFFSET_INVAL(offset)) + return; + + gpio_direction_output(ngl->gpio[offset], val); +} + +static void rb91x_ngl_gpio_direction_input(struct rb91x_ngl *ngl, int offset) +{ + if (OFFSET_INVAL(offset)) + return; + + gpio_direction_input(ngl->gpio[offset]); +} + +static const struct mfd_cell mfd_cells[] = { + { + .name = "mikrotik,rb91x-nand", + .of_compatible = "mikrotik,rb91x-nand", + }, { + .name = "mikrotik,rb91x-gpio-latch", + .of_compatible = "mikrotik,rb91x-gpio-latch", + }, +}; + +static int get_gpios(struct device *dev, const char *prop_name) +{ + int n; + + n = of_get_named_gpio(dev->of_node, prop_name, 0); + if (n < 0) + dev_err(dev, "Could not read required '%s' property: %d\n", prop_name, n); + //pr_info(DRIVER_NAME ": %s = %d\n", prop_name, n); + + return n; +} + +/* + * NOTE: all gpios are labeled with driver name, not with @name. + * @name is used only in error message. Since we failed to choose + * a good names for multiplexed gpios. + */ +static int req_gpio(struct device *dev, int gpio, const char *name) +{ + int ret; + + ret = devm_gpio_request(dev, gpio, DRIVER_NAME); + if (ret) { + pr_err(DRIVER_NAME ": failed to request gpio %d ('%s'): %d\n", + gpio, name, ret); + return ret; + } + + //pr_info(DRIVER_NAME ": request gpio %d ('%s')\n", gpio, name); + + return ret; +} + +static int probe(struct platform_device *pdev) +{ + struct device_node *of_node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct rb91x_ngl *ngl; + int i, n, ret; + + pr_info("rb91x-nand-gpio-latch driver probe\n"); + + ngl = devm_kzalloc(dev, sizeof(*ngl), GFP_KERNEL); + if (!ngl) + return -ENOMEM; + + /* TODO: read gpios flags (active high/low) */ + + for (i = 0; i < RB91X_NGL_GPIOS; i++) { + ngl->gpio[i] = -ENOENT; + } + + /* Read NAND control gpios */ + ngl->gpio[RB91X_NGL_NAND_NCE] = get_gpios(dev, "nand-nce-gpios"); + ngl->gpio[RB91X_NGL_NAND_CLE] = get_gpios(dev, "nand-cle-gpios"); + ngl->gpio[RB91X_NGL_NAND_ALE] = get_gpios(dev, "nand-ale-gpios"); + ngl->gpio[RB91X_NGL_NAND_NRW] = get_gpios(dev, "nand-nrw-gpios"); + ngl->gpio[RB91X_NGL_NAND_RDY] = get_gpios(dev, "nand-rdy-gpios"); + ngl->gpio[RB91X_NGL_NAND_READ] = get_gpios(dev, "nand-read-gpios"); + + ngl->gpio[RB91X_NGL_NLE] = get_gpios(dev, "nle-gpios"); + + /* Read NAND data gpios */ + + n = of_gpio_named_count(of_node, "nand-data-gpios"); + if (n != NAND_DATAS) { + dev_err(dev, DRIVER_NAME + ": required 'nand-data-gpios' property must have %d gpios\n", + NAND_DATAS); + return -EINVAL; + } + + //dev_info(dev, DRIVER_NAME ": nand-data-gpios count = %d\n", n); + + for (i = 0; i < n; i++) { + ret = of_get_named_gpio(of_node, "nand-data-gpios", i); + if (ret < 0) { + dev_err(dev, DRIVER_NAME + ": Couldn't read required 'nand-data-gpios': %d\n", + ret); + return -EINVAL; + } + + //dev_info(dev, DRIVER_NAME ": nand-data-gpios = %d\n", ret); + + ngl->gpio[RB91X_NGL_NAND_DATA0 + i] = ret; + } + + /* Read latch gpios */ + + n = of_gpio_named_count(of_node, "latch-gpios"); + if (n != LATCH_GPIOS) { + dev_err(dev, DRIVER_NAME + ": required 'latch-gpios' property must have %d gpios\n", + LATCH_GPIOS); + return -EINVAL; + } + + //dev_info(dev, DRIVER_NAME ": latch-gpios count = %d\n", n); + + for (i = 0; i < n; i++) { + ret = of_get_named_gpio(of_node, "latch-gpios", i); + if (ret < 0) { + dev_err(dev, DRIVER_NAME + ": Couldn't read required 'latch-gpios': %d\n", + ret); + return -EINVAL; + } + + //dev_info(dev, DRIVER_NAME ": latch-gpios = %d\n", ret); + + ngl->gpio[RB91X_NGL_LATCH_GPIO0 + i] = ret; + } + + if (ngl->gpio[RB91X_NGL_NAND_NCE] < 0 + || ngl->gpio[RB91X_NGL_NAND_CLE] < 0 + || ngl->gpio[RB91X_NGL_NAND_ALE] < 0 + || ngl->gpio[RB91X_NGL_NAND_NRW] < 0 + || ngl->gpio[RB91X_NGL_NAND_RDY] < 0 + || ngl->gpio[RB91X_NGL_NAND_READ] < 0 + || ngl->gpio[RB91X_NGL_NLE] < 0) + return -EINVAL; + + /* Request gpios */ + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NLE], "nLE")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_NCE], "NAND-nCE")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_CLE], "NAND-CLE")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_ALE], "NAND-ALE")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_NRW], "NAND-nRW")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_RDY], "NAND-RDY")) + return -EINVAL; + + if (req_gpio(dev, ngl->gpio[RB91X_NGL_NAND_READ], "NAND-READ")) + return -EINVAL; + + for (i = 0; i < NAND_DATAS; i++) { + /* + * Some data gpios are equal to control gpios. + * Check this. + */ + n = ngl->gpio[RB91X_NGL_NAND_DATA0 + i]; + if (n == ngl->gpio[RB91X_NGL_NAND_NCE] + || n == ngl->gpio[RB91X_NGL_NAND_CLE] + || n == ngl->gpio[RB91X_NGL_NAND_ALE] + || n == ngl->gpio[RB91X_NGL_NAND_NRW] + || n == ngl->gpio[RB91X_NGL_NAND_RDY] + || n == ngl->gpio[RB91X_NGL_NAND_READ]) + continue; + if (req_gpio(dev, n, "NAND-DATAx")) + return -EINVAL; + } + + /* + * NOTE: We suppose that latch gpios are equal to some + * control gpios, so they have been already requested. + */ + + ngl->nand_datas_count = nand_datas_count; + ngl->latch_lock = latch_lock; + ngl->latch_unlock = latch_unlock; + ngl->gpio_set_value = rb91x_ngl_gpio_set_value; + ngl->gpio_get_value = rb91x_ngl_gpio_get_value; + ngl->gpio_direction_input = rb91x_ngl_gpio_direction_input; + ngl->gpio_direction_output = rb91x_ngl_gpio_direction_output; + ngl->latch_gpio_set_value = latch_gpio_set_value; + ngl->latch_gpios_count = latch_gpios_count; + + mutex_init(&ngl->mutex); + + dev_set_drvdata(dev, ngl); + + /* + * All gpios and the latch are controlled by NAND driver, + * but we need to init gpio lines for the latch gpio in case + * of NAND driver is missing. + */ + + /* Unlock the latch */ + gpio_direction_output(ngl->gpio[RB91X_NGL_NLE], 1); + + /* TODO: are latch gpio lines active high or low? */ + for (i = 0; i < LATCH_GPIOS; i++) { + gpio_direction_output(ngl->gpio[RB91X_NGL_LATCH_GPIO0 + i], 0); + } + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + mfd_cells, ARRAY_SIZE(mfd_cells), + NULL, 0, NULL); +} + +static int remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id match[] = { + { .compatible = "mikrotik,nand-gpio-latch", }, + { }, +}; +MODULE_DEVICE_TABLE(of, match); + +static struct platform_driver rb91x_ngl_driver = { + .probe = probe, + .remove = remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(match), + }, +}; + +module_platform_driver(rb91x_ngl_driver); + +MODULE_DESCRIPTION("Driver for Mikrotik RouterBoard 91x NAND controlled through GPIOs multiplexed with latch"); +MODULE_AUTHOR("Denis Kalashnikov <[email protected]>"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/ath79/files/include/mfd/rb91x-ngl.h b/target/linux/ath79/files/include/mfd/rb91x-ngl.h new file mode 100644 index 0000000000..5360aa7548 --- /dev/null +++ b/target/linux/ath79/files/include/mfd/rb91x-ngl.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD driver for the MikroTik RouterBoard 91xG NAND controlled + * through GPIOs multiplexed with latch (rb91x-nand-gpio-latch). + * + * Copyright (C) 2021 Denis Kalashnikov <[email protected]> + */ + +#include <linux/mutex.h> + +enum rb91x_ngl_gpios { + /* NAND control gpios */ + RB91X_NGL_NAND_NCE, /* nCE -- Chip Enable, Active Low */ + RB91X_NGL_NAND_CLE, /* CLE -- Command Latch Enable */ + RB91X_NGL_NAND_ALE, /* ALE -- Address Latch Enable */ + RB91X_NGL_NAND_NRW, /* nRW -- Read/Write Enable, Active Low */ + RB91X_NGL_NAND_RDY, /* RDY -- NAND Ready */ + RB91X_NGL_NAND_READ, /* READ */ + + RB91X_NGL_NLE, /* nLE -- Latch Enable, Active Low */ + + /* NAND data gpios */ + RB91X_NGL_NAND_DATA0, + + /* Latch gpios */ + RB91X_NGL_LATCH_GPIO0 = RB91X_NGL_NAND_DATA0 + 32, + + RB91X_NGL_GPIOS = RB91X_NGL_LATCH_GPIO0 + 32, /* Total number of gpios */ +}; + +struct rb91x_ngl { + /* Public */ + + /* API for RB91x NAND controller driver */ + void (*gpio_set_value)(struct rb91x_ngl *ngl, int offset, int val); + void (*gpio_direction_input)(struct rb91x_ngl *ngl, int offset); + void (*gpio_direction_output)(struct rb91x_ngl *ngl, int offset, + int val); + int (*gpio_get_value)(struct rb91x_ngl *ngl, int offset); + void (*latch_lock)(struct rb91x_ngl *ngl); + void (*latch_unlock)(struct rb91x_ngl *ngl); + int (*nand_datas_count)(struct rb91x_ngl *ngl); + + /* API for RB91x gpio latch controller driver */ + void (*latch_gpio_set_value)(struct rb91x_ngl *ngl, int offset, + int val); + int (*latch_gpios_count)(struct rb91x_ngl *ngl); + + + /* Private */ + + /* + * To synchronize access of NAND driver and GPIO driver + * to shared gpio lines. + */ + struct mutex mutex; + + int gpio[RB91X_NGL_GPIOS]; +}; diff --git a/target/linux/ath79/mikrotik/config-default b/target/linux/ath79/mikrotik/config-default index 1e637bdfd3..67c980a491 100644 --- a/target/linux/ath79/mikrotik/config-default +++ b/target/linux/ath79/mikrotik/config-default @@ -8,6 +8,7 @@ CONFIG_LEDS_RESET=y CONFIG_LZO_DECOMPRESS=y CONFIG_MDIO_GPIO=y CONFIG_MFD_RB4XX_CPLD=y +CONFIG_MFD_RB91X_NGL=y CONFIG_MIKROTIK=y CONFIG_MIKROTIK_RB_SYSFS=y CONFIG_MTD_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 new file mode 100644 index 0000000000..a85db0892c --- /dev/null +++ b/target/linux/ath79/patches-5.4/939-mikrotik-rb91x.patch @@ -0,0 +1,21 @@ +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -2020,5 +2020,10 @@ config MFD_RB4XX_CPLD + Enables support for the CPLD chip (NAND & GPIO) on Mikrotik + Routerboard RB4xx series. + ++config MFD_RB91X_NGL ++ tristate "Mikrotik RB91x NAND and GPIO driver ++ select MFD_CORE ++ depends on ATH79 || COMPILE_TEST ++ + endmenu + endif +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -257,3 +257,5 @@ obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-b + obj-$(CONFIG_MFD_STMFX) += stmfx.o + + obj-$(CONFIG_MFD_RB4XX_CPLD) += rb4xx-cpld.o ++ ++obj-$(CONFIG_MFD_RB91X_NGL) += rb91x-ngl.o -- 2.26.3 _______________________________________________ openwrt-devel mailing list [email protected] https://lists.openwrt.org/mailman/listinfo/openwrt-devel
