aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Verkuil <hverkuil@xs4all.nl>2021-08-11 12:45:22 +0200
committerHans Verkuil <hverkuil@xs4all.nl>2021-09-09 12:24:58 +0200
commitb8ca4c46e7613329ffb82d415cc062175f442e16 (patch)
treeee68d97d946628e18fcd20b87d0aac3875b6425c
parentaeb93a9f22679ba01d9d4b51e995ccc2353a7dc8 (diff)
gpio-virt-link: wip: add virtual gpio link driver
Doesn't compile on this kernel version, needs more work. But this should make it possible to read output GPIOs. Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
-rw-r--r--drivers/gpio/Kconfig5
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-virt-link.c643
3 files changed, 649 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index fab571016adf..e2d63b610628 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1659,6 +1659,11 @@ config GPIO_AGGREGATOR
industrial control context, to be operated from userspace using
the GPIO chardev interface.
+config GPIO_VIRT_LINK
+ tristate "GPIO Virtual Link"
+ help
+ Say yes here to enable this.
+
config GPIO_MOCKUP
tristate "GPIO Testing Driver"
select IRQ_SIM
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 32a32659866a..472eda7b710e 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -165,6 +165,7 @@ obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o
obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o
obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o
obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o
+obj-$(CONFIG_GPIO_VIRT_LINK) += gpio-virt-link.o
obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o
obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o
obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o
diff --git a/drivers/gpio/gpio-virt-link.c b/drivers/gpio/gpio-virt-link.c
new file mode 100644
index 000000000000..d7588f9cb3b6
--- /dev/null
+++ b/drivers/gpio/gpio-virt-link.c
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2021 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/spinlock.h>
+#include <linux/debugfs.h>
+
+MODULE_DESCRIPTION("Virtual gpios to be used by other devices to communicate");
+MODULE_AUTHOR("Cisco Systems, Inc.");
+MODULE_AUTHOR("Martin Bugge <marbugge@cisco.com>");
+MODULE_LICENSE("GPL");
+
+/*
+ * Set num-links (N) in dts.
+ * will create a set of input and output gpios.
+ * input gpio 0 -> (N - 1)
+ * output gpio N -> (2*N - 1)
+ *
+ * The values set in an output gpio can be read in input gpio.
+ */
+
+static int debug = 0;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-3)");
+
+#define GPIO_VIRT_LINK_DRV_NAME "gpio_virt_link"
+#define GVL_NODE_OUT "gpio-virt-link-out"
+#define GVL_NODE_IN "gpio-virt-link-in"
+
+#define MAX_NUM_OUT 32
+#define MAX_NUM_IN 32
+#define MAX_NUM_LINKS 32
+
+struct gpio_virt_link_state {
+ struct device *dev;
+ spinlock_t lock;
+ /* out */
+ u32 num_outputs;
+ struct gpio_chip gcout;
+ unsigned long values_output;
+ /* in */
+ u32 num_inputs;
+ struct gpio_chip gcin;
+ struct irq_chip irqin;
+ unsigned long values_input;
+ unsigned long irq_mask;
+ /* links */
+ u32 num_links;
+ struct {
+ const char *desc;
+ int out;
+ int in;
+ } links[MAX_NUM_LINKS];
+ /* work */
+ struct workqueue_struct *workqueue;
+ struct work_struct update_input_work;
+ unsigned long irq_capture;
+ /* debugfs */
+ struct {
+ u8 name[64];
+ struct dentry *fsroot;
+ struct dentry *status;
+ struct seq_file *sf;
+ } dbg;
+};
+
+#define vgl_dbg(dbg_level, debug, fmt, arg...) \
+ do { \
+ if (debug >= (dbg_level)) \
+ dev_info(state->dev, fmt, ## arg); \
+ } while (0)
+
+#define gvl_info(state, fmt, arg...) \
+ if (state->dbg.sf) \
+ seq_printf(state->dbg.sf, fmt, ## arg); \
+ else \
+ dev_info(state->dev, fmt, ## arg)
+
+static void vgl_handle_irq(struct gpio_virt_link_state *state, unsigned long irq_capture)
+{
+ vgl_dbg(1, debug, "%s: irq-capture: 0x%08lx\n", __func__, irq_capture);
+
+ while (irq_capture) {
+ struct gpio_chip *gc = &state->gcin;
+ struct irq_data *irqdata;
+ unsigned long single_capture;
+ u32 irq_type;
+ int irq;
+
+ if (!gc->to_irq) {
+ break;
+ }
+
+ single_capture = ffs(irq_capture) - 1;
+ irq_capture &= ~BIT(single_capture);
+
+ irq = gc->to_irq(gc, single_capture);
+ irqdata = irq_get_irq_data(irq);
+ if (!irqdata) {
+ continue;
+ }
+
+ irq_type = irqd_get_trigger_type(irqdata);
+
+ vgl_dbg(1, debug, "%s: irq %d, single-capture 0x%lx, type 0x%x\n", __func__,
+ irq, single_capture, irq_type);
+
+ if (irq_type & IRQ_TYPE_EDGE_BOTH) {
+ handle_nested_irq(irq);
+ }
+ }
+}
+
+static void vgl_update_input_work(struct work_struct *work)
+{
+ struct gpio_virt_link_state *state;
+ int loop = 0;
+
+ state = container_of(work, struct gpio_virt_link_state, update_input_work);
+
+ while (true) {
+ unsigned long flags;
+ unsigned long irq_capture;
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ irq_capture = state->irq_capture;
+ state->irq_capture = 0;
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ vgl_dbg(1, debug, "%s: loop: %u, irq-capture: 0x%08lx\n", __func__, loop++, irq_capture);
+
+ if (!irq_capture) {
+ break;
+ }
+
+ vgl_handle_irq(state, irq_capture);
+ }
+}
+
+static void vgl_update_input(struct gpio_virt_link_state *state, unsigned int offset, int value)
+{
+ unsigned long flags;
+ unsigned long v;
+ bool trigger;
+
+ vgl_dbg(1, debug, "%s: offset %u, value %d\n", __func__, offset, value);
+
+ if (offset > state->num_inputs) {
+ dev_err(state->dev, "%s: invalid offset %d\n", __func__, offset);
+ return;
+ }
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ v = state->values_input;
+
+ if (value)
+ state->values_input |= BIT(offset);
+ else
+ state->values_input &= ~BIT(offset);
+
+ trigger = ((v != state->values_input) && (state->irq_mask & BIT(offset)));
+
+ if (trigger) {
+ state->irq_capture |= BIT(offset);
+ }
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ if (trigger) {
+ queue_work(state->workqueue, &state->update_input_work);
+ }
+}
+
+static void vgl_set_output(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+ unsigned long flags;
+ int i;
+
+ vgl_dbg(1, debug, "%s: offset %u, value %d\n", __func__, offset, value);
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ if (value)
+ state->values_output |= BIT(offset);
+ else
+ state->values_output &= ~BIT(offset);
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ for (i = 0; i < state->num_links; i++) {
+ if (state->links[i].out == offset) {
+ vgl_update_input(state, state->links[i].in, value);
+ }
+ }
+}
+
+static int vgl_direction_output(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+
+ vgl_dbg(1, debug, "%s: offset %u, value %d\n", __func__, offset, value);
+
+ if (offset > state->num_outputs) {
+ return -EINVAL;
+ }
+
+ vgl_set_output(chip, offset, value);
+
+ return 0;
+}
+
+static int vgl_get_output(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+ unsigned long flags;
+ int value = 0;
+
+ if (offset >= state->num_outputs) {
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ value = state->values_output & BIT(offset) ? 1 : 0;
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ vgl_dbg(1, debug, "%s: offset %u, value %d\n", __func__, offset, value);
+
+ return value;
+}
+
+static int vgl_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+
+ vgl_dbg(1, debug, "%s: offset %u\n", __func__, offset);
+
+ if (offset >= state->num_inputs) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vgl_get_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+ unsigned long flags;
+ int value = 0;
+
+ if (offset >= state->num_inputs) {
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ value = state->values_input & BIT(offset) ? 1 : 0;
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ vgl_dbg(1, debug, "%s: offset %u, value %d\n", __func__, offset, value);
+
+ return value;
+}
+
+static void vgl_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ vgl_dbg(1, debug, "%s: hwirq: %lu\n", __func__, d->hwirq);
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ __clear_bit(d->hwirq, &state->irq_mask);
+
+ spin_unlock_irqrestore(&state->lock, flags);
+}
+
+static void vgl_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ vgl_dbg(1, debug, "%s: hwirq: %lu\n", __func__, d->hwirq);
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ __set_bit(d->hwirq, &state->irq_mask);
+
+ spin_unlock_irqrestore(&state->lock, flags);
+}
+
+static int vgl_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gpio_virt_link_state *state = gpiochip_get_data(chip);
+
+ vgl_dbg(1, debug, "%s:\n", __func__);
+
+ if (type & ~IRQ_TYPE_EDGE_BOTH) {
+ dev_err(state->dev, "%s: only edge irq supported (type 0x%x)\n", __func__, type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gpio_virt_link_add_gpio_chips(struct gpio_virt_link_state *state)
+{
+ struct device_node *node;
+ struct gpio_chip *gc;
+ int ret;
+
+ /* out */
+ node = of_find_node_by_name(state->dev->of_node, GVL_NODE_OUT);
+ if (node) {
+ gc = &state->gcout;
+
+ gc->label = GVL_NODE_OUT;
+ gc->names = NULL;
+ gc->direction_input = NULL;
+ gc->direction_output = vgl_direction_output;
+ gc->set = vgl_set_output;
+ gc->get = vgl_get_output;
+ gc->base = -1;
+ gc->ngpio = state->num_outputs;
+ gc->can_sleep = 1;
+ gc->parent = state->dev;
+ gc->owner = THIS_MODULE;
+ gc->of_node = node;
+
+ of_node_put(node);
+
+ ret = devm_gpiochip_add_data(state->dev, gc, state);
+ if (ret < 0) {
+ dev_err(state->dev, "Failed to add gpio out chip\n");
+ return ret;
+ }
+ }
+
+ /* in */
+ node = of_find_node_by_name(state->dev->of_node, GVL_NODE_IN);
+ if (node) {
+ gc = &state->gcin;
+
+ gc->label = GVL_NODE_IN;
+ gc->names = NULL;
+ gc->direction_input = vgl_direction_input;
+ gc->direction_output = NULL;
+ gc->set = NULL;
+ gc->get = vgl_get_input;
+ gc->base = -1;
+ gc->ngpio = state->num_inputs;
+ gc->can_sleep = 1;
+ gc->parent = state->dev;
+ gc->owner = THIS_MODULE;
+ gc->of_node = node;
+
+ of_node_put(node);
+
+ state->irqin.name = "gpio-virt-link-irq",
+ state->irqin.irq_mask = vgl_irq_mask,
+ state->irqin.irq_unmask = vgl_irq_unmask,
+ state->irqin.irq_set_type = vgl_irq_set_type,
+ gc->irq.chip = &state->irqin;
+ gc->irq.parent_handler = NULL;
+ gc->irq.num_parents = 0;
+ gc->irq.parents = NULL;
+ gc->irq.default_type = IRQ_TYPE_NONE;
+ gc->irq.handler = handle_simple_irq;
+
+ ret = devm_gpiochip_add_data(state->dev, gc, state);
+ if (ret < 0) {
+ dev_err(state->dev, "Failed to add gpio chip\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* debugfs */
+static void vgl_show_gpio(struct gpio_virt_link_state *state, struct gpio_chip *gc, unsigned int offset, bool irq)
+{
+ const char *lable = gpiochip_is_requested(gc, offset);
+ if (!lable) {
+ lable = "Unrequested";
+ }
+ gvl_info(state, " gpio-%-3d (%-20.20s|%-20.20s) %s %s\n",
+ gc->base + offset,
+ gc->names ? gc->names[offset] : "",
+ lable,
+ gc->get(gc, offset) ? "hi" : "lo",
+ irq ? "IRQ" : "");
+}
+
+static void vgl_show_status(struct gpio_virt_link_state *state)
+{
+ int i;
+
+ gvl_info(state, "name : %s\n", state->dbg.name);
+ gvl_info(state, "values-output : 0x%08lx\n", state->values_output);
+ gvl_info(state, "values-input : 0x%08lx\n", state->values_input);
+ gvl_info(state, "irq-mask : 0x%08lx\n", state->irq_mask);
+ gvl_info(state, "irq-capture : 0x%08lx\n", state->irq_capture);
+
+ gvl_info(state, "outputs: %u\n", state->num_outputs);
+ for (i = 0; i < state->num_outputs; i++) {
+ vgl_show_gpio(state, &state->gcout, i, false);
+ }
+
+ gvl_info(state, "inputs: %u\n", state->num_inputs);
+ for (i = 0; i < state->num_inputs; i++) {
+ vgl_show_gpio(state, &state->gcin, i, state->irq_mask & BIT(i));
+ }
+
+ gvl_info(state, "links: %u\n", state->num_links);
+ for (i = 0; i < state->num_links; i++) {
+ gvl_info(state, " %2d: (%-25.25s) gpio-%3d --> gpio-%3d\n",
+ i,
+ state->links[i].desc ? state->links[i].desc : "",
+ state->gcout.base + state->links[i].out,
+ state->gcin.base + state->links[i].in);
+ }
+}
+
+static int dbg_status_show(struct seq_file *s, void *arg)
+{
+ struct gpio_virt_link_state *state = s->private;
+
+ state->dbg.sf = s;
+
+ vgl_show_status(state);
+
+ state->dbg.sf = NULL;
+
+ return 0;
+}
+
+static int dbg_status_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dbg_status_show, inode->i_private);
+}
+
+static const struct file_operations dbg_status_fops =
+{
+ .owner = THIS_MODULE,
+ .open = dbg_status_open,
+ .release = single_release,
+ .read = seq_read,
+};
+
+static int gpio_virt_link_probe(struct platform_device *pdev)
+{
+ struct gpio_virt_link_state *state;
+ struct device_node *node, *link_node;
+ const char *reg_name;
+ int out = 0;
+ int in = 0;
+ int ret;
+
+ /* out */
+ node = of_get_child_by_name(pdev->dev.of_node, GVL_NODE_OUT);
+ if (node) {
+ ret = of_property_read_u32(node, "num-gpios", &out);
+ of_node_put(node);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: no out num-gpios property\n", __func__);
+ return ret;
+ }
+ if (out > MAX_NUM_OUT || out == 0) {
+ dev_err(&pdev->dev, "%s: requested %d number of outputs\n", __func__, out);
+ return -EINVAL;
+ }
+ }
+
+ /* in */
+ node = of_get_child_by_name(pdev->dev.of_node, GVL_NODE_IN);
+ if (node) {
+ ret = of_property_read_u32(node, "num-gpios", &in);
+ of_node_put(node);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: no in num-gpios property\n", __func__);
+ return ret;
+ }
+ if (in > MAX_NUM_IN || in == 0) {
+ dev_err(&pdev->dev, "%s: requested %d number of inputs\n", __func__, in);
+ return -EINVAL;
+ }
+ }
+
+ /* state */
+ state = devm_kzalloc(&pdev->dev, sizeof(struct gpio_virt_link_state), GFP_KERNEL);
+ if (!state) {
+ return -ENOMEM;
+ }
+
+ state->dev = &pdev->dev;
+ state->num_outputs = out;
+ state->num_inputs = in;
+
+ /* links */
+ node = of_get_child_by_name(pdev->dev.of_node, "links");
+ if (node) {
+ int i = 0;
+ state->num_links = of_get_child_count(node);
+ if (state->num_links > MAX_NUM_LINKS) {
+ of_node_put(node);
+ dev_err(&pdev->dev, "%s: requested %d number of links\n", __func__, state->num_links);
+ return -EINVAL;
+ }
+ for_each_child_of_node(node, link_node) {
+ ret = of_property_read_string(link_node, "desc", &state->links[i].desc);
+ if (ret) {
+ state->links[i].desc = NULL;
+ }
+ ret = of_property_read_u32(link_node, "out", &state->links[i].out);
+ if (ret) {
+ break;
+ }
+ ret = of_property_read_u32(link_node, "in", &state->links[i].in);
+ if (ret) {
+ break;
+ }
+ i++;
+ }
+ of_node_put(node);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: failed getting link %d setting\n", __func__, i);
+ return ret;
+ }
+ }
+
+ /* lock */
+ spin_lock_init(&state->lock);
+
+ ret = gpio_virt_link_add_gpio_chips(state);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: add gpio chips failed\n", __func__);
+ return ret;
+ }
+
+ ret = of_property_read_string(pdev->dev.of_node, "reg-name", &reg_name);
+ if (ret == 0) {
+ snprintf(state->dbg.name, sizeof(state->dbg.name), "%s_%s", GPIO_VIRT_LINK_DRV_NAME, reg_name);
+ } else {
+ snprintf(state->dbg.name, sizeof(state->dbg.name), "%s", GPIO_VIRT_LINK_DRV_NAME);
+ }
+
+ state->workqueue = create_singlethread_workqueue(state->dbg.name);
+ if (!state->workqueue) {
+ dev_err(&pdev->dev, "Failed to create workqueue\n");
+ ret = -ENOMEM;
+ goto err_work_queue;
+ }
+ INIT_WORK(&state->update_input_work, vgl_update_input_work);
+
+ /* debug-fs */
+ state->dbg.fsroot = debugfs_create_dir(state->dbg.name, NULL);
+ if (!state->dbg.fsroot) {
+ dev_err(&pdev->dev, "%s: failed to create fsroot\n", __func__);
+ ret = -EIO;
+ goto err_fsroot;
+ }
+
+ state->dbg.status = debugfs_create_file("status", S_IRUGO, state->dbg.fsroot, state, &dbg_status_fops);
+ if (!state->dbg.status) {
+ dev_err(&pdev->dev, "%s: failed to create debugfs file\n", __func__);
+ ret = -EIO;
+ goto err_fs_status;
+ }
+
+ platform_set_drvdata(pdev, state);
+
+ dev_info(&pdev->dev, "%s:\n", __func__);
+
+ return 0;
+
+err_fs_status:
+ debugfs_remove_recursive(state->dbg.fsroot);
+err_fsroot:
+ destroy_workqueue(state->workqueue);
+err_work_queue:
+ return ret;
+}
+
+static int gpio_virt_link_remove(struct platform_device *pdev)
+{
+ struct gpio_virt_link_state *state = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "%s:\n", __func__);
+
+ state->irq_capture = 0x0;
+ state->irq_mask = 0x0;
+ cancel_work_sync(&state->update_input_work);
+
+ debugfs_remove_recursive(state->dbg.fsroot);
+
+ destroy_workqueue(state->workqueue);
+
+ return 0;
+}
+
+static const struct of_device_id gpio_virt_link_of_mtable[] = {
+ { .compatible = "cisco,gpio_virt_link", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_virt_link_of_mtable);
+
+static struct platform_driver gpio_virt_link_driver = {
+ .driver = {
+ .name = GPIO_VIRT_LINK_DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(gpio_virt_link_of_mtable),
+ },
+ .probe = gpio_virt_link_probe,
+ .remove = gpio_virt_link_remove,
+};
+module_platform_driver(gpio_virt_link_driver);

Privacy Policy