diff options
Diffstat (limited to 'target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch')
-rw-r--r-- | target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch | 1182 |
1 files changed, 0 insertions, 1182 deletions
diff --git a/target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch b/target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch deleted file mode 100644 index 7076026e7c..0000000000 --- a/target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch +++ /dev/null @@ -1,1182 +0,0 @@ -From c31626a9e173f2b2e0adc4cb8bfbb86a958bafcd Mon Sep 17 00:00:00 2001 -From: Phil Elwell <phil@raspberrypi.com> -Date: Wed, 30 Sep 2020 12:00:54 +0100 -Subject: [PATCH] gpio: Add gpio-fsm driver - -The gpio-fsm driver implements simple state machines that allow GPIOs -to be controlled in response to inputs from other GPIOs - real and -soft/virtual - and time delays. It can: -+ create dummy GPIOs for drivers that demand them, -+ drive multiple GPIOs from a single input, with optional delays, -+ add a debounce circuit to an input, -+ drive pattern sequences onto LEDs -etc. - -Signed-off-by: Phil Elwell <phil@raspberrypi.com> ---- - drivers/gpio/Kconfig | 9 + - drivers/gpio/Makefile | 1 + - drivers/gpio/gpio-fsm.c | 1103 +++++++++++++++++++++++++++ - include/dt-bindings/gpio/gpio-fsm.h | 21 + - 4 files changed, 1134 insertions(+) - create mode 100644 drivers/gpio/gpio-fsm.c - create mode 100644 include/dt-bindings/gpio/gpio-fsm.h - ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1071,6 +1071,15 @@ config HTC_EGPIO - several HTC phones. It provides basic support for input - pins, output pins, and irqs. - -+config GPIO_FSM -+ tristate "GPIO FSM support" -+ help -+ The GPIO FSM driver allows the creation of state machines for -+ manipulating GPIOs (both real and virtual), with state transitions -+ triggered by GPIO edges or delays. -+ -+ If unsure, say N. -+ - config GPIO_JANZ_TTL - tristate "Janz VMOD-TTL Digital IO Module" - depends on MFD_JANZ_CMODIO ---- a/drivers/gpio/Makefile -+++ b/drivers/gpio/Makefile -@@ -55,6 +55,7 @@ obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93x - obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o - obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o - obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o -+obj-$(CONFIG_GPIO_FSM) += gpio-fsm.o - obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o - obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o - obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o ---- /dev/null -+++ b/drivers/gpio/gpio-fsm.c -@@ -0,0 +1,1103 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * GPIO FSM driver -+ * -+ * This driver implements simple state machines that allow real GPIOs to be -+ * controlled in response to inputs from other GPIOs - real and soft/virtual - -+ * and time delays. It can: -+ * + create dummy GPIOs for drivers that demand them -+ * + drive multiple GPIOs from a single input, with optional delays -+ * + add a debounce circuit to an input -+ * + drive pattern sequences onto LEDs -+ * etc. -+ * -+ * Copyright (C) 2020 Raspberry Pi (Trading) Ltd. -+ */ -+ -+#include <linux/err.h> -+#include <linux/gpio.h> -+#include <linux/gpio/driver.h> -+#include <linux/interrupt.h> -+#include <linux/module.h> -+#include <linux/platform_device.h> -+ -+#include <dt-bindings/gpio/gpio-fsm.h> -+ -+#define MODULE_NAME "gpio-fsm" -+ -+#define GF_IO_TYPE(x) ((u32)(x) & 0xffff) -+#define GF_IO_INDEX(x) ((u32)(x) >> 16) -+ -+enum { -+ SIGNAL_GPIO, -+ SIGNAL_SOFT -+}; -+ -+enum { -+ INPUT_GPIO, -+ INPUT_SOFT -+}; -+ -+enum { -+ SYM_UNDEFINED, -+ SYM_NAME, -+ SYM_SET, -+ SYM_START, -+ SYM_SHUTDOWN, -+ -+ SYM_MAX -+}; -+ -+struct soft_gpio { -+ int dir; -+ int value; -+}; -+ -+struct input_gpio_state { -+ struct gpio_fsm *gf; -+ struct gpio_desc *desc; -+ struct fsm_state *target; -+ int index; -+ int value; -+ int irq; -+ bool enabled; -+ bool active_low; -+}; -+ -+struct gpio_event { -+ int index; -+ int value; -+ struct fsm_state *target; -+}; -+ -+struct symtab_entry { -+ const char *name; -+ void *value; -+ struct symtab_entry *next; -+}; -+ -+struct output_signal { -+ u8 type; -+ u8 value; -+ u16 index; -+}; -+ -+struct fsm_state { -+ const char *name; -+ struct output_signal *signals; -+ struct gpio_event *gpio_events; -+ struct gpio_event *soft_events; -+ struct fsm_state *delay_target; -+ struct fsm_state *shutdown_target; -+ unsigned int num_signals; -+ unsigned int num_gpio_events; -+ unsigned int num_soft_events; -+ unsigned int delay_ms; -+ unsigned int shutdown_ms; -+}; -+ -+struct gpio_fsm { -+ struct gpio_chip gc; -+ struct device *dev; -+ spinlock_t spinlock; -+ struct work_struct work; -+ struct timer_list timer; -+ wait_queue_head_t shutdown_event; -+ struct fsm_state *states; -+ struct input_gpio_state *input_gpio_states; -+ struct gpio_descs *input_gpios; -+ struct gpio_descs *output_gpios; -+ struct soft_gpio *soft_gpios; -+ struct fsm_state *start_state; -+ struct fsm_state *shutdown_state; -+ unsigned int num_states; -+ unsigned int num_output_gpios; -+ unsigned int num_input_gpios; -+ unsigned int num_soft_gpios; -+ unsigned int shutdown_timeout_ms; -+ unsigned int shutdown_jiffies; -+ -+ struct fsm_state *current_state; -+ struct fsm_state *next_state; -+ struct fsm_state *delay_target_state; -+ int delay_ms; -+ unsigned int debug; -+ bool shutting_down; -+ struct symtab_entry *symtab; -+}; -+ -+static struct symtab_entry *do_add_symbol(struct symtab_entry **symtab, -+ const char *name, void *value) -+{ -+ struct symtab_entry **p = symtab; -+ -+ while (*p && strcmp((*p)->name, name)) -+ p = &(*p)->next; -+ -+ if (*p) { -+ /* This is an existing symbol */ -+ if ((*p)->value) { -+ /* Already defined */ -+ if (value) { -+ if ((uintptr_t)value < SYM_MAX) -+ return ERR_PTR(-EINVAL); -+ else -+ return ERR_PTR(-EEXIST); -+ } -+ } else { -+ /* Undefined */ -+ (*p)->value = value; -+ } -+ } else { -+ /* This is a new symbol */ -+ *p = kmalloc(sizeof(struct symtab_entry), GFP_KERNEL); -+ if (*p) { -+ (*p)->name = name; -+ (*p)->value = value; -+ (*p)->next = NULL; -+ } -+ } -+ return *p; -+} -+ -+static int add_symbol(struct symtab_entry **symtab, -+ const char *name, void *value) -+{ -+ struct symtab_entry *sym = do_add_symbol(symtab, name, value); -+ -+ return PTR_ERR_OR_ZERO(sym); -+} -+ -+static struct symtab_entry *get_symbol(struct symtab_entry **symtab, -+ const char *name) -+{ -+ struct symtab_entry *sym = do_add_symbol(symtab, name, NULL); -+ -+ if (IS_ERR(sym)) -+ return NULL; -+ return sym; -+} -+ -+static void free_symbols(struct symtab_entry **symtab) -+{ -+ struct symtab_entry *sym = *symtab; -+ void *p; -+ -+ *symtab = NULL; -+ while (sym) { -+ p = sym; -+ sym = sym->next; -+ kfree(p); -+ } -+} -+ -+static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off) -+{ -+ struct gpio_fsm *gf = gpiochip_get_data(gc); -+ struct soft_gpio *sg; -+ -+ if (off >= gf->num_soft_gpios) -+ return -EINVAL; -+ sg = &gf->soft_gpios[off]; -+ -+ return sg->dir; -+} -+ -+static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off) -+{ -+ struct gpio_fsm *gf = gpiochip_get_data(gc); -+ struct soft_gpio *sg; -+ -+ if (off >= gf->num_soft_gpios) -+ return -EINVAL; -+ sg = &gf->soft_gpios[off]; -+ -+ return sg->value; -+} -+ -+static void gpio_fsm_go_to_state(struct gpio_fsm *gf, -+ struct fsm_state *new_state) -+{ -+ struct input_gpio_state *inp_state; -+ struct gpio_event *gp_ev; -+ struct fsm_state *state; -+ int i; -+ -+ dev_dbg(gf->dev, "go_to_state(%s)\n", -+ new_state ? new_state->name : "<unset>"); -+ -+ spin_lock(&gf->spinlock); -+ -+ if (gf->next_state) { -+ /* Something else has already requested a transition */ -+ spin_unlock(&gf->spinlock); -+ return; -+ } -+ -+ gf->next_state = new_state; -+ state = gf->current_state; -+ gf->delay_target_state = NULL; -+ -+ if (state) { -+ /* Disarm any GPIO IRQs */ -+ for (i = 0; i < state->num_gpio_events; i++) { -+ gp_ev = &state->gpio_events[i]; -+ inp_state = &gf->input_gpio_states[gp_ev->index]; -+ inp_state->target = NULL; -+ } -+ } -+ -+ spin_unlock(&gf->spinlock); -+ -+ if (new_state) -+ schedule_work(&gf->work); -+} -+ -+static void gpio_fsm_set_soft(struct gpio_fsm *gf, -+ unsigned int off, int val) -+{ -+ struct soft_gpio *sg = &gf->soft_gpios[off]; -+ struct gpio_event *gp_ev; -+ struct fsm_state *state; -+ int i; -+ -+ dev_dbg(gf->dev, "set(%d,%d)\n", off, val); -+ state = gf->current_state; -+ sg->value = val; -+ for (i = 0; i < state->num_soft_events; i++) { -+ gp_ev = &state->soft_events[i]; -+ if (gp_ev->index == off && gp_ev->value == val) { -+ if (gf->debug) -+ dev_info(gf->dev, -+ "GF_SOFT %d->%d -> %s\n", gp_ev->index, -+ gp_ev->value, gp_ev->target->name); -+ gpio_fsm_go_to_state(gf, gp_ev->target); -+ break; -+ } -+ } -+} -+ -+static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off) -+{ -+ struct gpio_fsm *gf = gpiochip_get_data(gc); -+ struct soft_gpio *sg; -+ -+ if (off >= gf->num_soft_gpios) -+ return -EINVAL; -+ sg = &gf->soft_gpios[off]; -+ sg->dir = GPIOF_DIR_IN; -+ -+ return 0; -+} -+ -+static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off, -+ int value) -+{ -+ struct gpio_fsm *gf = gpiochip_get_data(gc); -+ struct soft_gpio *sg; -+ -+ if (off >= gf->num_soft_gpios) -+ return -EINVAL; -+ sg = &gf->soft_gpios[off]; -+ sg->dir = GPIOF_DIR_OUT; -+ gpio_fsm_set_soft(gf, off, value); -+ -+ return 0; -+} -+ -+static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val) -+{ -+ struct gpio_fsm *gf; -+ -+ gf = gpiochip_get_data(gc); -+ if (off < gf->num_soft_gpios) -+ gpio_fsm_set_soft(gf, off, val); -+} -+ -+static void gpio_fsm_enter_state(struct gpio_fsm *gf, -+ struct fsm_state *state) -+{ -+ struct input_gpio_state *inp_state; -+ struct output_signal *signal; -+ struct gpio_event *event; -+ struct gpio_desc *gpiod; -+ struct soft_gpio *soft; -+ int value; -+ int i; -+ -+ dev_dbg(gf->dev, "enter_state(%s)\n", state->name); -+ -+ gf->current_state = state; -+ -+ // 1. Apply any listed signals -+ for (i = 0; i < state->num_signals; i++) { -+ signal = &state->signals[i]; -+ -+ if (gf->debug) -+ dev_info(gf->dev, " set %s %d->%d\n", -+ (signal->type == SIGNAL_GPIO) ? "GF_OUT" : -+ "GF_SOFT", -+ signal->index, signal->value); -+ switch (signal->type) { -+ case SIGNAL_GPIO: -+ gpiod = gf->output_gpios->desc[signal->index]; -+ gpiod_set_value_cansleep(gpiod, signal->value); -+ break; -+ case SIGNAL_SOFT: -+ soft = &gf->soft_gpios[signal->index]; -+ gpio_fsm_set_soft(gf, signal->index, signal->value); -+ break; -+ } -+ } -+ -+ // 2. Exit if successfully reached shutdown state -+ if (gf->shutting_down && state == state->shutdown_target) { -+ wake_up(&gf->shutdown_event); -+ return; -+ } -+ -+ // 3. Schedule a timer callback if shutting down -+ if (state->shutdown_target) { -+ // Remember the absolute shutdown time in case remove is called -+ // at a later time. -+ gf->shutdown_jiffies = -+ jiffies + msecs_to_jiffies(state->shutdown_ms); -+ -+ if (gf->shutting_down) { -+ gf->delay_target_state = state->shutdown_target; -+ gf->delay_ms = state->shutdown_ms; -+ mod_timer(&gf->timer, gf->shutdown_jiffies); -+ } -+ } -+ -+ // During shutdown, skip everything else -+ if (gf->shutting_down) -+ return; -+ -+ // Otherwise record what the shutdown time would be -+ gf->shutdown_jiffies = jiffies + msecs_to_jiffies(state->shutdown_ms); -+ -+ // 4. Check soft inputs for transitions to take -+ for (i = 0; i < state->num_soft_events; i++) { -+ event = &state->soft_events[i]; -+ if (gf->soft_gpios[event->index].value == event->value) { -+ if (gf->debug) -+ dev_info(gf->dev, -+ "GF_SOFT %d=%d -> %s\n", event->index, -+ event->value, event->target->name); -+ gpio_fsm_go_to_state(gf, event->target); -+ return; -+ } -+ } -+ -+ // 5. Check GPIOs for transitions to take, enabling the IRQs -+ for (i = 0; i < state->num_gpio_events; i++) { -+ event = &state->gpio_events[i]; -+ inp_state = &gf->input_gpio_states[event->index]; -+ inp_state->target = event->target; -+ inp_state->value = event->value; -+ inp_state->enabled = true; -+ -+ value = gpiod_get_value(gf->input_gpios->desc[event->index]); -+ -+ // Clear stale event state -+ disable_irq(inp_state->irq); -+ -+ irq_set_irq_type(inp_state->irq, -+ (inp_state->value ^ inp_state->active_low) ? -+ IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING); -+ enable_irq(inp_state->irq); -+ -+ if (value == event->value && inp_state->target) { -+ if (gf->debug) -+ dev_info(gf->dev, -+ "GF_IN %d=%d -> %s\n", event->index, -+ event->value, event->target->name); -+ gpio_fsm_go_to_state(gf, event->target); -+ return; -+ } -+ } -+ -+ // 6. Schedule a timer callback if delay_target -+ if (state->delay_target) { -+ gf->delay_target_state = state->delay_target; -+ gf->delay_ms = state->delay_ms; -+ mod_timer(&gf->timer, -+ jiffies + msecs_to_jiffies(state->delay_ms)); -+ } -+} -+ -+static void gpio_fsm_work(struct work_struct *work) -+{ -+ struct input_gpio_state *inp_state; -+ struct fsm_state *new_state; -+ struct fsm_state *state; -+ struct gpio_event *gp_ev; -+ struct gpio_fsm *gf; -+ int i; -+ -+ gf = container_of(work, struct gpio_fsm, work); -+ spin_lock(&gf->spinlock); -+ state = gf->current_state; -+ new_state = gf->next_state; -+ if (!new_state) -+ new_state = gf->delay_target_state; -+ gf->next_state = NULL; -+ gf->delay_target_state = NULL; -+ spin_unlock(&gf->spinlock); -+ -+ if (state) { -+ /* Disable any enabled GPIO IRQs */ -+ for (i = 0; i < state->num_gpio_events; i++) { -+ gp_ev = &state->gpio_events[i]; -+ inp_state = &gf->input_gpio_states[gp_ev->index]; -+ if (inp_state->enabled) { -+ inp_state->enabled = false; -+ irq_set_irq_type(inp_state->irq, -+ IRQF_TRIGGER_NONE); -+ } -+ } -+ } -+ -+ if (new_state) -+ gpio_fsm_enter_state(gf, new_state); -+} -+ -+static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id) -+{ -+ struct input_gpio_state *inp_state = dev_id; -+ struct gpio_fsm *gf = inp_state->gf; -+ struct fsm_state *target; -+ -+ target = inp_state->target; -+ if (!target) -+ return IRQ_NONE; -+ -+ /* If the IRQ has fired then the desired state _must_ have occurred */ -+ inp_state->enabled = false; -+ irq_set_irq_type(inp_state->irq, IRQF_TRIGGER_NONE); -+ if (gf->debug) -+ dev_info(gf->dev, "GF_IN %d->%d -> %s\n", -+ inp_state->index, inp_state->value, target->name); -+ gpio_fsm_go_to_state(gf, target); -+ return IRQ_HANDLED; -+} -+ -+static void gpio_fsm_timer(struct timer_list *timer) -+{ -+ struct gpio_fsm *gf = container_of(timer, struct gpio_fsm, timer); -+ struct fsm_state *target; -+ -+ target = gf->delay_target_state; -+ if (!target) -+ return; -+ -+ if (gf->debug) -+ dev_info(gf->dev, "GF_DELAY %d -> %s\n", gf->delay_ms, -+ target->name); -+ -+ gpio_fsm_go_to_state(gf, target); -+} -+ -+int gpio_fsm_parse_signals(struct gpio_fsm *gf, struct fsm_state *state, -+ struct property *prop) -+{ -+ const __be32 *cells = prop->value; -+ struct output_signal *signal; -+ u32 io; -+ u32 type; -+ u32 index; -+ u32 value; -+ int ret = 0; -+ int i; -+ -+ if (prop->length % 8) { -+ dev_err(gf->dev, "malformed set in state %s\n", -+ state->name); -+ return -EINVAL; -+ } -+ -+ state->num_signals = prop->length/8; -+ state->signals = devm_kcalloc(gf->dev, state->num_signals, -+ sizeof(struct output_signal), -+ GFP_KERNEL); -+ for (i = 0; i < state->num_signals; i++) { -+ signal = &state->signals[i]; -+ io = be32_to_cpu(cells[0]); -+ type = GF_IO_TYPE(io); -+ index = GF_IO_INDEX(io); -+ value = be32_to_cpu(cells[1]); -+ -+ if (type != GF_OUT && type != GF_SOFT) { -+ dev_err(gf->dev, -+ "invalid set type %d in state %s\n", -+ type, state->name); -+ ret = -EINVAL; -+ break; -+ } -+ if (type == GF_OUT && index >= gf->num_output_gpios) { -+ dev_err(gf->dev, -+ "invalid GF_OUT number %d in state %s\n", -+ index, state->name); -+ ret = -EINVAL; -+ break; -+ } -+ if (type == GF_SOFT && index >= gf->num_soft_gpios) { -+ dev_err(gf->dev, -+ "invalid GF_SOFT number %d in state %s\n", -+ index, state->name); -+ ret = -EINVAL; -+ break; -+ } -+ if (value != 0 && value != 1) { -+ dev_err(gf->dev, -+ "invalid set value %d in state %s\n", -+ value, state->name); -+ ret = -EINVAL; -+ break; -+ } -+ signal->type = (type == GF_OUT) ? SIGNAL_GPIO : SIGNAL_SOFT; -+ signal->index = index; -+ signal->value = value; -+ cells += 2; -+ } -+ -+ return ret; -+} -+ -+struct gpio_event *new_event(struct gpio_event **events, int *num_events) -+{ -+ int num = ++(*num_events); -+ *events = krealloc(*events, num * sizeof(struct gpio_event), -+ GFP_KERNEL); -+ return *events ? *events + (num - 1) : NULL; -+} -+ -+int gpio_fsm_parse_events(struct gpio_fsm *gf, struct fsm_state *state, -+ struct property *prop) -+{ -+ const __be32 *cells = prop->value; -+ struct symtab_entry *sym; -+ int num_cells; -+ int ret = 0; -+ int i; -+ -+ if (prop->length % 8) { -+ dev_err(gf->dev, -+ "malformed transitions from state %s to state %s\n", -+ state->name, prop->name); -+ return -EINVAL; -+ } -+ -+ sym = get_symbol(&gf->symtab, prop->name); -+ num_cells = prop->length / 4; -+ i = 0; -+ while (i < num_cells) { -+ struct gpio_event *gp_ev; -+ u32 event, param; -+ u32 index; -+ -+ event = be32_to_cpu(cells[i++]); -+ param = be32_to_cpu(cells[i++]); -+ index = GF_IO_INDEX(event); -+ -+ switch (GF_IO_TYPE(event)) { -+ case GF_IN: -+ if (index >= gf->num_input_gpios) { -+ dev_err(gf->dev, -+ "invalid GF_IN %d in transitions from state %s to state %s\n", -+ index, state->name, prop->name); -+ return -EINVAL; -+ } -+ if (param > 1) { -+ dev_err(gf->dev, -+ "invalid GF_IN value %d in transitions from state %s to state %s\n", -+ param, state->name, prop->name); -+ return -EINVAL; -+ } -+ gp_ev = new_event(&state->gpio_events, -+ &state->num_gpio_events); -+ if (!gp_ev) -+ return -ENOMEM; -+ gp_ev->index = index; -+ gp_ev->value = param; -+ gp_ev->target = (struct fsm_state *)sym; -+ break; -+ -+ case GF_SOFT: -+ if (index >= gf->num_soft_gpios) { -+ dev_err(gf->dev, -+ "invalid GF_SOFT %d in transitions from state %s to state %s\n", -+ index, state->name, prop->name); -+ return -EINVAL; -+ } -+ if (param > 1) { -+ dev_err(gf->dev, -+ "invalid GF_SOFT value %d in transitions from state %s to state %s\n", -+ param, state->name, prop->name); -+ return -EINVAL; -+ } -+ gp_ev = new_event(&state->soft_events, -+ &state->num_soft_events); -+ if (!gp_ev) -+ return -ENOMEM; -+ gp_ev->index = index; -+ gp_ev->value = param; -+ gp_ev->target = (struct fsm_state *)sym; -+ break; -+ -+ case GF_DELAY: -+ if (state->delay_target) { -+ dev_err(gf->dev, -+ "state %s has multiple GF_DELAYs\n", -+ state->name); -+ return -EINVAL; -+ } -+ state->delay_target = (struct fsm_state *)sym; -+ state->delay_ms = param; -+ break; -+ -+ case GF_SHUTDOWN: -+ if (state->shutdown_target == state) { -+ dev_err(gf->dev, -+ "shutdown state %s has GF_SHUTDOWN\n", -+ state->name); -+ return -EINVAL; -+ } else if (state->shutdown_target) { -+ dev_err(gf->dev, -+ "state %s has multiple GF_SHUTDOWNs\n", -+ state->name); -+ return -EINVAL; -+ } -+ state->shutdown_target = -+ (struct fsm_state *)sym; -+ state->shutdown_ms = param; -+ break; -+ -+ default: -+ dev_err(gf->dev, -+ "invalid event %08x in transitions from state %s to state %s\n", -+ event, state->name, prop->name); -+ return -EINVAL; -+ } -+ } -+ if (i != num_cells) { -+ dev_err(gf->dev, -+ "malformed transitions from state %s to state %s\n", -+ state->name, prop->name); -+ return -EINVAL; -+ } -+ -+ return ret; -+} -+ -+int gpio_fsm_parse_state(struct gpio_fsm *gf, -+ struct fsm_state *state, -+ struct device_node *np) -+{ -+ struct symtab_entry *sym; -+ struct property *prop; -+ int ret; -+ -+ state->name = np->name; -+ ret = add_symbol(&gf->symtab, np->name, state); -+ if (ret) { -+ switch (ret) { -+ case -EINVAL: -+ dev_err(gf->dev, "'%s' is not a valid state name\n", -+ np->name); -+ break; -+ case -EEXIST: -+ dev_err(gf->dev, "state %s already defined\n", -+ np->name); -+ break; -+ default: -+ dev_err(gf->dev, "error %d adding state %s symbol\n", -+ ret, np->name); -+ break; -+ } -+ return ret; -+ } -+ -+ for_each_property_of_node(np, prop) { -+ sym = get_symbol(&gf->symtab, prop->name); -+ if (!sym) { -+ ret = -ENOMEM; -+ break; -+ } -+ -+ switch ((uintptr_t)sym->value) { -+ case SYM_SET: -+ ret = gpio_fsm_parse_signals(gf, state, prop); -+ break; -+ case SYM_START: -+ if (gf->start_state) { -+ dev_err(gf->dev, "multiple start states\n"); -+ ret = -EINVAL; -+ } else { -+ gf->start_state = state; -+ } -+ break; -+ case SYM_SHUTDOWN: -+ state->shutdown_target = state; -+ gf->shutdown_state = state; -+ break; -+ case SYM_NAME: -+ /* Ignore */ -+ break; -+ default: -+ /* A set of transition events to this state */ -+ ret = gpio_fsm_parse_events(gf, state, prop); -+ break; -+ } -+ } -+ -+ return ret; -+} -+ -+static void dump_all(struct gpio_fsm *gf) -+{ -+ int i, j; -+ -+ dev_info(gf->dev, "Input GPIOs:\n"); -+ for (i = 0; i < gf->num_input_gpios; i++) -+ dev_info(gf->dev, " %d: %p\n", i, -+ gf->input_gpios->desc[i]); -+ -+ dev_info(gf->dev, "Output GPIOs:\n"); -+ for (i = 0; i < gf->num_output_gpios; i++) -+ dev_info(gf->dev, " %d: %p\n", i, -+ gf->output_gpios->desc[i]); -+ -+ dev_info(gf->dev, "Soft GPIOs:\n"); -+ for (i = 0; i < gf->num_soft_gpios; i++) -+ dev_info(gf->dev, " %d: %s %d\n", i, -+ (gf->soft_gpios[i].dir == GPIOF_DIR_IN) ? "IN" : "OUT", -+ gf->soft_gpios[i].value); -+ -+ dev_info(gf->dev, "Start state: %s\n", -+ gf->start_state ? gf->start_state->name : "-"); -+ -+ dev_info(gf->dev, "Shutdown timeout: %d ms\n", -+ gf->shutdown_timeout_ms); -+ -+ for (i = 0; i < gf->num_states; i++) { -+ struct fsm_state *state = &gf->states[i]; -+ -+ dev_info(gf->dev, "State %s:\n", state->name); -+ -+ if (state->shutdown_target == state) -+ dev_info(gf->dev, " Shutdown state\n"); -+ -+ dev_info(gf->dev, " Signals:\n"); -+ for (j = 0; j < state->num_signals; j++) { -+ struct output_signal *signal = &state->signals[j]; -+ -+ dev_info(gf->dev, " %d: %s %d=%d\n", j, -+ (signal->type == SIGNAL_GPIO) ? "GPIO" : -+ "SOFT", -+ signal->index, signal->value); -+ } -+ -+ dev_info(gf->dev, " GPIO events:\n"); -+ for (j = 0; j < state->num_gpio_events; j++) { -+ struct gpio_event *event = &state->gpio_events[j]; -+ -+ dev_info(gf->dev, " %d: %d=%d -> %s\n", j, -+ event->index, event->value, -+ event->target->name); -+ } -+ -+ dev_info(gf->dev, " Soft events:\n"); -+ for (j = 0; j < state->num_soft_events; j++) { -+ struct gpio_event *event = &state->soft_events[j]; -+ -+ dev_info(gf->dev, " %d: %d=%d -> %s\n", j, -+ event->index, event->value, -+ event->target->name); -+ } -+ -+ if (state->delay_target) -+ dev_info(gf->dev, " Delay: %d ms -> %s\n", -+ state->delay_ms, state->delay_target->name); -+ -+ if (state->shutdown_target && state->shutdown_target != state) -+ dev_info(gf->dev, " Shutdown: %d ms -> %s\n", -+ state->shutdown_ms, -+ state->shutdown_target->name); -+ } -+ dev_info(gf->dev, "\n"); -+} -+ -+static int resolve_sym_to_state(struct gpio_fsm *gf, struct fsm_state **pstate) -+{ -+ struct symtab_entry *sym = (struct symtab_entry *)*pstate; -+ -+ if (!sym) -+ return -ENOMEM; -+ -+ *pstate = sym->value; -+ -+ if (!*pstate) { -+ dev_err(gf->dev, "state %s not defined\n", -+ sym->name); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int gpio_fsm_probe(struct platform_device *pdev) -+{ -+ struct input_gpio_state *inp_state; -+ struct device *dev = &pdev->dev; -+ struct device_node *np = dev->of_node; -+ struct device_node *cp; -+ struct gpio_fsm *gf; -+ u32 debug = 0; -+ int num_states; -+ u32 num_soft_gpios; -+ int ret; -+ int i; -+ static const char *const reserved_symbols[] = { -+ [SYM_NAME] = "name", -+ [SYM_SET] = "set", -+ [SYM_START] = "start_state", -+ [SYM_SHUTDOWN] = "shutdown_state", -+ }; -+ -+ if (of_property_read_u32(np, "num-soft-gpios", &num_soft_gpios)) { -+ dev_err(dev, "missing 'num-soft-gpios' property\n"); -+ return -EINVAL; -+ } -+ -+ of_property_read_u32(np, "debug", &debug); -+ -+ gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL); -+ if (!gf) -+ return -ENOMEM; -+ -+ gf->dev = dev; -+ gf->debug = debug; -+ -+ if (of_property_read_u32(np, "shutdown-timeout-ms", -+ &gf->shutdown_timeout_ms)) -+ gf->shutdown_timeout_ms = 5000; -+ -+ gf->num_soft_gpios = num_soft_gpios; -+ gf->soft_gpios = devm_kcalloc(dev, num_soft_gpios, -+ sizeof(struct soft_gpio), GFP_KERNEL); -+ if (!gf->soft_gpios) -+ return -ENOMEM; -+ for (i = 0; i < num_soft_gpios; i++) { -+ struct soft_gpio *sg = &gf->soft_gpios[i]; -+ -+ sg->dir = GPIOF_DIR_IN; -+ sg->value = 0; -+ } -+ -+ gf->input_gpios = devm_gpiod_get_array_optional(dev, "input", GPIOD_IN); -+ if (IS_ERR(gf->input_gpios)) { -+ ret = PTR_ERR(gf->input_gpios); -+ dev_err(dev, "failed to get input gpios from DT - %d\n", ret); -+ return ret; -+ } -+ gf->num_input_gpios = (gf->input_gpios ? gf->input_gpios->ndescs : 0); -+ -+ gf->input_gpio_states = devm_kcalloc(dev, gf->num_input_gpios, -+ sizeof(struct input_gpio_state), -+ GFP_KERNEL); -+ if (!gf->input_gpio_states) -+ return -ENOMEM; -+ for (i = 0; i < gf->num_input_gpios; i++) { -+ inp_state = &gf->input_gpio_states[i]; -+ inp_state->desc = gf->input_gpios->desc[i]; -+ inp_state->gf = gf; -+ inp_state->index = i; -+ inp_state->irq = gpiod_to_irq(inp_state->desc); -+ inp_state->active_low = gpiod_is_active_low(inp_state->desc); -+ if (inp_state->irq >= 0) -+ ret = devm_request_irq(gf->dev, inp_state->irq, -+ gpio_fsm_gpio_irq_handler, -+ IRQF_TRIGGER_NONE, -+ dev_name(dev), -+ inp_state); -+ else -+ ret = inp_state->irq; -+ -+ if (ret) { -+ dev_err(dev, -+ "failed to get IRQ for input gpio - %d\n", -+ ret); -+ return ret; -+ } -+ } -+ -+ gf->output_gpios = devm_gpiod_get_array_optional(dev, "output", -+ GPIOD_OUT_LOW); -+ if (IS_ERR(gf->output_gpios)) { -+ ret = PTR_ERR(gf->output_gpios); -+ dev_err(dev, "failed to get output gpios from DT - %d\n", ret); -+ return ret; -+ } -+ gf->num_output_gpios = (gf->output_gpios ? gf->output_gpios->ndescs : -+ 0); -+ -+ num_states = of_get_child_count(np); -+ if (!num_states) { -+ dev_err(dev, "no states declared\n"); -+ return -EINVAL; -+ } -+ gf->states = devm_kcalloc(dev, num_states, -+ sizeof(struct fsm_state), GFP_KERNEL); -+ if (!gf->states) -+ return -ENOMEM; -+ -+ // add reserved words to the symbol table -+ for (i = 0; i < ARRAY_SIZE(reserved_symbols); i++) { -+ if (reserved_symbols[i]) -+ add_symbol(&gf->symtab, reserved_symbols[i], (void *)i); -+ } -+ -+ // parse the state -+ for_each_child_of_node(np, cp) { -+ struct fsm_state *state = &gf->states[gf->num_states]; -+ -+ ret = gpio_fsm_parse_state(gf, state, cp); -+ if (ret) -+ return ret; -+ gf->num_states++; -+ } -+ -+ if (!gf->start_state) { -+ dev_err(gf->dev, "no start state defined\n"); -+ return -EINVAL; -+ } -+ -+ // resolve symbol pointers into state pointers -+ for (i = 0; !ret && i < gf->num_states; i++) { -+ struct fsm_state *state = &gf->states[i]; -+ int j; -+ -+ for (j = 0; !ret && j < state->num_gpio_events; j++) { -+ struct gpio_event *ev = &state->gpio_events[j]; -+ -+ ret = resolve_sym_to_state(gf, &ev->target); -+ } -+ -+ for (j = 0; !ret && j < state->num_soft_events; j++) { -+ struct gpio_event *ev = &state->soft_events[j]; -+ -+ ret = resolve_sym_to_state(gf, &ev->target); -+ } -+ -+ if (!ret) { -+ resolve_sym_to_state(gf, &state->delay_target); -+ if (state->shutdown_target != state) -+ resolve_sym_to_state(gf, -+ &state->shutdown_target); -+ } -+ } -+ -+ if (!ret && gf->debug > 1) -+ dump_all(gf); -+ -+ free_symbols(&gf->symtab); -+ -+ if (ret) -+ return ret; -+ -+ gf->gc.parent = dev; -+ gf->gc.label = np->name; -+ gf->gc.owner = THIS_MODULE; -+ gf->gc.of_node = np; -+ gf->gc.base = -1; -+ gf->gc.ngpio = num_soft_gpios; -+ -+ gf->gc.get_direction = gpio_fsm_get_direction; -+ gf->gc.direction_input = gpio_fsm_direction_input; -+ gf->gc.direction_output = gpio_fsm_direction_output; -+ gf->gc.get = gpio_fsm_get; -+ gf->gc.set = gpio_fsm_set; -+ gf->gc.can_sleep = true; -+ spin_lock_init(&gf->spinlock); -+ INIT_WORK(&gf->work, gpio_fsm_work); -+ timer_setup(&gf->timer, gpio_fsm_timer, 0); -+ init_waitqueue_head(&gf->shutdown_event); -+ -+ platform_set_drvdata(pdev, gf); -+ -+ if (gf->debug) -+ dev_info(gf->dev, "Start -> %s\n", gf->start_state->name); -+ -+ gpio_fsm_go_to_state(gf, gf->start_state); -+ -+ return devm_gpiochip_add_data(dev, &gf->gc, gf); -+} -+ -+static int gpio_fsm_remove(struct platform_device *pdev) -+{ -+ struct gpio_fsm *gf = platform_get_drvdata(pdev); -+ int i; -+ -+ if (gf->shutdown_state) { -+ if (gf->debug) -+ dev_info(gf->dev, "Shutting down...\n"); -+ -+ spin_lock(&gf->spinlock); -+ gf->shutting_down = true; -+ if (gf->current_state->shutdown_target && -+ gf->current_state->shutdown_target != gf->current_state) { -+ gf->delay_target_state = -+ gf->current_state->shutdown_target; -+ mod_timer(&gf->timer, gf->shutdown_jiffies); -+ } -+ spin_unlock(&gf->spinlock); -+ -+ wait_event_timeout(gf->shutdown_event, -+ gf->current_state->shutdown_target == -+ gf->current_state, -+ msecs_to_jiffies(gf->shutdown_timeout_ms)); -+ if (gf->current_state->shutdown_target == gf->current_state) -+ gpio_fsm_enter_state(gf, gf->shutdown_state); -+ } -+ cancel_work_sync(&gf->work); -+ del_timer_sync(&gf->timer); -+ -+ /* Events aren't allocated from managed storage */ -+ for (i = 0; i < gf->num_states; i++) { -+ kfree(gf->states[i].gpio_events); -+ kfree(gf->states[i].soft_events); -+ } -+ if (gf->debug) -+ dev_info(gf->dev, "Exiting\n"); -+ -+ return 0; -+} -+ -+static void gpio_fsm_shutdown(struct platform_device *pdev) -+{ -+ gpio_fsm_remove(pdev); -+} -+ -+static const struct of_device_id gpio_fsm_ids[] = { -+ { .compatible = "rpi,gpio-fsm" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(of, gpio_fsm_ids); -+ -+static struct platform_driver gpio_fsm_driver = { -+ .driver = { -+ .name = MODULE_NAME, -+ .of_match_table = of_match_ptr(gpio_fsm_ids), -+ }, -+ .probe = gpio_fsm_probe, -+ .remove = gpio_fsm_remove, -+ .shutdown = gpio_fsm_shutdown, -+}; -+module_platform_driver(gpio_fsm_driver); -+ -+MODULE_LICENSE("GPL"); -+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); -+MODULE_DESCRIPTION("GPIO FSM driver"); -+MODULE_ALIAS("platform:gpio-fsm"); ---- /dev/null -+++ b/include/dt-bindings/gpio/gpio-fsm.h -@@ -0,0 +1,21 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * This header provides constants for binding rpi,gpio-fsm. -+ */ -+ -+#ifndef _DT_BINDINGS_GPIO_FSM_H -+#define _DT_BINDINGS_GPIO_FSM_H -+ -+#define GF_IN 0 -+#define GF_OUT 1 -+#define GF_SOFT 2 -+#define GF_DELAY 3 -+#define GF_SHUTDOWN 4 -+ -+#define GF_IO(t, v) (((v) << 16) | ((t) & 0xffff)) -+ -+#define GF_IP(x) GF_IO(GF_IN, (x)) -+#define GF_OP(x) GF_IO(GF_OUT, (x)) -+#define GF_SW(x) GF_IO(GF_SOFT, (x)) -+ -+#endif |