// 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/bitmap.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/configfs.h>
#include <linux/device.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/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/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include "gpiolib.h"
#define GPIO_SIM_NGPIO_MAX 1024
#define GPIO_SIM_PROP_MAX 4 /* Max 3 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;
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;
struct gpio_desc *desc;
struct gpio_chip *gc;
gc = &chip->gc;
desc = &gc->gpiodev->descs[offset];
guard(mutex)(&chip->lock);
if (test_bit(FLAG_REQUESTED, &desc->flags) &&
!test_bit(FLAG_IS_OUT, &desc->flags)) {
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(FLAG_REQUESTED, &desc->flags) ||
!test_bit(FLAG_IS_OUT, &desc->flags))
__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 void 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);
}
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, mask, gc->ngpio);
return 0;
}
static void gpio_sim_set_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(chip->value_map, chip->value_map, bits, mask,
gc->ngpio);
}
static int gpio_sim_direction_output(struct gpio_chip *gc,
unsigned int offset, int value)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
scoped_guard(mutex, &chip->lock) {
__clear_bit(offset, chip->direction_map);
__assign_bit(offset, chip->value_map, value);
}
return 0;
}
static int gpio_sim_direction_input(struct gpio_chip *gc, unsigned int offset)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
scoped_guard(mutex, &chip->lock)
__set_bit(offset, chip->direction_map);
return 0;
}
static int gpio_sim_get_direction(struct gpio_chip *gc, unsigned int offset)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
int direction;
scoped_guard(mutex, &chip->lock)
direction = !!test_bit(offset, chip->direction_map);
return direction ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
}
static int gpio_sim_set_config(struct gpio_chip *gc,
unsigned int offset, unsigned long config)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
switch (pinconf_to_config_param(config)) {
case PIN_CONFIG_BIAS_PULL_UP:
return gpio_sim_apply_pull(chip, offset, 1);
case PIN_CONFIG_BIAS_PULL_DOWN:
return gpio_sim_apply_pull(chip, offset, 0);
default:
break;
}
return -ENOTSUPP;
}
static int gpio_sim_to_irq(struct gpio_chip *gc, unsigned int offset)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
return irq_create_mapping(chip->irq_sim, offset);
}
static void gpio_sim_free(struct gpio_chip *gc, unsigned int offset)
{
struct gpio_sim_chip *chip = gpiochip_get_data(gc);
scoped_guard(mutex, &chip->lock)
__assign_bit(offset, chip->value_map,
!!test_bit(offset, chip->pull_map));
}
static ssize_t gpio_sim_sysfs_val_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
struct gpio_sim_chip *chip = dev_get_drvdata(dev);
int val;
scoped_guard(mutex, &chip->lock)
val = !!test_bit(line_attr->offset, chip->value_map);
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t gpio_sim_sysfs_val_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
/*
* Not assigning this function will result in write() returning -EIO
* which is confusing. Return -EPERM explicitly.
*/
return -EPERM;
}
static const char *const gpio_sim_sysfs_pull_strings[] = {
[0] = "pull-down",
[1] = "pull-up",
};
static ssize_t gpio_sim_sysfs_pull_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
struct gpio_sim_chip *chip = dev_get_drvdata(dev);
int pull;
scoped_guard(mutex, &chip->lock)
pull = !!test_bit(line_attr->offset, chip->pull_map);
return sysfs_emit(buf, "%s\n", gpio_sim_sysfs_pull_strings[pull]);
}
static ssize_t gpio_sim_sysfs_pull_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
struct gpio_sim_chip *chip = dev_get_drvdata(dev);
int ret, pull;
pull = sysfs_match_string(gpio_sim_sysfs_pull_strings, buf);
if (pull < 0)
return pull;
ret = gpio_sim_apply_pull(chip, line_attr->offset, pull);
if (ret)
return ret;
return len;
}
static void gpio_sim_mutex_destroy(void *data)
{
struct mutex *lock = data;
mutex_destroy(lock);
}
static void gpio_sim_dispose_mappings(void *data)
{
struct gpio_sim_chip *chip = data;
unsigned int i;
for (i = 0; i < chip->gc.ngpio; i++)
irq_dispose_mapping(irq_find_mapping(chip->irq_sim, i));
}
static void gpio_sim_sysfs_remove(void *data)
{
struct gpio_sim_chip *chip = data;
sysfs_remove_groups(&chip->gc.gpiodev->dev.kobj, chip->attr_groups);
}
static int gpio_sim_setup_sysfs(struct gpio_sim_chip *chip)
{
struct device_attribute *val_dev_attr, *pull_dev_attr;
struct gpio_sim_attribute *val_attr, *pull_attr;
unsigned int num_lines = chip->gc.ngpio;
struct device *dev = chip->gc.parent;
struct attribute_group *attr_group;
struct attribute **attrs;
int i, ret;
chip->attr_groups = devm_kcalloc(dev, sizeof(*chip->attr_groups),
num_lines + 1, GFP_KERNEL);
if (!chip->attr_groups)
return -ENOMEM;
for (i = 0; i < num_lines; i++) {
attr_group = devm_kzalloc(dev, sizeof(*attr_group), GFP_KERNEL);
attrs = devm_kcalloc(dev, GPIO_SIM_NUM_ATTRS, sizeof
|