// 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 #include #include #include #include #include #include #include #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 : ""); 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// * /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 "); MODULE_DESCRIPTION("GPIO FSM driver"); MODULE_ALIAS("platform:gpio-fsm");