Merge branch 'chardev' into devel
This commit is contained in:
commit
60afe31d1f
|
@ -1,7 +1,7 @@
|
|||
What: /sys/class/gpio/
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Brownell <dbrownell@users.sourceforge.net>
|
||||
Contact: Linus Walleij <linusw@kernel.org>
|
||||
Description:
|
||||
|
||||
As a Kconfig option, individual GPIO signals may be accessed from
|
||||
|
@ -26,3 +26,5 @@ Description:
|
|||
/label ... (r/o) descriptive, not necessarily unique
|
||||
/ngpio ... (r/o) number of GPIOs; numbered N to N + (ngpio - 1)
|
||||
|
||||
This ABI is deprecated and will be removed after 2020. It is
|
||||
replaced with the GPIO character device.
|
|
@ -0,0 +1,26 @@
|
|||
What: /dev/gpiochip[0-9]+
|
||||
Date: November 2015
|
||||
KernelVersion: 4.4
|
||||
Contact: linux-gpio@vger.kernel.org
|
||||
Description:
|
||||
The character device files /dev/gpiochip* are the interface
|
||||
between GPIO chips and userspace.
|
||||
|
||||
The ioctl(2)-based ABI is defined and documented in
|
||||
[include/uapi]<linux/gpio.h>.
|
||||
|
||||
The following file operations are supported:
|
||||
|
||||
open(2)
|
||||
Currently the only useful flags are O_RDWR.
|
||||
|
||||
ioctl(2)
|
||||
Initiate various actions.
|
||||
See the inline documentation in [include/uapi]<linux/gpio.h>
|
||||
for descriptions of all ioctls.
|
||||
|
||||
close(2)
|
||||
Stops and free up the I/O contexts that was associated
|
||||
with the file descriptor.
|
||||
|
||||
Users: TBD
|
|
@ -4821,10 +4821,14 @@ L: linux-gpio@vger.kernel.org
|
|||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio.git
|
||||
S: Maintained
|
||||
F: Documentation/gpio/
|
||||
F: Documentation/ABI/testing/gpio-cdev
|
||||
F: Documentation/ABI/obsolete/sysfs-gpio
|
||||
F: drivers/gpio/
|
||||
F: include/linux/gpio/
|
||||
F: include/linux/gpio.h
|
||||
F: include/asm-generic/gpio.h
|
||||
F: include/uapi/linux/gpio.h
|
||||
F: tools/gpio/
|
||||
|
||||
GRE DEMULTIPLEXER DRIVER
|
||||
M: Dmitry Kozlov <xeb@mail.ru>
|
||||
|
|
|
@ -547,6 +547,7 @@ static struct class gpio_class = {
|
|||
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
||||
{
|
||||
struct gpio_chip *chip;
|
||||
struct gpio_device *gdev;
|
||||
struct gpiod_data *data;
|
||||
unsigned long flags;
|
||||
int status;
|
||||
|
@ -566,6 +567,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
|||
}
|
||||
|
||||
chip = desc->chip;
|
||||
gdev = chip->gpiodev;
|
||||
|
||||
mutex_lock(&sysfs_lock);
|
||||
|
||||
|
@ -605,7 +607,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
|
|||
if (chip->names && chip->names[offset])
|
||||
ioname = chip->names[offset];
|
||||
|
||||
dev = device_create_with_groups(&gpio_class, chip->parent,
|
||||
dev = device_create_with_groups(&gpio_class, &gdev->dev,
|
||||
MKDEV(0, 0), data, gpio_groups,
|
||||
ioname ? ioname : "gpio%u",
|
||||
desc_to_gpio(desc));
|
||||
|
@ -771,7 +773,7 @@ static int __init gpiolib_sysfs_init(void)
|
|||
{
|
||||
int status;
|
||||
unsigned long flags;
|
||||
struct gpio_chip *chip;
|
||||
struct gpio_device *gdev;
|
||||
|
||||
status = class_register(&gpio_class);
|
||||
if (status < 0)
|
||||
|
@ -784,8 +786,8 @@ static int __init gpiolib_sysfs_init(void)
|
|||
* registered, and so arch_initcall() can always gpio_export().
|
||||
*/
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
list_for_each_entry(chip, &gpio_chips, list) {
|
||||
if (chip->cdev)
|
||||
list_for_each_entry(gdev, &gpio_devices, list) {
|
||||
if (gdev->chip->cdev)
|
||||
continue;
|
||||
|
||||
/*
|
||||
|
@ -798,7 +800,7 @@ static int __init gpiolib_sysfs_init(void)
|
|||
* gpio_lock prevents us from doing this.
|
||||
*/
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
status = gpiochip_sysfs_register(chip);
|
||||
status = gpiochip_sysfs_register(gdev->chip);
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
}
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/gpio.h>
|
||||
|
||||
#include "gpiolib.h"
|
||||
|
||||
|
@ -42,6 +47,14 @@
|
|||
#define extra_checks 0
|
||||
#endif
|
||||
|
||||
/* Device and char device-related information */
|
||||
static DEFINE_IDA(gpio_ida);
|
||||
static dev_t gpio_devt;
|
||||
#define GPIO_DEV_MAX 256 /* 256 GPIO chip devices supported */
|
||||
static struct bus_type gpio_bus_type = {
|
||||
.name = "gpio",
|
||||
};
|
||||
|
||||
/* gpio_lock prevents conflicts during gpio_desc[] table updates.
|
||||
* While any GPIO is requested, its gpio_chip is not removable;
|
||||
* each GPIO's "requested" flag serves as a lock and refcount.
|
||||
|
@ -50,8 +63,7 @@ DEFINE_SPINLOCK(gpio_lock);
|
|||
|
||||
static DEFINE_MUTEX(gpio_lookup_lock);
|
||||
static LIST_HEAD(gpio_lookup_list);
|
||||
LIST_HEAD(gpio_chips);
|
||||
|
||||
LIST_HEAD(gpio_devices);
|
||||
|
||||
static void gpiochip_free_hogs(struct gpio_chip *chip);
|
||||
static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip);
|
||||
|
@ -67,15 +79,16 @@ static inline void desc_set_label(struct gpio_desc *d, const char *label)
|
|||
*/
|
||||
struct gpio_desc *gpio_to_desc(unsigned gpio)
|
||||
{
|
||||
struct gpio_chip *chip;
|
||||
struct gpio_device *gdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
|
||||
list_for_each_entry(chip, &gpio_chips, list) {
|
||||
if (chip->base <= gpio && chip->base + chip->ngpio > gpio) {
|
||||
list_for_each_entry(gdev, &gpio_devices, list) {
|
||||
if (gdev->chip->base <= gpio &&
|
||||
gdev->chip->base + gdev->chip->ngpio > gpio) {
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
return &chip->desc[gpio - chip->base];
|
||||
return &gdev->chip->desc[gpio - gdev->chip->base];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,16 +138,16 @@ EXPORT_SYMBOL_GPL(gpiod_to_chip);
|
|||
/* dynamic allocation of GPIOs, e.g. on a hotplugged device */
|
||||
static int gpiochip_find_base(int ngpio)
|
||||
{
|
||||
struct gpio_chip *chip;
|
||||
struct gpio_device *gdev;
|
||||
int base = ARCH_NR_GPIOS - ngpio;
|
||||
|
||||
list_for_each_entry_reverse(chip, &gpio_chips, list) {
|
||||
list_for_each_entry_reverse(gdev, &gpio_devices, list) {
|
||||
/* found a free space? */
|
||||
if (chip->base + chip->ngpio <= base)
|
||||
if (gdev->chip->base + gdev->chip->ngpio <= base)
|
||||
break;
|
||||
else
|
||||
/* nope, check the space right before the chip */
|
||||
base = chip->base - ngpio;
|
||||
base = gdev->chip->base - ngpio;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(base)) {
|
||||
|
@ -187,18 +200,28 @@ EXPORT_SYMBOL_GPL(gpiod_get_direction);
|
|||
* Return -EBUSY if the new chip overlaps with some other chip's integer
|
||||
* space.
|
||||
*/
|
||||
static int gpiochip_add_to_list(struct gpio_chip *chip)
|
||||
static int gpiodev_add_to_list(struct gpio_device *gdev)
|
||||
{
|
||||
struct gpio_chip *iterator;
|
||||
struct gpio_chip *previous = NULL;
|
||||
struct gpio_device *iterator;
|
||||
struct gpio_device *previous = NULL;
|
||||
|
||||
if (list_empty(&gpio_chips)) {
|
||||
list_add_tail(&chip->list, &gpio_chips);
|
||||
if (!gdev->chip)
|
||||
return -EINVAL;
|
||||
|
||||
if (list_empty(&gpio_devices)) {
|
||||
list_add_tail(&gdev->list, &gpio_devices);
|
||||
return 0;
|
||||
}
|
||||
|
||||
list_for_each_entry(iterator, &gpio_chips, list) {
|
||||
if (iterator->base >= chip->base + chip->ngpio) {
|
||||
list_for_each_entry(iterator, &gpio_devices, list) {
|
||||
/*
|
||||
* The list may contain dangling GPIO devices with no
|
||||
* live chip assigned.
|
||||
*/
|
||||
if (!iterator->chip)
|
||||
continue;
|
||||
if (iterator->chip->base >=
|
||||
gdev->chip->base + gdev->chip->ngpio) {
|
||||
/*
|
||||
* Iterator is the first GPIO chip so there is no
|
||||
* previous one
|
||||
|
@ -211,8 +234,8 @@ static int gpiochip_add_to_list(struct gpio_chip *chip)
|
|||
* [base, base + ngpio - 1]) between previous
|
||||
* and iterator chip.
|
||||
*/
|
||||
if (previous->base + previous->ngpio
|
||||
<= chip->base)
|
||||
if (previous->chip->base + previous->chip->ngpio
|
||||
<= gdev->chip->base)
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
@ -225,18 +248,18 @@ static int gpiochip_add_to_list(struct gpio_chip *chip)
|
|||
* Let iterator point to the last chip in the list.
|
||||
*/
|
||||
|
||||
iterator = list_last_entry(&gpio_chips, struct gpio_chip, list);
|
||||
if (iterator->base + iterator->ngpio <= chip->base) {
|
||||
list_add(&chip->list, &iterator->list);
|
||||
iterator = list_last_entry(&gpio_devices, struct gpio_device, list);
|
||||
if (iterator->chip->base + iterator->chip->ngpio <= gdev->chip->base) {
|
||||
list_add(&gdev->list, &iterator->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_err(chip->parent,
|
||||
dev_err(&gdev->dev,
|
||||
"GPIO integer space overlap, cannot add chip\n");
|
||||
return -EBUSY;
|
||||
|
||||
found:
|
||||
list_add_tail(&chip->list, &iterator->list);
|
||||
list_add_tail(&gdev->list, &iterator->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -245,16 +268,16 @@ found:
|
|||
*/
|
||||
static struct gpio_desc *gpio_name_to_desc(const char * const name)
|
||||
{
|
||||
struct gpio_chip *chip;
|
||||
struct gpio_device *gdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
|
||||
list_for_each_entry(chip, &gpio_chips, list) {
|
||||
list_for_each_entry(gdev, &gpio_devices, list) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i != chip->ngpio; ++i) {
|
||||
struct gpio_desc *gpio = &chip->desc[i];
|
||||
for (i = 0; i != gdev->chip->ngpio; ++i) {
|
||||
struct gpio_desc *gpio = &gdev->chip->desc[i];
|
||||
|
||||
if (!gpio->name || !name)
|
||||
continue;
|
||||
|
@ -290,8 +313,8 @@ static int gpiochip_set_desc_names(struct gpio_chip *gc)
|
|||
|
||||
gpio = gpio_name_to_desc(gc->names[i]);
|
||||
if (gpio)
|
||||
dev_warn(gc->parent, "Detected name collision for "
|
||||
"GPIO name '%s'\n",
|
||||
dev_warn(&gc->gpiodev->dev,
|
||||
"Detected name collision for GPIO name '%s'\n",
|
||||
gc->names[i]);
|
||||
}
|
||||
|
||||
|
@ -302,6 +325,88 @@ static int gpiochip_set_desc_names(struct gpio_chip *gc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpio_ioctl() - ioctl handler for the GPIO chardev
|
||||
*/
|
||||
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct gpio_device *gdev = filp->private_data;
|
||||
struct gpio_chip *chip = gdev->chip;
|
||||
int __user *ip = (int __user *)arg;
|
||||
struct gpiochip_info chipinfo;
|
||||
|
||||
/* We fail any subsequent ioctl():s when the chip is gone */
|
||||
if (!chip)
|
||||
return -ENODEV;
|
||||
|
||||
if (cmd == GPIO_GET_CHIPINFO_IOCTL) {
|
||||
/* Fill in the struct and pass to userspace */
|
||||
strncpy(chipinfo.name, dev_name(&gdev->dev),
|
||||
sizeof(chipinfo.name));
|
||||
chipinfo.name[sizeof(chipinfo.name)-1] = '\0';
|
||||
chipinfo.lines = chip->ngpio;
|
||||
if (copy_to_user(ip, &chipinfo, sizeof(chipinfo)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpio_chrdev_open() - open the chardev for ioctl operations
|
||||
* @inode: inode for this chardev
|
||||
* @filp: file struct for storing private data
|
||||
* Returns 0 on success
|
||||
*/
|
||||
static int gpio_chrdev_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct gpio_device *gdev = container_of(inode->i_cdev,
|
||||
struct gpio_device, chrdev);
|
||||
|
||||
/* Fail on open if the backing gpiochip is gone */
|
||||
if (!gdev || !gdev->chip)
|
||||
return -ENODEV;
|
||||
get_device(&gdev->dev);
|
||||
filp->private_data = gdev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gpio_chrdev_release() - close chardev after ioctl operations
|
||||
* @inode: inode for this chardev
|
||||
* @filp: file struct for storing private data
|
||||
* Returns 0 on success
|
||||
*/
|
||||
static int gpio_chrdev_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct gpio_device *gdev = container_of(inode->i_cdev,
|
||||
struct gpio_device, chrdev);
|
||||
|
||||
if (!gdev)
|
||||
return -ENODEV;
|
||||
put_device(&gdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations gpio_fileops = {
|
||||
.release = gpio_chrdev_release,
|
||||
.open = gpio_chrdev_open,
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = noop_llseek,
|
||||
.unlocked_ioctl = gpio_ioctl,
|
||||
.compat_ioctl = gpio_ioctl,
|
||||
};
|
||||
|
||||
static void gpiodevice_release(struct device *dev)
|
||||
{
|
||||
struct gpio_device *gdev = dev_get_drvdata(dev);
|
||||
|
||||
cdev_del(&gdev->chrdev);
|
||||
list_del(&gdev->list);
|
||||
ida_simple_remove(&gpio_ida, gdev->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* gpiochip_add_data() - register a gpio_chip
|
||||
* @chip: the chip to register, with chip->base initialized
|
||||
|
@ -323,19 +428,61 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
|
|||
{
|
||||
unsigned long flags;
|
||||
int status = 0;
|
||||
unsigned id;
|
||||
unsigned i;
|
||||
int base = chip->base;
|
||||
struct gpio_desc *descs;
|
||||
struct gpio_device *gdev;
|
||||
|
||||
descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL);
|
||||
if (!descs)
|
||||
/*
|
||||
* First: allocate and populate the internal stat container, and
|
||||
* set up the struct device.
|
||||
*/
|
||||
gdev = kmalloc(sizeof(*gdev), GFP_KERNEL);
|
||||
if (!gdev)
|
||||
return -ENOMEM;
|
||||
gdev->dev.bus = &gpio_bus_type;
|
||||
gdev->chip = chip;
|
||||
chip->gpiodev = gdev;
|
||||
if (chip->parent) {
|
||||
gdev->dev.parent = chip->parent;
|
||||
gdev->dev.of_node = chip->parent->of_node;
|
||||
} else {
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
/* If the gpiochip has an assigned OF node this takes precedence */
|
||||
if (chip->of_node)
|
||||
gdev->dev.of_node = chip->of_node;
|
||||
#endif
|
||||
}
|
||||
gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL);
|
||||
if (gdev->id < 0) {
|
||||
status = gdev->id;
|
||||
goto err_free_gdev;
|
||||
}
|
||||
dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
|
||||
device_initialize(&gdev->dev);
|
||||
dev_set_drvdata(&gdev->dev, gdev);
|
||||
if (chip->parent && chip->parent->driver)
|
||||
gdev->owner = chip->parent->driver->owner;
|
||||
else if (chip->owner)
|
||||
/* TODO: remove chip->owner */
|
||||
gdev->owner = chip->owner;
|
||||
else
|
||||
gdev->owner = THIS_MODULE;
|
||||
|
||||
/* FIXME: devm_kcalloc() these and move to gpio_device */
|
||||
descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL);
|
||||
if (!descs) {
|
||||
status = -ENOMEM;
|
||||
goto err_free_gdev;
|
||||
}
|
||||
|
||||
/* FIXME: move driver data into gpio_device dev_set_drvdata() */
|
||||
chip->data = data;
|
||||
|
||||
if (chip->ngpio == 0) {
|
||||
chip_err(chip, "tried to insert a GPIO chip with zero lines\n");
|
||||
return -EINVAL;
|
||||
status = -EINVAL;
|
||||
goto err_free_descs;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
|
@ -350,15 +497,16 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
|
|||
chip->base = base;
|
||||
}
|
||||
|
||||
status = gpiochip_add_to_list(chip);
|
||||
status = gpiodev_add_to_list(gdev);
|
||||
if (status) {
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
goto err_free_descs;
|
||||
}
|
||||
|
||||
for (id = 0; id < chip->ngpio; id++) {
|
||||
struct gpio_desc *desc = &descs[id];
|
||||
for (i = 0; i < chip->ngpio; i++) {
|
||||
struct gpio_desc *desc = &descs[i];
|
||||
|
||||
/* REVISIT: maybe a pointer to gpio_device is better */
|
||||
desc->chip = chip;
|
||||
|
||||
/* REVISIT: most hardware initializes GPIOs as inputs (often
|
||||
|
@ -369,18 +517,15 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
|
|||
*/
|
||||
desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0;
|
||||
}
|
||||
|
||||
chip->desc = descs;
|
||||
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
||||
#ifdef CONFIG_PINCTRL
|
||||
/* FIXME: move pin ranges to gpio_device */
|
||||
INIT_LIST_HEAD(&chip->pin_ranges);
|
||||
#endif
|
||||
|
||||
if (!chip->owner && chip->parent && chip->parent->driver)
|
||||
chip->owner = chip->parent->driver->owner;
|
||||
|
||||
status = gpiochip_set_desc_names(chip);
|
||||
if (status)
|
||||
goto err_remove_from_list;
|
||||
|
@ -391,28 +536,58 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
|
|||
|
||||
acpi_gpiochip_add(chip);
|
||||
|
||||
/*
|
||||
* By first adding the chardev, and then adding the device,
|
||||
* we get a device node entry in sysfs under
|
||||
* /sys/bus/gpio/devices/gpiochipN/dev that can be used for
|
||||
* coldplug of device nodes and other udev business.
|
||||
*/
|
||||
cdev_init(&gdev->chrdev, &gpio_fileops);
|
||||
gdev->chrdev.owner = THIS_MODULE;
|
||||
gdev->chrdev.kobj.parent = &gdev->dev.kobj;
|
||||
gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);
|
||||
status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1);
|
||||
if (status < 0)
|
||||
chip_warn(chip, "failed to add char device %d:%d\n",
|
||||
MAJOR(gpio_devt), gdev->id);
|
||||
else
|
||||
chip_dbg(chip, "added GPIO chardev (%d:%d)\n",
|
||||
MAJOR(gpio_devt), gdev->id);
|
||||
status = device_add(&gdev->dev);
|
||||
if (status)
|
||||
goto err_remove_chardev;
|
||||
|
||||
status = gpiochip_sysfs_register(chip);
|
||||
if (status)
|
||||
goto err_remove_chip;
|
||||
goto err_remove_device;
|
||||
|
||||
/* From this point, the .release() function cleans up gpio_device */
|
||||
gdev->dev.release = gpiodevice_release;
|
||||
get_device(&gdev->dev);
|
||||
pr_debug("%s: registered GPIOs %d to %d on device: %s\n", __func__,
|
||||
chip->base, chip->base + chip->ngpio - 1,
|
||||
chip->label ? : "generic");
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_device:
|
||||
device_del(&gdev->dev);
|
||||
err_remove_chardev:
|
||||
cdev_del(&gdev->chrdev);
|
||||
err_remove_chip:
|
||||
acpi_gpiochip_remove(chip);
|
||||
gpiochip_free_hogs(chip);
|
||||
of_gpiochip_remove(chip);
|
||||
err_remove_from_list:
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
list_del(&chip->list);
|
||||
list_del(&gdev->list);
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
chip->desc = NULL;
|
||||
err_free_descs:
|
||||
kfree(descs);
|
||||
|
||||
err_free_gdev:
|
||||
ida_simple_remove(&gpio_ida, gdev->id);
|
||||
kfree(gdev);
|
||||
/* failures here can mean systems won't boot... */
|
||||
pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__,
|
||||
chip->base, chip->base + chip->ngpio - 1,
|
||||
|
@ -429,15 +604,18 @@ EXPORT_SYMBOL_GPL(gpiochip_add_data);
|
|||
*/
|
||||
void gpiochip_remove(struct gpio_chip *chip)
|
||||
{
|
||||
struct gpio_device *gdev = chip->gpiodev;
|
||||
struct gpio_desc *desc;
|
||||
unsigned long flags;
|
||||
unsigned id;
|
||||
bool requested = false;
|
||||
|
||||
/* Numb the device, cancelling all outstanding operations */
|
||||
gdev->chip = NULL;
|
||||
|
||||
/* FIXME: should the legacy sysfs handling be moved to gpio_device? */
|
||||
gpiochip_sysfs_unregister(chip);
|
||||
|
||||
gpiochip_irqchip_remove(chip);
|
||||
|
||||
acpi_gpiochip_remove(chip);
|
||||
gpiochip_remove_pin_ranges(chip);
|
||||
gpiochip_free_hogs(chip);
|
||||
|
@ -450,15 +628,23 @@ void gpiochip_remove(struct gpio_chip *chip)
|
|||
if (test_bit(FLAG_REQUESTED, &desc->flags))
|
||||
requested = true;
|
||||
}
|
||||
list_del(&chip->list);
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
||||
if (requested)
|
||||
dev_crit(chip->parent,
|
||||
dev_crit(&chip->gpiodev->dev,
|
||||
"REMOVING GPIOCHIP WITH GPIOS STILL REQUESTED\n");
|
||||
|
||||
/* FIXME: need to be moved to gpio_device and held there */
|
||||
kfree(chip->desc);
|
||||
chip->desc = NULL;
|
||||
|
||||
/*
|
||||
* The gpiochip side puts its use of the device to rest here:
|
||||
* if there are no userspace clients, the chardev and device will
|
||||
* be removed, else it will be dangling until the last user is
|
||||
* gone.
|
||||
*/
|
||||
put_device(&gdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiochip_remove);
|
||||
|
||||
|
@ -477,17 +663,21 @@ struct gpio_chip *gpiochip_find(void *data,
|
|||
int (*match)(struct gpio_chip *chip,
|
||||
void *data))
|
||||
{
|
||||
struct gpio_device *gdev;
|
||||
struct gpio_chip *chip;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
list_for_each_entry(chip, &gpio_chips, list)
|
||||
if (match(chip, data))
|
||||
list_for_each_entry(gdev, &gpio_devices, list)
|
||||
if (match(gdev->chip, data))
|
||||
break;
|
||||
|
||||
/* No match? */
|
||||
if (&chip->list == &gpio_chips)
|
||||
if (&gdev->list == &gpio_devices)
|
||||
chip = NULL;
|
||||
else
|
||||
chip = gdev->chip;
|
||||
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
||||
return chip;
|
||||
|
@ -617,14 +807,14 @@ static int gpiochip_irq_reqres(struct irq_data *d)
|
|||
{
|
||||
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
|
||||
|
||||
if (!try_module_get(chip->owner))
|
||||
if (!try_module_get(chip->gpiodev->owner))
|
||||
return -ENODEV;
|
||||
|
||||
if (gpiochip_lock_as_irq(chip, d->hwirq)) {
|
||||
chip_err(chip,
|
||||
"unable to lock HW IRQ %lu for IRQ\n",
|
||||
d->hwirq);
|
||||
module_put(chip->owner);
|
||||
module_put(chip->gpiodev->owner);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
|
@ -635,7 +825,7 @@ static void gpiochip_irq_relres(struct irq_data *d)
|
|||
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
|
||||
|
||||
gpiochip_unlock_as_irq(chip, d->hwirq);
|
||||
module_put(chip->owner);
|
||||
module_put(chip->gpiodev->owner);
|
||||
}
|
||||
|
||||
static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
|
@ -985,10 +1175,10 @@ int gpiod_request(struct gpio_desc *desc, const char *label)
|
|||
if (!chip)
|
||||
goto done;
|
||||
|
||||
if (try_module_get(chip->owner)) {
|
||||
if (try_module_get(chip->gpiodev->owner)) {
|
||||
status = __gpiod_request(desc, label);
|
||||
if (status < 0)
|
||||
module_put(chip->owner);
|
||||
module_put(chip->gpiodev->owner);
|
||||
}
|
||||
|
||||
done:
|
||||
|
@ -1034,7 +1224,7 @@ static bool __gpiod_free(struct gpio_desc *desc)
|
|||
void gpiod_free(struct gpio_desc *desc)
|
||||
{
|
||||
if (desc && __gpiod_free(desc))
|
||||
module_put(desc->chip->owner);
|
||||
module_put(desc->chip->gpiodev->owner);
|
||||
else
|
||||
WARN_ON(extra_checks);
|
||||
}
|
||||
|
@ -2456,6 +2646,26 @@ void gpiod_put_array(struct gpio_descs *descs)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(gpiod_put_array);
|
||||
|
||||
static int __init gpiolib_dev_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Register GPIO sysfs bus */
|
||||
ret = bus_register(&gpio_bus_type);
|
||||
if (ret < 0) {
|
||||
pr_err("gpiolib: could not register GPIO bus type\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = alloc_chrdev_region(&gpio_devt, 0, GPIO_DEV_MAX, "gpiochip");
|
||||
if (ret < 0) {
|
||||
pr_err("gpiolib: failed to allocate char dev region\n");
|
||||
bus_unregister(&gpio_bus_type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
core_initcall(gpiolib_dev_init);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
||||
|
@ -2492,16 +2702,16 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
|||
static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct gpio_chip *chip = NULL;
|
||||
struct gpio_device *gdev = NULL;
|
||||
loff_t index = *pos;
|
||||
|
||||
s->private = "";
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
list_for_each_entry(chip, &gpio_chips, list)
|
||||
list_for_each_entry(gdev, &gpio_devices, list)
|
||||
if (index-- == 0) {
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
return chip;
|
||||
return gdev;
|
||||
}
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
||||
|
@ -2511,14 +2721,14 @@ static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos)
|
|||
static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct gpio_chip *chip = v;
|
||||
struct gpio_device *gdev = v;
|
||||
void *ret = NULL;
|
||||
|
||||
spin_lock_irqsave(&gpio_lock, flags);
|
||||
if (list_is_last(&chip->list, &gpio_chips))
|
||||
if (list_is_last(&gdev->list, &gpio_devices))
|
||||
ret = NULL;
|
||||
else
|
||||
ret = list_entry(chip->list.next, struct gpio_chip, list);
|
||||
ret = list_entry(gdev->list.next, struct gpio_device, list);
|
||||
spin_unlock_irqrestore(&gpio_lock, flags);
|
||||
|
||||
s->private = "\n";
|
||||
|
@ -2533,15 +2743,24 @@ static void gpiolib_seq_stop(struct seq_file *s, void *v)
|
|||
|
||||
static int gpiolib_seq_show(struct seq_file *s, void *v)
|
||||
{
|
||||
struct gpio_chip *chip = v;
|
||||
struct device *dev;
|
||||
struct gpio_device *gdev = v;
|
||||
struct gpio_chip *chip = gdev->chip;
|
||||
struct device *parent;
|
||||
|
||||
seq_printf(s, "%sGPIOs %d-%d", (char *)s->private,
|
||||
chip->base, chip->base + chip->ngpio - 1);
|
||||
dev = chip->parent;
|
||||
if (dev)
|
||||
seq_printf(s, ", %s/%s", dev->bus ? dev->bus->name : "no-bus",
|
||||
dev_name(dev));
|
||||
if (!chip) {
|
||||
seq_printf(s, "%s%s: (dangling chip)", (char *)s->private,
|
||||
dev_name(&gdev->dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
seq_printf(s, "%s%s: GPIOs %d-%d", (char *)s->private,
|
||||
dev_name(&gdev->dev),
|
||||
chip->base, chip->base + chip->ngpio - 1);
|
||||
parent = chip->parent;
|
||||
if (parent)
|
||||
seq_printf(s, ", parent: %s/%s",
|
||||
parent->bus ? parent->bus->name : "no-bus",
|
||||
dev_name(parent));
|
||||
if (chip->label)
|
||||
seq_printf(s, ", %s", chip->label);
|
||||
if (chip->can_sleep)
|
||||
|
|
|
@ -12,13 +12,40 @@
|
|||
#ifndef GPIOLIB_H
|
||||
#define GPIOLIB_H
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/cdev.h>
|
||||
|
||||
enum of_gpio_flags;
|
||||
enum gpiod_flags;
|
||||
struct acpi_device;
|
||||
|
||||
/**
|
||||
* struct gpio_device - internal state container for GPIO devices
|
||||
* @id: numerical ID number for the GPIO chip
|
||||
* @dev: the GPIO device struct
|
||||
* @chrdev: character device for the GPIO device
|
||||
* @owner: helps prevent removal of modules exporting active GPIOs
|
||||
* @chip: pointer to the corresponding gpiochip, holding static
|
||||
* data for this device
|
||||
* @list: links gpio_device:s together for traversal
|
||||
*
|
||||
* This state container holds most of the runtime variable data
|
||||
* for a GPIO device and can hold references and live on after the
|
||||
* GPIO chip has been removed, if it is still being used from
|
||||
* userspace.
|
||||
*/
|
||||
struct gpio_device {
|
||||
int id;
|
||||
struct device dev;
|
||||
struct cdev chrdev;
|
||||
struct module *owner;
|
||||
struct gpio_chip *chip;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct acpi_gpio_info - ACPI GPIO specific information
|
||||
* @gpioint: if %true this GPIO is of type GpioInt otherwise type is GpioIo
|
||||
|
@ -90,7 +117,7 @@ struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
|
|||
struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip, u16 hwnum);
|
||||
|
||||
extern struct spinlock gpio_lock;
|
||||
extern struct list_head gpio_chips;
|
||||
extern struct list_head gpio_devices;
|
||||
|
||||
struct gpio_desc {
|
||||
struct gpio_chip *chip;
|
||||
|
@ -149,17 +176,17 @@ static int __maybe_unused gpio_chip_hwgpio(const struct gpio_desc *desc)
|
|||
/* With chip prefix */
|
||||
|
||||
#define chip_emerg(chip, fmt, ...) \
|
||||
pr_emerg("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_emerg(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
#define chip_crit(chip, fmt, ...) \
|
||||
pr_crit("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_crit(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
#define chip_err(chip, fmt, ...) \
|
||||
pr_err("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_err(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
#define chip_warn(chip, fmt, ...) \
|
||||
pr_warn("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_warn(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
#define chip_info(chip, fmt, ...) \
|
||||
pr_info("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_info(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
#define chip_dbg(chip, fmt, ...) \
|
||||
pr_debug("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__)
|
||||
dev_dbg(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__)
|
||||
|
||||
#ifdef CONFIG_GPIO_SYSFS
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef __LINUX_GPIO_DRIVER_H
|
||||
#define __LINUX_GPIO_DRIVER_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/irq.h>
|
||||
|
@ -10,22 +11,22 @@
|
|||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/kconfig.h>
|
||||
|
||||
struct device;
|
||||
struct gpio_desc;
|
||||
struct of_phandle_args;
|
||||
struct device_node;
|
||||
struct seq_file;
|
||||
struct gpio_device;
|
||||
|
||||
#ifdef CONFIG_GPIOLIB
|
||||
|
||||
/**
|
||||
* struct gpio_chip - abstract a GPIO controller
|
||||
* @label: for diagnostics
|
||||
* @gpiodev: the internal state holder, opaque struct
|
||||
* @parent: optional parent device providing the GPIOs
|
||||
* @cdev: class device used by sysfs interface (may be NULL)
|
||||
* @owner: helps prevent removal of modules exporting active GPIOs
|
||||
* @data: per-instance data assigned by the driver
|
||||
* @list: links gpio_chips together for traversal
|
||||
* @request: optional hook for chip-specific activation, such as
|
||||
* enabling module power and clock; may sleep
|
||||
* @free: optional hook for chip-specific deactivation, such as
|
||||
|
@ -107,11 +108,11 @@ struct seq_file;
|
|||
*/
|
||||
struct gpio_chip {
|
||||
const char *label;
|
||||
struct gpio_device *gpiodev;
|
||||
struct device *parent;
|
||||
struct device *cdev;
|
||||
struct module *owner;
|
||||
void *data;
|
||||
struct list_head list;
|
||||
|
||||
int (*request)(struct gpio_chip *chip,
|
||||
unsigned offset);
|
||||
|
|
|
@ -138,6 +138,7 @@ header-y += genetlink.h
|
|||
header-y += gen_stats.h
|
||||
header-y += gfs2_ondisk.h
|
||||
header-y += gigaset_dev.h
|
||||
header-y += gpio.h
|
||||
header-y += gsmmux.h
|
||||
header-y += hdlcdrv.h
|
||||
header-y += hdlc.h
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* <linux/gpio.h> - userspace ABI for the GPIO character devices
|
||||
*
|
||||
* Copyright (C) 2015 Linus Walleij
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _UAPI_GPIO_H_
|
||||
#define _UAPI_GPIO_H_
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct gpiochip_info - Information about a certain GPIO chip
|
||||
* @name: the name of this GPIO chip
|
||||
* @lines: number of GPIO lines on this chip
|
||||
*/
|
||||
struct gpiochip_info {
|
||||
char name[32];
|
||||
__u32 lines;
|
||||
};
|
||||
|
||||
#define GPIO_GET_CHIPINFO_IOCTL _IOR('o', 0x01, struct gpiochip_info)
|
||||
|
||||
#endif /* _UAPI_GPIO_H_ */
|
|
@ -13,6 +13,7 @@ help:
|
|||
@echo ' cpupower - a tool for all things x86 CPU power'
|
||||
@echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer'
|
||||
@echo ' freefall - laptop accelerometer program for disk protection'
|
||||
@echo ' gpio - GPIO tools'
|
||||
@echo ' hv - tools used when in Hyper-V clients'
|
||||
@echo ' iio - IIO tools'
|
||||
@echo ' lguest - a minimal 32-bit x86 hypervisor'
|
||||
|
@ -53,7 +54,7 @@ acpi: FORCE
|
|||
cpupower: FORCE
|
||||
$(call descend,power/$@)
|
||||
|
||||
cgroup firewire hv guest spi usb virtio vm net iio: FORCE
|
||||
cgroup firewire hv guest spi usb virtio vm net iio gpio: FORCE
|
||||
$(call descend,$@)
|
||||
|
||||
liblockdep: FORCE
|
||||
|
@ -119,7 +120,7 @@ acpi_clean:
|
|||
cpupower_clean:
|
||||
$(call descend,power/cpupower,clean)
|
||||
|
||||
cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean:
|
||||
cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean gpio_clean:
|
||||
$(call descend,$(@:_clean=),clean)
|
||||
|
||||
liblockdep_clean:
|
||||
|
@ -155,6 +156,7 @@ build_clean:
|
|||
clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean lguest_clean \
|
||||
perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \
|
||||
vm_clean net_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
|
||||
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean
|
||||
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
|
||||
gpio_clean
|
||||
|
||||
.PHONY: FORCE
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
CC = $(CROSS_COMPILE)gcc
|
||||
CFLAGS += -Wall -g -D_GNU_SOURCE
|
||||
|
||||
all: lsgpio
|
||||
|
||||
lsgpio: lsgpio.o gpio-utils.o
|
||||
|
||||
%.o: %.c gpio-utils.h
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f *.o lsgpio
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* GPIO tools - helpers library for the GPIO tools
|
||||
*
|
||||
* Copyright (C) 2015 Linus Walleij
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "gpio-utils.h"
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* GPIO tools - utility helpers library for the GPIO tools
|
||||
*
|
||||
* Copyright (C) 2015 Linus Walleij
|
||||
*
|
||||
* Portions copied from iio_utils and lssio:
|
||||
* Copyright (c) 2010 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
|
||||
* Copyright (c) 2008 Jonathan Cameron
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _GPIO_UTILS_H_
|
||||
#define _GPIO_UTILS_H_
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static inline int check_prefix(const char *str, const char *prefix)
|
||||
{
|
||||
return strlen(str) > strlen(prefix) &&
|
||||
strncmp(str, prefix, strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
#endif /* _GPIO_UTILS_H_ */
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* lsgpio - example on how to list the GPIO lines on a system
|
||||
*
|
||||
* Copyright (C) 2015 Linus Walleij
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* Usage:
|
||||
* lsgpio <-n device-name>
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "gpio-utils.h"
|
||||
|
||||
int list_device(const char *device_name)
|
||||
{
|
||||
struct gpiochip_info cinfo;
|
||||
char *chrdev_name;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
ret = asprintf(&chrdev_name, "/dev/%s", device_name);
|
||||
if (ret < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
fd = open(chrdev_name, 0);
|
||||
if (fd == -1) {
|
||||
ret = -errno;
|
||||
fprintf(stderr, "Failed to open %s\n", chrdev_name);
|
||||
goto free_chrdev_name;
|
||||
}
|
||||
|
||||
/* Inspect this GPIO chip */
|
||||
ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
fprintf(stderr, "Failed to retrieve GPIO fd\n");
|
||||
if (close(fd) == -1)
|
||||
perror("Failed to close GPIO character device file");
|
||||
|
||||
goto free_chrdev_name;
|
||||
}
|
||||
fprintf(stdout, "GPIO chip: %s, %u GPIO lines\n",
|
||||
cinfo.name, cinfo.lines);
|
||||
|
||||
if (close(fd) == -1) {
|
||||
ret = -errno;
|
||||
goto free_chrdev_name;
|
||||
}
|
||||
|
||||
free_chrdev_name:
|
||||
free(chrdev_name);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void print_usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: lsgpio [options]...\n"
|
||||
"List GPIO chips, lines and states\n"
|
||||
" -n <name> List GPIOs on a named device\n"
|
||||
" -? This helptext\n"
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *device_name;
|
||||
int ret;
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "n:")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
device_name = optarg;
|
||||
break;
|
||||
case '?':
|
||||
print_usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (device_name)
|
||||
ret = list_device(device_name);
|
||||
else {
|
||||
const struct dirent *ent;
|
||||
DIR *dp;
|
||||
|
||||
/* List all GPIO devices one at a time */
|
||||
dp = opendir("/dev");
|
||||
if (!dp) {
|
||||
ret = -errno;
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
ret = -ENOENT;
|
||||
while (ent = readdir(dp), ent) {
|
||||
if (check_prefix(ent->d_name, "gpiochip")) {
|
||||
ret = list_device(ent->d_name);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
if (closedir(dp) == -1) {
|
||||
perror("scanning devices: Failed to close directory");
|
||||
ret = -errno;
|
||||
}
|
||||
}
|
||||
error_out:
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue