diff options
Diffstat (limited to 'drivers/gpio/gpiolib.c')
-rw-r--r-- | drivers/gpio/gpiolib.c | 173 |
1 files changed, 123 insertions, 50 deletions
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 996a73390..be74bd370 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -20,6 +20,7 @@ #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> +#include <linux/compat.h> #include <uapi/linux/gpio.h> #include "gpiolib.h" @@ -316,7 +317,7 @@ 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; + void __user *ip = (void __user *)arg; /* We fail any subsequent ioctl():s when the chip is gone */ if (!chip) @@ -388,6 +389,14 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EINVAL; } +#ifdef CONFIG_COMPAT +static long gpio_ioctl_compat(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return gpio_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + /** * gpio_chrdev_open() - open the chardev for ioctl operations * @inode: inode for this chardev @@ -431,7 +440,9 @@ static const struct file_operations gpio_fileops = { .owner = THIS_MODULE, .llseek = noop_llseek, .unlocked_ioctl = gpio_ioctl, - .compat_ioctl = gpio_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = gpio_ioctl_compat, +#endif }; static void gpiodevice_release(struct device *dev) @@ -616,21 +627,38 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data) goto err_free_label; } + spin_unlock_irqrestore(&gpio_lock, flags); + for (i = 0; i < chip->ngpio; i++) { struct gpio_desc *desc = &gdev->descs[i]; desc->gdev = gdev; - - /* REVISIT: most hardware initializes GPIOs as inputs (often - * with pullups enabled) so power usage is minimized. Linux - * code should set the gpio direction first thing; but until - * it does, and in case chip->get_direction is not set, we may - * expose the wrong direction in sysfs. + /* + * REVISIT: most hardware initializes GPIOs as inputs + * (often with pullups enabled) so power usage is + * minimized. Linux code should set the gpio direction + * first thing; but until it does, and in case + * chip->get_direction is not set, we may expose the + * wrong direction in sysfs. */ - desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0; - } - spin_unlock_irqrestore(&gpio_lock, flags); + if (chip->get_direction) { + /* + * If we have .get_direction, set up the initial + * direction flag from the hardware. + */ + int dir = chip->get_direction(chip, i); + + if (!dir) + set_bit(FLAG_IS_OUT, &desc->flags); + } else if (!chip->direction_input) { + /* + * If the chip lacks the .direction_input callback + * we logically assume all lines are outputs. + */ + set_bit(FLAG_IS_OUT, &desc->flags); + } + } #ifdef CONFIG_PINCTRL INIT_LIST_HEAD(&gdev->pin_ranges); @@ -1552,8 +1580,8 @@ EXPORT_SYMBOL_GPL(gpiod_direction_input); static int _gpiod_direction_output_raw(struct gpio_desc *desc, int value) { - struct gpio_chip *chip; - int status = -EINVAL; + struct gpio_chip *gc = desc->gdev->chip; + int ret; /* GPIOs used for IRQs shall not be set as output */ if (test_bit(FLAG_USED_AS_IRQ, &desc->flags)) { @@ -1563,28 +1591,50 @@ static int _gpiod_direction_output_raw(struct gpio_desc *desc, int value) return -EIO; } - /* Open drain pin should not be driven to 1 */ - if (value && test_bit(FLAG_OPEN_DRAIN, &desc->flags)) - return gpiod_direction_input(desc); - - /* Open source pin should not be driven to 0 */ - if (!value && test_bit(FLAG_OPEN_SOURCE, &desc->flags)) - return gpiod_direction_input(desc); + if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) { + /* First see if we can enable open drain in hardware */ + if (gc->set_single_ended) { + ret = gc->set_single_ended(gc, gpio_chip_hwgpio(desc), + LINE_MODE_OPEN_DRAIN); + if (!ret) + goto set_output_value; + } + /* Emulate open drain by not actively driving the line high */ + if (value) + return gpiod_direction_input(desc); + } + else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) { + if (gc->set_single_ended) { + ret = gc->set_single_ended(gc, gpio_chip_hwgpio(desc), + LINE_MODE_OPEN_SOURCE); + if (!ret) + goto set_output_value; + } + /* Emulate open source by not actively driving the line low */ + if (!value) + return gpiod_direction_input(desc); + } else { + /* Make sure to disable open drain/source hardware, if any */ + if (gc->set_single_ended) + gc->set_single_ended(gc, + gpio_chip_hwgpio(desc), + LINE_MODE_PUSH_PULL); + } - chip = desc->gdev->chip; - if (!chip->set || !chip->direction_output) { +set_output_value: + if (!gc->set || !gc->direction_output) { gpiod_warn(desc, "%s: missing set() or direction_output() operations\n", __func__); return -EIO; } - status = chip->direction_output(chip, gpio_chip_hwgpio(desc), value); - if (status == 0) + ret = gc->direction_output(gc, gpio_chip_hwgpio(desc), value); + if (!ret) set_bit(FLAG_IS_OUT, &desc->flags); trace_gpio_value(desc_to_gpio(desc), 0, value); - trace_gpio_direction(desc_to_gpio(desc), 0, status); - return status; + trace_gpio_direction(desc_to_gpio(desc), 0, ret); + return ret; } /** @@ -1846,10 +1896,10 @@ static void gpio_chip_set_multiple(struct gpio_chip *chip, } } -static void gpiod_set_array_value_priv(bool raw, bool can_sleep, - unsigned int array_size, - struct gpio_desc **desc_array, - int *value_array) +void gpiod_set_array_value_complex(bool raw, bool can_sleep, + unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) { int i = 0; @@ -1955,8 +2005,8 @@ void gpiod_set_raw_array_value(unsigned int array_size, { if (!desc_array) return; - gpiod_set_array_value_priv(true, false, array_size, desc_array, - value_array); + gpiod_set_array_value_complex(true, false, array_size, desc_array, + value_array); } EXPORT_SYMBOL_GPL(gpiod_set_raw_array_value); @@ -1977,8 +2027,8 @@ void gpiod_set_array_value(unsigned int array_size, { if (!desc_array) return; - gpiod_set_array_value_priv(false, false, array_size, desc_array, - value_array); + gpiod_set_array_value_complex(false, false, array_size, desc_array, + value_array); } EXPORT_SYMBOL_GPL(gpiod_set_array_value); @@ -2003,8 +2053,8 @@ EXPORT_SYMBOL_GPL(gpiod_cansleep); */ int gpiod_to_irq(const struct gpio_desc *desc) { - struct gpio_chip *chip; - int offset; + struct gpio_chip *chip; + int offset; /* * Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics @@ -2016,7 +2066,16 @@ int gpiod_to_irq(const struct gpio_desc *desc) chip = desc->gdev->chip; offset = gpio_chip_hwgpio(desc); - return chip->to_irq ? chip->to_irq(chip, offset) : -ENXIO; + if (chip->to_irq) { + int retirq = chip->to_irq(chip, offset); + + /* Zero means NO_IRQ */ + if (!retirq) + return -ENXIO; + + return retirq; + } + return -ENXIO; } EXPORT_SYMBOL_GPL(gpiod_to_irq); @@ -2030,17 +2089,30 @@ EXPORT_SYMBOL_GPL(gpiod_to_irq); */ int gpiochip_lock_as_irq(struct gpio_chip *chip, unsigned int offset) { - if (offset >= chip->ngpio) - return -EINVAL; + struct gpio_desc *desc; + + desc = gpiochip_get_desc(chip, offset); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + /* Flush direction if something changed behind our back */ + if (chip->get_direction) { + int dir = chip->get_direction(chip, offset); - if (test_bit(FLAG_IS_OUT, &chip->gpiodev->descs[offset].flags)) { + if (dir) + clear_bit(FLAG_IS_OUT, &desc->flags); + else + set_bit(FLAG_IS_OUT, &desc->flags); + } + + if (test_bit(FLAG_IS_OUT, &desc->flags)) { chip_err(chip, "%s: tried to flag a GPIO set as output for IRQ\n", __func__); return -EIO; } - set_bit(FLAG_USED_AS_IRQ, &chip->gpiodev->descs[offset].flags); + set_bit(FLAG_USED_AS_IRQ, &desc->flags); return 0; } EXPORT_SYMBOL_GPL(gpiochip_lock_as_irq); @@ -2188,8 +2260,8 @@ void gpiod_set_raw_array_value_cansleep(unsigned int array_size, might_sleep_if(extra_checks); if (!desc_array) return; - gpiod_set_array_value_priv(true, true, array_size, desc_array, - value_array); + gpiod_set_array_value_complex(true, true, array_size, desc_array, + value_array); } EXPORT_SYMBOL_GPL(gpiod_set_raw_array_value_cansleep); @@ -2211,8 +2283,8 @@ void gpiod_set_array_value_cansleep(unsigned int array_size, might_sleep_if(extra_checks); if (!desc_array) return; - gpiod_set_array_value_priv(false, true, array_size, desc_array, - value_array); + gpiod_set_array_value_complex(false, true, array_size, desc_array, + value_array); } EXPORT_SYMBOL_GPL(gpiod_set_array_value_cansleep); @@ -2726,15 +2798,16 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, local_desc = gpiochip_request_own_desc(chip, hwnum, name); if (IS_ERR(local_desc)) { - pr_err("requesting hog GPIO %s (chip %s, offset %d) failed\n", - name, chip->label, hwnum); - return PTR_ERR(local_desc); + status = PTR_ERR(local_desc); + pr_err("requesting hog GPIO %s (chip %s, offset %d) failed, %d\n", + name, chip->label, hwnum, status); + return status; } status = gpiod_configure_flags(desc, name, lflags, dflags); if (status < 0) { - pr_err("setup of hog GPIO %s (chip %s, offset %d) failed\n", - name, chip->label, hwnum); + pr_err("setup of hog GPIO %s (chip %s, offset %d) failed, %d\n", + name, chip->label, hwnum, status); gpiochip_free_own_desc(desc); return status; } |