[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