[PATCH 1/4] ath79: add MFD driver (NAND and GPIO) for Mikrotik RB91xG
Denis Kalashnikov
denis281089 at gmail.com
Thu May 6 17:25:11 BST 2021
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 <denis281089 at gmail.com>
---
.../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 <denis281089 at gmail.com>
+ *
+ */
+#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 <denis281089 at gmail.com>");
+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 <denis281089 at gmail.com>
+ */
+
+#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
More information about the openwrt-devel
mailing list