// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPIO testing driver based on configfs.
*
* Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/array_size.h>
#include <linux/bitmap.h>
#include <linux/cleanup.h>
#include <linux/configfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irq_sim.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include "dev-sync-probe.h"
#define GPIO_SIM_NGPIO_MAX 1024
#define GPIO_SIM_PROP_MAX 5 /* Max 4 properties + sentinel. */
#define GPIO_SIM_NUM_ATTRS 3 /* value, pull and sentinel */
static DEFINE_IDA(gpio_sim_ida);
struct gpio_sim_chip {
struct gpio_chip gc;
struct device *dev;
unsigned long *request_map;
unsigned long *direction_map;
unsigned long *value_map;
unsigned long *pull_map;
struct irq_domain *irq_sim;
struct mutex lock;
const struct attribute_group **attr_groups;
};
struct gpio_sim_attribute {
struct device_attribute dev_attr;
unsigned int offset;
};
static struct gpio_sim_attribute *
to_gpio_sim_attr(struct device_attribute *dev_attr)
{
return container_of(dev_attr, struct gpio_sim_attribute, dev_attr);
}
static int gpio_sim_apply_pull(struct gpio_sim_chip *chip,
unsigned int offset, int value)
{
int irq, irq_type, ret;
guard(mutex)(&chip->lock);
if (test_bit(offset, chip->request_map) &&
test_bit(offset, chip->direction_map)) {
if (value == !!test_bit(offset, chip->value_map))
goto set_pull;
/*
* This is fine - it just means, nobody is listening
* for interrupts on this line, otherwise
* irq_create_mapping() would have been called from
* the to_irq() callback.
*/
irq = irq_find_mapping(chip->irq_sim, offset);
if (!irq)
goto set_value;
irq_type = irq_get_trigger_type(irq);
if ((value && (irq_type & IRQ_TYPE_EDGE_RISING)) ||
(!value && (irq_type & IRQ_TYPE_EDGE_FALLING))) {
ret = irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING,
true);
if (ret)
goto set_pull;
}
}
set_value:
/* Change the value unless we're actively driving the line. */
if (!test_bit(offset, chip->request_map) ||
test_bit(offset, chip->direction_map))
__assign_bit(offset, chip->value_map, value);
set_pull:
__assign_bit(offset, chip->pull_map, value);
return 0;
}
static int gpio_sim_get(struct gpio_chip *gc, unsigned int offset)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
guard(mutex)(&chip->lock);
return !!test_bit(offset, chip->value_map);
}
static int gpio_sim_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
scoped_guard(mutex, &chip->lock)
__assign_bit(offset, chip->value_map, value);
return 0;
}
static int gpio_sim_get_multiple(struct gpio_chip *gc,
unsigned long *mask, unsigned long *bits)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
scoped_guard(mutex, &chip->lock)
bitmap_replace(bits, bits, chip->value_map,
|