| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304 |
- From f0b841f737e04175f295a77245508b10efd117fb Mon Sep 17 00:00:00 2001
- From: Phil Elwell <[email protected]>
- 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 <[email protected]>
- gpio-fsm: Fix a build warning
- Signed-off-by: Phil Elwell <[email protected]>
- 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 <[email protected]>
- 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 <[email protected]>
- 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 <[email protected]>
- 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 <[email protected]>
- ---
- drivers/gpio/Kconfig | 9 +
- drivers/gpio/Makefile | 1 +
- drivers/gpio/gpio-fsm.c | 1210 +++++++++++++++++++++++++++++++++++++++
- 3 files changed, 1220 insertions(+)
- create mode 100644 drivers/gpio/gpio-fsm.c
- --- a/drivers/gpio/Kconfig
- +++ b/drivers/gpio/Kconfig
- @@ -1244,6 +1244,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
- @@ -61,6 +61,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,1210 @@
- +// 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 <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 int 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 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_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_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_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_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;
- +}
- +
- +
- +/*
- + * /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,
- + .owner = THIS_MODULE,
- +
- + .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;
- + 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.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);
- +
- + 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_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));
- + /* 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 <[email protected]>");
- +MODULE_DESCRIPTION("GPIO FSM driver");
- +MODULE_ALIAS("platform:gpio-fsm");
|