// SPDX-License-Identifier: GPL-2.0
#include <linux/anon_inodes.h>
#include <linux/bitmap.h>
#include <linux/cdev.h>
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/timekeeping.h>
#include <linux/uaccess.h>
#include <uapi/linux/gpio.h>
#include "gpiolib.h"
#include "gpiolib-cdev.h"
/* Character device interface to GPIO.
*
* The GPIO character device, /dev/gpiochipN, provides userspace an
* interface to gpiolib GPIOs via ioctl()s.
*/
/*
* GPIO line handle management
*/
/**
* struct linehandle_state - contains the state of a userspace handle
* @gdev: the GPIO device the handle pertains to
* @label: consumer label used to tag descriptors
* @descs: the GPIO descriptors held by this handle
* @num_descs: the number of descriptors held in the descs array
*/
struct linehandle_state {
struct gpio_device *gdev;
const char *label;
struct gpio_desc *descs[GPIOHANDLES_MAX];
u32 num_descs;
};
#define GPIOHANDLE_REQUEST_VALID_FLAGS \
(GPIOHANDLE_REQUEST_INPUT | \
GPIOHANDLE_REQUEST_OUTPUT | \
GPIOHANDLE_REQUEST_ACTIVE_LOW | \
GPIOHANDLE_REQUEST_BIAS_PULL_UP | \
GPIOHANDLE_REQUEST_BIAS_PULL_DOWN | \
GPIOHANDLE_REQUEST_BIAS_DISABLE | \
GPIOHANDLE_REQUEST_OPEN_DRAIN | \
GPIOHANDLE_REQUEST_OPEN_SOURCE)
static int linehandle_validate_flags(u32 flags)
{
/* Return an error if an unknown flag is set */
if (flags & ~GPIOHANDLE_REQUEST_VALID_FLAGS)
return -EINVAL;
/*
* Do not allow both INPUT & OUTPUT flags to be set as they are
* contradictory.
*/
if ((flags & GPIOHANDLE_REQUEST_INPUT) &&
(flags & GPIOHANDLE_REQUEST_OUTPUT))
return -EINVAL;
/*
* Do not allow OPEN_SOURCE & OPEN_DRAIN flags in a single request. If
* the hardware actually supports enabling both at the same time the
* electrical result would be disastrous.
*/
if ((flags & GPIOHANDLE_REQUEST_OPEN_DRAIN) &&
(flags & GPIOHANDLE_REQUEST_OPEN_SOURCE))
return -EINVAL;
/* OPEN_DRAIN and OPEN_SOURCE flags only make sense for output mode. */
if (!(flags & GPIOHANDLE_REQUEST_OUTPUT) &&
((flags & GPIOHANDLE_REQUEST_OPEN_DRAIN) ||
(flags & GPIOHANDLE_REQUEST_OPEN_SOURCE)))
return -EINVAL;
/* Bias flags only allowed for input or output mode. */
if (!((flags & GPIOHANDLE_REQUEST_INPUT) ||
(flags & GPIOHANDLE_REQUEST_OUTPUT)) &&
((flags & GPIOHANDLE_REQUEST_BIAS_DISABLE) ||
(flags & GPIOHANDLE_REQUEST_BIAS_PULL_UP) ||
(flags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN)))
return -EINVAL;
/* Only one bias flag can be set. */
if (((flags & GPIOHANDLE_REQUEST_BIAS_DISABLE) &&
(flags & (GPIOHANDLE_REQUEST_BIAS_PULL_DOWN |
GPIOHANDLE_REQUEST_BIAS_PULL_UP))) ||
((flags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) &&
(flags & GPIOHANDLE_REQUEST_BIAS_PULL_UP)))
return -EINVAL;
return 0;
}
static void linehandle_flags_to_desc_flags(u32 lflags, unsigned long *flagsp)
{
assign_bit(FLAG_ACTIVE_LOW, flagsp,
lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW);
assign_bit(FLAG_OPEN_DRAIN, flagsp,
lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN);
assign_bit(FLAG_OPEN_SOURCE, flagsp,
lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE);
assign_bit(FLAG_PULL_UP, flagsp,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP);
assign_bit(FLAG_PULL_DOWN, flagsp,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN);
assign_bit(FLAG_BIAS_DISABLE, flagsp,
lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE);
}
static long linehandle_set_config(struct linehandle_state *lh,
void __user *ip)
{
struct gpiohandle_config gcnf;
struct gpio_desc *desc;
int i, ret;
u32 lflags;
if (copy_from_user(&gcnf, ip, sizeof(gcnf)))
return -EFAULT;
lflags = gcnf.flags;
ret = linehandle_validate_flags(lflags);
if (ret)
return ret;
for (i = 0; i < lh->