--- /dev/null
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2021 Marvell.
+
+Marvell CNXK GPIO Driver
+========================
+
+CNXK GPIO PMD configures and manages GPIOs available on the system using
+standard enqueue/dequeue mechanism offered by raw device abstraction. PMD relies
+both on standard sysfs GPIO interface provided by the Linux kernel and GPIO
+kernel driver custom interface allowing one to install userspace interrupt
+handlers.
+
+Features
+--------
+
+Following features are available:
+
+- export/unexport a GPIO
+- read/write specific value from/to exported GPIO
+- set GPIO direction
+- set GPIO edge that triggers interrupt
+- set GPIO active low
+- register interrupt handler for specific GPIO
+
+Requirements
+------------
+
+PMD relies on modified kernel GPIO driver which exposes ``ioctl()`` interface
+for installing interrupt handlers for low latency signal processing.
+
+Driver is shipped with Marvell SDK.
+
+Device Setup
+------------
+
+CNXK GPIO PMD binds to virtual device which gets created by passing
+`--vdev=cnxk_gpio,gpiochip=<number>` command line to EAL. `gpiochip` parameter
+tells PMD which GPIO controller should be used. Available controllers are
+available under `/sys/class/gpio`. For further details on how Linux represents
+GPIOs in userspace please refer to
+`sysfs.txt <https://www.kernel.org/doc/Documentation/gpio/sysfs.txt>`_.
+
+If `gpiochip=<number>` was omitted then first gpiochip from the alphabetically
+sort list of available gpiochips is used.
+
+.. code-block:: console
+
+ $ ls /sys/class/gpio
+ export gpiochip448 unexport
+
+In above scenario only one GPIO controller is present hence
+`--vdev=cnxk_gpio,gpiochip=448` should be passed to EAL.
+
+Before performing actual data transfer one needs to call
+``rte_rawdev_queue_count()`` followed by ``rte_rawdev_queue_conf_get()``. The
+former returns number GPIOs available in the system irrespective of GPIOs
+being controllable or not. Thus it is user responsibility to pick the proper
+ones. The latter call simply returns queue capacity.
+
+Respective queue needs to be configured with ``rte_rawdev_queue_setup()``. This
+call barely exports GPIO to userspace.
+
+To perform actual data transfer use standard ``rte_rawdev_enqueue_buffers()``
+and ``rte_rawdev_dequeue_buffers()`` APIs. Not all messages produce sensible
+responses hence dequeueing is not always necessary.
--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2021 Marvell.
+ */
+
+#include <dirent.h>
+#include <string.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_eal.h>
+#include <rte_kvargs.h>
+#include <rte_lcore.h>
+#include <rte_rawdev_pmd.h>
+
+#include <roc_api.h>
+
+#include "cnxk_gpio.h"
+
+#define CNXK_GPIO_BUFSZ 128
+#define CNXK_GPIO_CLASS_PATH "/sys/class/gpio"
+
+static const char *const cnxk_gpio_args[] = {
+#define CNXK_GPIO_ARG_GPIOCHIP "gpiochip"
+ CNXK_GPIO_ARG_GPIOCHIP,
+ NULL
+};
+
+static void
+cnxk_gpio_format_name(char *name, size_t len)
+{
+ snprintf(name, len, "cnxk_gpio");
+}
+
+static int
+cnxk_gpio_filter_gpiochip(const struct dirent *dirent)
+{
+ const char *pattern = "gpiochip";
+
+ return !strncmp(dirent->d_name, pattern, strlen(pattern));
+}
+
+static void
+cnxk_gpio_set_defaults(struct cnxk_gpiochip *gpiochip)
+{
+ struct dirent **namelist;
+ int n;
+
+ n = scandir(CNXK_GPIO_CLASS_PATH, &namelist, cnxk_gpio_filter_gpiochip,
+ alphasort);
+ if (n < 0 || n == 0)
+ return;
+
+ sscanf(namelist[0]->d_name, "gpiochip%d", &gpiochip->num);
+ while (n--)
+ free(namelist[n]);
+ free(namelist);
+}
+
+static int
+cnxk_gpio_parse_arg_gpiochip(const char *key __rte_unused, const char *value,
+ void *extra_args)
+{
+ long val;
+
+ errno = 0;
+ val = strtol(value, NULL, 10);
+ if (errno)
+ return -errno;
+
+ *(int *)extra_args = (int)val;
+
+ return 0;
+}
+
+static int
+cnxk_gpio_parse_args(struct cnxk_gpiochip *gpiochip,
+ struct rte_devargs *devargs)
+{
+ struct rte_kvargs *kvlist;
+ int ret;
+
+ kvlist = rte_kvargs_parse(devargs->args, cnxk_gpio_args);
+ if (!kvlist)
+ return 0;
+
+ ret = rte_kvargs_count(kvlist, CNXK_GPIO_ARG_GPIOCHIP);
+ if (ret == 1) {
+ ret = rte_kvargs_process(kvlist, CNXK_GPIO_ARG_GPIOCHIP,
+ cnxk_gpio_parse_arg_gpiochip,
+ &gpiochip->num);
+ if (ret)
+ goto out;
+ }
+
+ ret = 0;
+out:
+ rte_kvargs_free(kvlist);
+
+ return ret;
+}
+
+static int
+cnxk_gpio_read_attr(char *attr, char *val)
+{
+ FILE *fp;
+ int ret;
+
+ fp = fopen(attr, "r");
+ if (!fp)
+ return -errno;
+
+ ret = fscanf(fp, "%s", val);
+ if (ret < 0)
+ return -errno;
+ if (ret != 1)
+ return -EIO;
+
+ ret = fclose(fp);
+ if (ret)
+ return -errno;
+
+ return 0;
+}
+
+static int
+cnxk_gpio_read_attr_int(char *attr, int *val)
+{
+ char buf[CNXK_GPIO_BUFSZ];
+ int ret;
+
+ ret = cnxk_gpio_read_attr(attr, buf);
+ if (ret)
+ return ret;
+
+ ret = sscanf(buf, "%d", val);
+ if (ret < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int
+cnxk_gpio_dev_close(struct rte_rawdev *dev)
+{
+ RTE_SET_USED(dev);
+
+ return 0;
+}
+
+static const struct rte_rawdev_ops cnxk_gpio_rawdev_ops = {
+ .dev_close = cnxk_gpio_dev_close,
+};
+
+static int
+cnxk_gpio_probe(struct rte_vdev_device *dev)
+{
+ char name[RTE_RAWDEV_NAME_MAX_LEN];
+ struct cnxk_gpiochip *gpiochip;
+ struct rte_rawdev *rawdev;
+ char buf[CNXK_GPIO_BUFSZ];
+ int ret;
+
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return 0;
+
+ cnxk_gpio_format_name(name, sizeof(name));
+ rawdev = rte_rawdev_pmd_allocate(name, sizeof(*gpiochip),
+ rte_socket_id());
+ if (!rawdev) {
+ RTE_LOG(ERR, PMD, "failed to allocate %s rawdev", name);
+ return -ENOMEM;
+ }
+
+ rawdev->dev_ops = &cnxk_gpio_rawdev_ops;
+ rawdev->device = &dev->device;
+ rawdev->driver_name = dev->device.name;
+
+ gpiochip = rawdev->dev_private;
+ cnxk_gpio_set_defaults(gpiochip);
+
+ /* defaults may be overwritten by this call */
+ ret = cnxk_gpio_parse_args(gpiochip, dev->device.devargs);
+ if (ret)
+ goto out;
+
+ /* read gpio base */
+ snprintf(buf, sizeof(buf), "%s/gpiochip%d/base", CNXK_GPIO_CLASS_PATH,
+ gpiochip->num);
+ ret = cnxk_gpio_read_attr_int(buf, &gpiochip->base);
+ if (ret) {
+ RTE_LOG(ERR, PMD, "failed to read %s", buf);
+ goto out;
+ }
+
+ /* read number of available gpios */
+ snprintf(buf, sizeof(buf), "%s/gpiochip%d/ngpio", CNXK_GPIO_CLASS_PATH,
+ gpiochip->num);
+ ret = cnxk_gpio_read_attr_int(buf, &gpiochip->num_gpios);
+ if (ret) {
+ RTE_LOG(ERR, PMD, "failed to read %s", buf);
+ goto out;
+ }
+
+ gpiochip->gpios = rte_calloc(NULL, gpiochip->num_gpios,
+ sizeof(struct cnxk_gpio *), 0);
+ if (!gpiochip->gpios) {
+ RTE_LOG(ERR, PMD, "failed to allocate gpios memory");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ return 0;
+out:
+ rte_rawdev_pmd_release(rawdev);
+
+ return ret;
+}
+
+static int
+cnxk_gpio_remove(struct rte_vdev_device *dev)
+{
+ char name[RTE_RAWDEV_NAME_MAX_LEN];
+ struct cnxk_gpiochip *gpiochip;
+ struct rte_rawdev *rawdev;
+
+ RTE_SET_USED(dev);
+
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+ return 0;
+
+ cnxk_gpio_format_name(name, sizeof(name));
+ rawdev = rte_rawdev_pmd_get_named_dev(name);
+ if (!rawdev)
+ return -ENODEV;
+
+ gpiochip = rawdev->dev_private;
+ rte_free(gpiochip->gpios);
+ rte_rawdev_pmd_release(rawdev);
+
+ return 0;
+}
+
+static struct rte_vdev_driver cnxk_gpio_drv = {
+ .probe = cnxk_gpio_probe,
+ .remove = cnxk_gpio_remove,
+};
+
+RTE_PMD_REGISTER_VDEV(cnxk_gpio, cnxk_gpio_drv);
+RTE_PMD_REGISTER_PARAM_STRING(cnxk_gpio, "gpiochip=<int>");