diff options
Diffstat (limited to 'drivers/gpio/gpiolib.c')
-rw-r--r-- | drivers/gpio/gpiolib.c | 375 |
1 files changed, 303 insertions, 72 deletions
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index bdbc1649eafa..9913886ede90 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -86,6 +86,7 @@ static int gpiochip_add_irqchip(struct gpio_chip *gpiochip, struct lock_class_key *lock_key, struct lock_class_key *request_key); static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip); +static int gpiochip_irqchip_init_hw(struct gpio_chip *gpiochip); static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gpiochip); static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gpiochip); @@ -389,6 +390,14 @@ static void gpiochip_free_valid_mask(struct gpio_chip *gpiochip) gpiochip->valid_mask = NULL; } +static int gpiochip_add_pin_ranges(struct gpio_chip *gc) +{ + if (gc->add_pin_ranges) + return gc->add_pin_ranges(gc); + + return 0; +} + bool gpiochip_line_is_valid(const struct gpio_chip *gpiochip, unsigned int offset) { @@ -421,9 +430,127 @@ struct linehandle_state { (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_configure_flag(unsigned long *flagsp, + u32 bit, bool active) +{ + if (active) + set_bit(bit, flagsp); + else + clear_bit(bit, flagsp); +} + +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; + unsigned long *flagsp; + + 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->numdescs; i++) { + desc = lh->descs[i]; + flagsp = &desc->flags; + + linehandle_configure_flag(flagsp, FLAG_ACTIVE_LOW, + lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW); + + linehandle_configure_flag(flagsp, FLAG_OPEN_DRAIN, + lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN); + + linehandle_configure_flag(flagsp, FLAG_OPEN_SOURCE, + lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE); + + linehandle_configure_flag(flagsp, FLAG_PULL_UP, + lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP); + + linehandle_configure_flag(flagsp, FLAG_PULL_DOWN, + lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN); + + linehandle_configure_flag(flagsp, FLAG_BIAS_DISABLE, + lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE); + + /* + * Lines have to be requested explicitly for input + * or output, else the line will be treated "as is". + */ + if (lflags & GPIOHANDLE_REQUEST_OUTPUT) { + int val = !!gcnf.default_values[i]; + + ret = gpiod_direction_output(desc, val); + if (ret) + return ret; + } else if (lflags & GPIOHANDLE_REQUEST_INPUT) { + ret = gpiod_direction_input(desc); + if (ret) + return ret; + } + } + return 0; +} + static long linehandle_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { @@ -474,6 +601,8 @@ static long linehandle_ioctl(struct file *filep, unsigned int cmd, lh->descs, NULL, vals); + } else if (cmd == GPIOHANDLE_SET_CONFIG_IOCTL) { + return linehandle_set_config(lh, ip); } return -EINVAL; } @@ -525,32 +654,9 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) lflags = handlereq.flags; - /* Return an error if an unknown flag is set */ - if (lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) - return -EINVAL; - - /* - * Do not allow both INPUT & OUTPUT flags to be set as they are - * contradictory. - */ - if ((lflags & GPIOHANDLE_REQUEST_INPUT) && - (lflags & 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 ((lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) && - (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE)) - return -EINVAL; - - /* OPEN_DRAIN and OPEN_SOURCE flags only make sense for output mode. */ - if (!(lflags & GPIOHANDLE_REQUEST_OUTPUT) && - ((lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) || - (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE))) - return -EINVAL; + ret = linehandle_validate_flags(lflags); + if (ret) + return ret; lh = kzalloc(sizeof(*lh), GFP_KERNEL); if (!lh) @@ -592,6 +698,12 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) set_bit(FLAG_OPEN_DRAIN, &desc->flags); if (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE) set_bit(FLAG_OPEN_SOURCE, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE) + set_bit(FLAG_BIAS_DISABLE, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) + set_bit(FLAG_PULL_DOWN, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP) + set_bit(FLAG_PULL_UP, &desc->flags); ret = gpiod_set_transitory(desc, false); if (ret < 0) @@ -894,6 +1006,32 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) if (copy_from_user(&eventreq, ip, sizeof(eventreq))) return -EFAULT; + offset = eventreq.lineoffset; + lflags = eventreq.handleflags; + eflags = eventreq.eventflags; + + if (offset >= gdev->ngpio) + return -EINVAL; + + /* Return an error if a unknown flag is set */ + if ((lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) || + (eflags & ~GPIOEVENT_REQUEST_VALID_FLAGS)) + return -EINVAL; + + /* This is just wrong: we don't look for events on output lines */ + if ((lflags & GPIOHANDLE_REQUEST_OUTPUT) || + (lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) || + (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE)) + return -EINVAL; + + /* Only one bias flag can be set. */ + if (((lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE) && + (lflags & (GPIOHANDLE_REQUEST_BIAS_PULL_DOWN | + GPIOHANDLE_REQUEST_BIAS_PULL_UP))) || + ((lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) && + (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP))) + return -EINVAL; + le = kzalloc(sizeof(*le), GFP_KERNEL); if (!le) return -ENOMEM; @@ -911,30 +1049,6 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) } } - offset = eventreq.lineoffset; - lflags = eventreq.handleflags; - eflags = eventreq.eventflags; - - if (offset >= gdev->ngpio) { - ret = -EINVAL; - goto out_free_label; - } - - /* Return an error if a unknown flag is set */ - if ((lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) || - (eflags & ~GPIOEVENT_REQUEST_VALID_FLAGS)) { - ret = -EINVAL; - goto out_free_label; - } - - /* This is just wrong: we don't look for events on output lines */ - if ((lflags & GPIOHANDLE_REQUEST_OUTPUT) || - (lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) || - (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE)) { - ret = -EINVAL; - goto out_free_label; - } - desc = &gdev->descs[offset]; ret = gpiod_request(desc, le->label); if (ret) @@ -944,6 +1058,12 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) if (lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW) set_bit(FLAG_ACTIVE_LOW, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE) + set_bit(FLAG_BIAS_DISABLE, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) + set_bit(FLAG_PULL_DOWN, &desc->flags); + if (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP) + set_bit(FLAG_PULL_UP, &desc->flags); ret = gpiod_direction_input(desc); if (ret) @@ -1097,6 +1217,12 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) lineinfo.flags |= (GPIOLINE_FLAG_OPEN_SOURCE | GPIOLINE_FLAG_IS_OUT); + if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) + lineinfo.flags |= GPIOLINE_FLAG_BIAS_DISABLE; + if (test_bit(FLAG_PULL_DOWN, &desc->flags)) + lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN; + if (test_bit(FLAG_PULL_UP, &desc->flags)) + lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_UP; if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) return -EFAULT; @@ -1402,6 +1528,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, } } + ret = gpiochip_add_pin_ranges(chip); + if (ret) + goto err_remove_of_chip; + acpi_gpiochip_add(chip); machine_gpiochip_add(chip); @@ -1410,6 +1540,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, if (ret) goto err_remove_acpi_chip; + ret = gpiochip_irqchip_init_hw(chip); + if (ret) + goto err_remove_acpi_chip; + ret = gpiochip_add_irqchip(chip, lock_key, request_key); if (ret) goto err_remove_irqchip_mask; @@ -1439,6 +1573,7 @@ err_remove_of_chip: gpiochip_free_hogs(chip); of_gpiochip_remove(chip); err_free_gpiochip_mask: + gpiochip_remove_pin_ranges(chip); gpiochip_free_valid_mask(chip); err_remove_from_list: spin_lock_irqsave(&gpio_lock, flags); @@ -1494,8 +1629,8 @@ void gpiochip_remove(struct gpio_chip *chip) gdev->chip = NULL; gpiochip_irqchip_remove(chip); acpi_gpiochip_remove(chip); - gpiochip_remove_pin_ranges(chip); of_gpiochip_remove(chip); + gpiochip_remove_pin_ranges(chip); gpiochip_free_valid_mask(chip); /* * We accept no more calls into the driver from this point, so @@ -1534,7 +1669,7 @@ static void devm_gpio_chip_release(struct device *dev, void *res) } /** - * devm_gpiochip_add_data() - Resource manager gpiochip_add_data() + * devm_gpiochip_add_data() - Resource managed gpiochip_add_data() * @dev: pointer to the device that gpio_chip belongs to. * @chip: the chip to register, with chip->base initialized * @data: driver-private data associated with this chip @@ -1622,6 +1757,16 @@ static struct gpio_chip *find_chip_by_name(const char *name) * The following is irqchip helper code for gpiochips. */ +static int gpiochip_irqchip_init_hw(struct gpio_chip *gc) +{ + struct gpio_irq_chip *girq = &gc->irq; + + if (!girq->init_hw) + return 0; + + return girq->init_hw(gc); +} + static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc) { struct gpio_irq_chip *girq = &gc->irq; @@ -2446,8 +2591,13 @@ static inline int gpiochip_add_irqchip(struct gpio_chip *gpiochip, { return 0; } - static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip) {} + +static inline int gpiochip_irqchip_init_hw(struct gpio_chip *gpiochip) +{ + return 0; +} + static inline int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gpiochip) { return 0; @@ -2770,6 +2920,9 @@ static bool gpiod_free_commit(struct gpio_desc *desc) clear_bit(FLAG_REQUESTED, &desc->flags); clear_bit(FLAG_OPEN_DRAIN, &desc->flags); clear_bit(FLAG_OPEN_SOURCE, &desc->flags); + clear_bit(FLAG_PULL_UP, &desc->flags); + clear_bit(FLAG_PULL_DOWN, &desc->flags); + clear_bit(FLAG_BIAS_DISABLE, &desc->flags); clear_bit(FLAG_IS_HOGGED, &desc->flags); ret = true; } @@ -2896,6 +3049,7 @@ static int gpio_set_config(struct gpio_chip *gc, unsigned offset, unsigned arg; switch (mode) { + case PIN_CONFIG_BIAS_DISABLE: case PIN_CONFIG_BIAS_PULL_DOWN: case PIN_CONFIG_BIAS_PULL_UP: arg = 1; @@ -2909,6 +3063,26 @@ static int gpio_set_config(struct gpio_chip *gc, unsigned offset, return gc->set_config ? gc->set_config(gc, offset, config) : -ENOTSUPP; } +static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc) +{ + int bias = 0; + int ret = 0; + + if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) + bias = PIN_CONFIG_BIAS_DISABLE; + else if (test_bit(FLAG_PULL_UP, &desc->flags)) + bias = PIN_CONFIG_BIAS_PULL_UP; + else if (test_bit(FLAG_PULL_DOWN, &desc->flags)) + bias = PIN_CONFIG_BIAS_PULL_DOWN; + + if (bias) { + ret = gpio_set_config(chip, gpio_chip_hwgpio(desc), bias); + if (ret != -ENOTSUPP) + return ret; + } + return 0; +} + /** * gpiod_direction_input - set the GPIO direction to input * @desc: GPIO to set to input @@ -2953,15 +3127,10 @@ int gpiod_direction_input(struct gpio_desc *desc) __func__); return -EIO; } - if (ret == 0) + if (ret == 0) { clear_bit(FLAG_IS_OUT, &desc->flags); - - if (test_bit(FLAG_PULL_UP, &desc->flags)) - gpio_set_config(chip, gpio_chip_hwgpio(desc), - PIN_CONFIG_BIAS_PULL_UP); - else if (test_bit(FLAG_PULL_DOWN, &desc->flags)) - gpio_set_config(chip, gpio_chip_hwgpio(desc), - PIN_CONFIG_BIAS_PULL_DOWN); + ret = gpio_set_bias(chip, desc); + } trace_gpio_direction(desc_to_gpio(desc), 1, ret); @@ -3070,8 +3239,10 @@ int gpiod_direction_output(struct gpio_desc *desc, int value) if (!ret) goto set_output_value; /* Emulate open drain by not actively driving the line high */ - if (value) - return gpiod_direction_input(desc); + if (value) { + ret = gpiod_direction_input(desc); + goto set_output_flag; + } } else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) { ret = gpio_set_config(gc, gpio_chip_hwgpio(desc), @@ -3079,15 +3250,31 @@ int gpiod_direction_output(struct gpio_desc *desc, int value) if (!ret) goto set_output_value; /* Emulate open source by not actively driving the line low */ - if (!value) - return gpiod_direction_input(desc); + if (!value) { + ret = gpiod_direction_input(desc); + goto set_output_flag; + } } else { gpio_set_config(gc, gpio_chip_hwgpio(desc), PIN_CONFIG_DRIVE_PUSH_PULL); } set_output_value: + ret = gpio_set_bias(gc, desc); + if (ret) + return ret; return gpiod_direction_output_raw_commit(desc, value); + +set_output_flag: + /* + * When emulating open-source or open-drain functionalities by not + * actively driving the line (setting mode to input) we still need to + * set the IS_OUT flag or otherwise we won't be able to set the line + * value anymore. + */ + if (ret == 0) + set_bit(FLAG_IS_OUT, &desc->flags); + return ret; } EXPORT_SYMBOL_GPL(gpiod_direction_output); @@ -3448,8 +3635,6 @@ static void gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value) if (value) { ret = chip->direction_input(chip, offset); - if (!ret) - clear_bit(FLAG_IS_OUT, &desc->flags); } else { ret = chip->direction_output(chip, offset, 0); if (!ret) @@ -3479,8 +3664,6 @@ static void gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value set_bit(FLAG_IS_OUT, &desc->flags); } else { ret = chip->direction_input(chip, offset); - if (!ret) - clear_bit(FLAG_IS_OUT, &desc->flags); } trace_gpio_direction(desc_to_gpio(desc), !value, ret); if (ret < 0) @@ -4325,6 +4508,54 @@ static int platform_gpio_count(struct device *dev, const char *con_id) } /** + * fwnode_gpiod_get_index - obtain a GPIO from firmware node + * @fwnode: handle of the firmware node + * @con_id: function within the GPIO consumer + * @index: index of the GPIO to obtain for the consumer + * @flags: GPIO initialization flags + * @label: label to attach to the requested GPIO + * + * This function can be used for drivers that get their configuration + * from opaque firmware. + * + * The function properly finds the corresponding GPIO using whatever is the + * underlying firmware interface and then makes sure that the GPIO + * descriptor is requested before it is returned to the caller. + * + * Returns: + * On successful request the GPIO pin is configured in accordance with + * provided @flags. + * + * In case of error an ERR_PTR() is returned. + */ +struct gpio_desc *fwnode_gpiod_get_index(struct fwnode_handle *fwnode, + const char *con_id, int index, + enum gpiod_flags flags, + const char *label) +{ + struct gpio_desc *desc; + char prop_name[32]; /* 32 is max size of property name */ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { + if (con_id) + snprintf(prop_name, sizeof(prop_name), "%s-%s", + con_id, gpio_suffixes[i]); + else + snprintf(prop_name, sizeof(prop_name), "%s", + gpio_suffixes[i]); + + desc = fwnode_get_named_gpiod(fwnode, prop_name, index, flags, + label); + if (!IS_ERR(desc) || (PTR_ERR(desc) != -ENOENT)) + break; + } + + return desc; +} +EXPORT_SYMBOL_GPL(fwnode_gpiod_get_index); + +/** * gpiod_count - return the number of GPIOs associated with a device / function * or -ENOENT if no GPIO has been assigned to the requested function * @dev: GPIO consumer, can be NULL for system-global GPIOs @@ -4663,9 +4894,9 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, pr_info("GPIO line %d (%s) hogged as %s%s\n", desc_to_gpio(desc), name, - (dflags&GPIOD_FLAGS_BIT_DIR_OUT) ? "output" : "input", - (dflags&GPIOD_FLAGS_BIT_DIR_OUT) ? - (dflags&GPIOD_FLAGS_BIT_DIR_VAL) ? "/high" : "/low":""); + (dflags & GPIOD_FLAGS_BIT_DIR_OUT) ? "output" : "input", + (dflags & GPIOD_FLAGS_BIT_DIR_OUT) ? + (dflags & GPIOD_FLAGS_BIT_DIR_VAL) ? "/high" : "/low" : ""); return 0; } |