mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-19 05:38:00 +00:00
1322 lines
33 KiB
Diff
1322 lines
33 KiB
Diff
|
From 083bdfe2cb20a846287cf3b3e0fbd2dc83942748 Mon Sep 17 00:00:00 2001
|
||
|
From: Phil Elwell <phil@raspberrypi.com>
|
||
|
Date: Wed, 30 Sep 2020 12:00:54 +0100
|
||
|
Subject: [PATCH 0255/1085] 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>
|
||
|
|
||
|
gpio-fsm: Fix a build warning
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio-fsm: Rename 'num-soft-gpios' to avoid warning
|
||
|
|
||
|
As of 5.10, the Device Tree parser warns about properties that look
|
||
|
like references to "suppliers" of various services. "num-soft-gpios"
|
||
|
resembles a declaration of a GPIO called "num-soft", causing the value
|
||
|
to be interpreted as a phandle, the owner of which is checked for a
|
||
|
"#gpio-cells" property.
|
||
|
|
||
|
To avoid this warning, rename the gpio-fsm property to "num-swgpios".
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio-fsm: Show state info in /sys/class/gpio-fsm
|
||
|
|
||
|
Add gpio-fsm sysfs entries under /sys/class/gpio-fsm. For each state
|
||
|
machine show the current state, which state (if any) will be entered
|
||
|
after a delay, and the current value of that delay.
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio-fsm: Fix shutdown timeout handling
|
||
|
|
||
|
The driver is intended to jump directly to a shutdown state in the
|
||
|
event of a timeout during shutdown, but the sense of the test was
|
||
|
inverted.
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio-fsm: Clamp the delay time to zero
|
||
|
|
||
|
The sysfs delay_ms value is calculated live, and it is possible for
|
||
|
the time left to appear to be negative briefly if the timer handling
|
||
|
hasn't completed. Ensure the displayed value never goes below zero,
|
||
|
for the sake of appearances.
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio-fsm: Sort functions into a more logical order
|
||
|
|
||
|
Move some functions into a more logical ordering. This change causes
|
||
|
no functional change and is essentially cosmetic.
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
|
||
|
gpio_fsm: Rework the atomic-vs-non-atomic split
|
||
|
|
||
|
Partition the code to separate atomic and non-atomic methods so that
|
||
|
none of them have to handle both cases. The result avoids using deferred
|
||
|
work unless necessary, and should be easier to understand.
|
||
|
|
||
|
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
|
||
|
---
|
||
|
drivers/gpio/Kconfig | 9 +
|
||
|
drivers/gpio/Makefile | 1 +
|
||
|
drivers/gpio/gpio-fsm.c | 1212 +++++++++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 1222 insertions(+)
|
||
|
create mode 100644 drivers/gpio/gpio-fsm.c
|
||
|
|
||
|
--- a/drivers/gpio/Kconfig
|
||
|
+++ b/drivers/gpio/Kconfig
|
||
|
@@ -1299,6 +1299,15 @@ config GPIO_ELKHARTLAKE
|
||
|
To compile this driver as a module, choose M here: the module will
|
||
|
be called gpio-elkhartlake.
|
||
|
|
||
|
+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
|
||
|
@@ -63,6 +63,7 @@ obj-$(CONFIG_GPIO_EN7523) += gpio-en752
|
||
|
obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
|
||
|
obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o
|
||
|
obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o
|
||
|
+obj-$(CONFIG_GPIO_FSM) += gpio-fsm.o
|
||
|
obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
|
||
|
obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o
|
||
|
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/gpio/gpio-fsm.c
|
||
|
@@ -0,0 +1,1212 @@
|
||
|
+// 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/kdev_t.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/sysfs.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;
|
||
|
+ unsigned long delay_jiffies;
|
||
|
+ 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 void gpio_fsm_set_soft(struct gpio_fsm *gf,
|
||
|
+ unsigned int off, int 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;
|
||
|
+ gf->delay_target_state = NULL;
|
||
|
+
|
||
|
+ // 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_jiffies = gf->shutdown_jiffies;
|
||
|
+ gf->delay_target_state = state->shutdown_target;
|
||
|
+ gf->delay_ms = state->shutdown_ms;
|
||
|
+ mod_timer(&gf->timer, gf->delay_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_enter_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_cansleep(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_enter_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_jiffies = jiffies +
|
||
|
+ msecs_to_jiffies(state->delay_ms);
|
||
|
+ gf->delay_ms = state->delay_ms;
|
||
|
+ mod_timer(&gf->timer, gf->delay_jiffies);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+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>");
|
||
|
+
|
||
|
+ state = gf->current_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);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ gpio_fsm_enter_state(gf, new_state);
|
||
|
+}
|
||
|
+
|
||
|
+static void gpio_fsm_go_to_state_deferred(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_deferred(%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;
|
||
|
+
|
||
|
+ /* 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);
|
||
|
+
|
||
|
+ schedule_work(&gf->work);
|
||
|
+}
|
||
|
+
|
||
|
+static void gpio_fsm_work(struct work_struct *work)
|
||
|
+{
|
||
|
+ struct fsm_state *new_state;
|
||
|
+ struct gpio_fsm *gf;
|
||
|
+
|
||
|
+ gf = container_of(work, struct gpio_fsm, work);
|
||
|
+ spin_lock(&gf->spinlock);
|
||
|
+ new_state = gf->next_state;
|
||
|
+ gf->next_state = NULL;
|
||
|
+ spin_unlock(&gf->spinlock);
|
||
|
+
|
||
|
+ gpio_fsm_go_to_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_deferred(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_deferred(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 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_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_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 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_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;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * /sys/class/gpio-fsm/<fsm-name>/
|
||
|
+ * /state ... the current state
|
||
|
+ */
|
||
|
+
|
||
|
+static ssize_t state_show(struct device *dev,
|
||
|
+ struct device_attribute *attr, char *buf)
|
||
|
+{
|
||
|
+ const struct gpio_fsm *gf = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ return sprintf(buf, "%s\n", gf->current_state->name);
|
||
|
+}
|
||
|
+static DEVICE_ATTR_RO(state);
|
||
|
+
|
||
|
+static ssize_t delay_state_show(struct device *dev,
|
||
|
+ struct device_attribute *attr, char *buf)
|
||
|
+{
|
||
|
+ const struct gpio_fsm *gf = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ return sprintf(buf, "%s\n",
|
||
|
+ gf->delay_target_state ? gf->delay_target_state->name :
|
||
|
+ "-");
|
||
|
+}
|
||
|
+
|
||
|
+static DEVICE_ATTR_RO(delay_state);
|
||
|
+
|
||
|
+static ssize_t delay_ms_show(struct device *dev,
|
||
|
+ struct device_attribute *attr, char *buf)
|
||
|
+{
|
||
|
+ const struct gpio_fsm *gf = dev_get_drvdata(dev);
|
||
|
+ int jiffies_left;
|
||
|
+
|
||
|
+ jiffies_left = max((int)(gf->delay_jiffies - jiffies), 0);
|
||
|
+ return sprintf(buf,
|
||
|
+ gf->delay_target_state ? "%u\n" : "-\n",
|
||
|
+ jiffies_to_msecs(jiffies_left));
|
||
|
+}
|
||
|
+static DEVICE_ATTR_RO(delay_ms);
|
||
|
+
|
||
|
+static struct attribute *gpio_fsm_attrs[] = {
|
||
|
+ &dev_attr_state.attr,
|
||
|
+ &dev_attr_delay_state.attr,
|
||
|
+ &dev_attr_delay_ms.attr,
|
||
|
+ NULL,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct attribute_group gpio_fsm_group = {
|
||
|
+ .attrs = gpio_fsm_attrs,
|
||
|
+ //.is_visible = gpio_is_visible,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct attribute_group *gpio_fsm_groups[] = {
|
||
|
+ &gpio_fsm_group,
|
||
|
+ NULL
|
||
|
+};
|
||
|
+
|
||
|
+static struct attribute *gpio_fsm_class_attrs[] = {
|
||
|
+ // There are no top-level attributes
|
||
|
+ NULL,
|
||
|
+};
|
||
|
+ATTRIBUTE_GROUPS(gpio_fsm_class);
|
||
|
+
|
||
|
+static struct class gpio_fsm_class = {
|
||
|
+ .name = MODULE_NAME,
|
||
|
+
|
||
|
+ .class_groups = gpio_fsm_class_groups,
|
||
|
+};
|
||
|
+
|
||
|
+static int gpio_fsm_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct input_gpio_state *inp_state;
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ struct device *sysfs_dev;
|
||
|
+ struct device_node *np = dev_of_node(dev);
|
||
|
+ 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-swgpios", &num_soft_gpios) &&
|
||
|
+ of_property_read_u32(np, "num-soft-gpios", &num_soft_gpios)) {
|
||
|
+ dev_err(dev, "missing 'num-swgpios' 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 *)(uintptr_t)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.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);
|
||
|
+
|
||
|
+ sysfs_dev = device_create_with_groups(&gpio_fsm_class, dev,
|
||
|
+ MKDEV(0, 0), gf,
|
||
|
+ gpio_fsm_groups,
|
||
|
+ "%s", np->name);
|
||
|
+ if (IS_ERR(sysfs_dev))
|
||
|
+ dev_err(gf->dev, "Error creating sysfs entry\n");
|
||
|
+
|
||
|
+ if (gf->debug)
|
||
|
+ dev_info(gf->dev, "Start -> %s\n", gf->start_state->name);
|
||
|
+
|
||
|
+ gpio_fsm_enter_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));
|
||
|
+ /* On failure to reach a shutdown state, jump to one */
|
||
|
+ 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,
|
||
|
+};
|
||
|
+
|
||
|
+static int gpio_fsm_init(void)
|
||
|
+{
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ ret = class_register(&gpio_fsm_class);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ ret = platform_driver_register(&gpio_fsm_driver);
|
||
|
+ if (ret)
|
||
|
+ class_unregister(&gpio_fsm_class);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+module_init(gpio_fsm_init);
|
||
|
+
|
||
|
+static void gpio_fsm_exit(void)
|
||
|
+{
|
||
|
+ platform_driver_unregister(&gpio_fsm_driver);
|
||
|
+ class_unregister(&gpio_fsm_class);
|
||
|
+}
|
||
|
+module_exit(gpio_fsm_exit);
|
||
|
+
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
|
||
|
+MODULE_DESCRIPTION("GPIO FSM driver");
|
||
|
+MODULE_ALIAS("platform:gpio-fsm");
|