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, 1182 insertions, 0 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 new file mode 100644 index 0000000000..7076026e7c --- /dev/null +++ b/target/linux/bcm27xx/patches-5.4/950-0986-gpio-Add-gpio-fsm-driver.patch @@ -0,0 +1,1182 @@ +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 |