Re: [PATCH/RFC 2/2] gpio: of: Add DT overlay support for GPIO hogs

From: Frank Rowand
Date: Tue Jan 07 2020 - 02:26:03 EST


On 1/7/20 1:10 AM, Frank Rowand wrote:
> On 1/6/20 5:34 PM, Frank Rowand wrote:
>> On 12/30/19 7:38 AM, Geert Uytterhoeven wrote:
>>> As GPIO hogs are configured at GPIO controller initialization time,
>>> adding/removing GPIO hogs in DT overlays does not work.
>>>
>>> Add support for GPIO hogs described in DT overlays by registering an OF
>>> reconfiguration notifier, to handle the addition and removal of GPIO hog
>>> subnodes to/from a GPIO controller device node.
>>>
>>> Note that when a GPIO hog device node is being removed, its "gpios"
>>> properties is no longer available, so we have to keep track of which
>>> node a hog belongs to, which is done by adding a pointer to the hog's
>>> device node to struct gpio_desc.
>>
>> If I have read the patches and the existing overlay source correctly,
>> then some observations:
>>
>> - A gpio hog node added in an overlay will be properly processed.
>>
>> - A gpio hog node already existing in the live devicetree, but with a
>> non-active status will be properly processed if the status of the
>> gpio hog node is changed to "ok" in the overlay.
>>
>> - If a gpio hog node already exists in the live devicetree with an
>> active status, then any updated or added properties in that gpio
>> hog node in the overlay will have no effect.
>>
>> There is a scenario where the updated property would have an effect:
>> apply a second overlay that sets the status to inactive, then apply
>> a third overlay that sets the status back to active. This is a
>> rather contrived example and I think it should be documented as
>> not supported and the result undefined.
>
> I went back and double checked the related code. For gpio hog nodes
> that are in a non-overlay, the status property is checked because
> of_gpiochip_scan_gpios() uses for_each_available_child_of_node()
> to search for gpio hog nodes, and for_each_available_child_of_node()
> checks the status property. But in the case of a gpio hog node
> added by an overlay, of_gpio_notify() does not check the status
> property in the gpio hog node. The check for the status property
> should be added to of_gpio_notify().

One more thing I have not thought through is the case where the
overlay contains both the gpio node and the gpio hog node. I'll
walk through that tomorrow.

-Frank

>
> -Frank
>
>>
>> It would be good to document this explicitly.
>>
>>
>>>
>>> Signed-off-by: Geert Uytterhoeven <geert+renesas@xxxxxxxxx>
>>> ---
>>> drivers/gpio/gpiolib-of.c | 84 +++++++++++++++++++++++++++++++++++++++
>>> drivers/gpio/gpiolib-of.h | 2 +
>>> drivers/gpio/gpiolib.c | 14 +++++--
>>> drivers/gpio/gpiolib.h | 3 ++
>>> 4 files changed, 100 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
>>> index dfae797846bb746b..89a6138ac0a4b506 100644
>>> --- a/drivers/gpio/gpiolib-of.c
>>> +++ b/drivers/gpio/gpiolib-of.c
>>> @@ -649,6 +649,10 @@ static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog)
>>> ret = gpiod_hog(desc, name, lflags, dflags);
>>> if (ret < 0)
>>> return ret;
>>> +
>>> +#ifdef CONFIG_OF_DYNAMIC
>>> + desc->hog = hog;
>>> +#endif
>>> }
>>>
>>> return 0;
>>> @@ -676,11 +680,91 @@ static int of_gpiochip_scan_gpios(struct gpio_chip *chip)
>>> of_node_put(np);
>>> return ret;
>>> }
>>> +
>>> + of_node_set_flag(np, OF_POPULATED);
>>> }
>>>
>>> return 0;
>>> }
>>>
>>> +#ifdef CONFIG_OF_DYNAMIC
>>> +/**
>>> + * of_gpiochip_remove_hog - Remove all hogs in a hog device node
>>> + * @chip: gpio chip to act on
>>> + * @hog: device node describing the hogs
>>> + */
>>> +static void of_gpiochip_remove_hog(struct gpio_chip *chip,
>>> + struct device_node *hog)
>>> +{
>>> + struct gpio_desc *descs = chip->gpiodev->descs;
>>> + unsigned int i;
>>> +
>>> + for (i = 0; i < chip->ngpio; i++) {
>>> + if (test_bit(FLAG_IS_HOGGED, &descs[i].flags) &&
>>> + descs[i].hog == hog)
>>> + gpiochip_free_own_desc(&descs[i]);
>>> + }
>>> +}
>>> +
>>> +static int of_gpiochip_match_node(struct gpio_chip *chip, void *data)
>>> +{
>>> + return chip->gpiodev->dev.of_node == data;
>>> +}
>>> +
>>> +static struct gpio_chip *of_find_gpiochip_by_node(struct device_node *np)
>>> +{
>>> + return gpiochip_find(np, of_gpiochip_match_node);
>>> +}
>>> +
>>> +static int of_gpio_notify(struct notifier_block *nb, unsigned long action,
>>> + void *arg)
>>> +{
>>> + struct of_reconfig_data *rd = arg;
>>> + struct gpio_chip *chip;
>>> + int ret;
>>> +
>>> + switch (of_reconfig_get_state_change(action, arg)) {
>>> + case OF_RECONFIG_CHANGE_ADD:
>>> + if (!of_property_read_bool(rd->dn, "gpio-hog"))
>>> + return NOTIFY_OK; /* not for us */
>>> +
>>> + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED))
>>> + return NOTIFY_OK;
>>
>> I don't think OF_POPULATED could be already set. It seems to be a
>> bug if it is.
>>
>>
>>> +
>>> + chip = of_find_gpiochip_by_node(rd->dn->parent);
>>> + if (chip == NULL)
>>> + return NOTIFY_OK; /* not for us */
>>
>> If I understand correctly, "not for us" is a misleading comment.
>> The notification is for the node rd->dn->parent, but the device
>> does not exist, so we can't do the hogging at the moment. (If the
>> device is created later, then the gpio hog child node will exist,
>> and the init will "do the right thing" with the hog node -- so
>> not a problem.)
>>
>>
>>> +
>>> + ret = of_gpiochip_add_hog(chip, rd->dn);
>>> + if (ret < 0) {
>>> + pr_err("%s: failed to add hogs for %pOF\n", __func__,
>>> + rd->dn);
>>> + of_node_clear_flag(rd->dn, OF_POPULATED);
>>> + return notifier_from_errno(ret);
>>> + }
>>> + break;
>>> +
>>> + case OF_RECONFIG_CHANGE_REMOVE:
>>> + if (!of_node_check_flag(rd->dn, OF_POPULATED))
>>> + return NOTIFY_OK; /* already depopulated */
>>
>> I don't think OF_POPULATED could be already cleared. It seems to be a
>> bug if it is.
>>
>>> +
>>> + chip = of_find_gpiochip_by_node(rd->dn->parent);
>>> + if (chip == NULL)
>>> + return NOTIFY_OK; /* not for us */
>>
>> Again, a misleading comment.
>>
>>> +
>>> + of_gpiochip_remove_hog(chip, rd->dn);
>>> + of_node_clear_flag(rd->dn, OF_POPULATED);
>>> + break;
>>> + }
>>> +
>>> + return NOTIFY_OK;
>>> +}
>>> +
>>> +struct notifier_block gpio_of_notifier = {
>>> + .notifier_call = of_gpio_notify,
>>> +};
>>> +#endif /* CONFIG_OF_DYNAMIC */
>>> +
>>> /**
>>> * of_gpio_simple_xlate - translate gpiospec to the GPIO number and flags
>>> * @gc: pointer to the gpio_chip structure
>>> diff --git a/drivers/gpio/gpiolib-of.h b/drivers/gpio/gpiolib-of.h
>>> index 9768831b1fe2f25b..ed26664f153782fc 100644
>>> --- a/drivers/gpio/gpiolib-of.h
>>> +++ b/drivers/gpio/gpiolib-of.h
>>> @@ -35,4 +35,6 @@ static inline bool of_gpio_need_valid_mask(const struct gpio_chip *gc)
>>> }
>>> #endif /* CONFIG_OF_GPIO */
>>>
>>> +extern struct notifier_block gpio_of_notifier;
>>> +
>>> #endif /* GPIOLIB_OF_H */
>>> diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
>>> index bff5ac774d870b67..ef12cfcaf0962c1c 100644
>>> --- a/drivers/gpio/gpiolib.c
>>> +++ b/drivers/gpio/gpiolib.c
>>> @@ -2952,6 +2952,9 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
>>> clear_bit(FLAG_PULL_DOWN, &desc->flags);
>>> clear_bit(FLAG_BIAS_DISABLE, &desc->flags);
>>> clear_bit(FLAG_IS_HOGGED, &desc->flags);
>>> +#ifdef CONFIG_OF_DYNAMIC
>>> + desc->hog = NULL;
>>> +#endif
>>> ret = true;
>>> }
>>>
>>> @@ -5145,10 +5148,15 @@ static int __init gpiolib_dev_init(void)
>>> if (ret < 0) {
>>> pr_err("gpiolib: failed to allocate char dev region\n");
>>> bus_unregister(&gpio_bus_type);
>>> - } else {
>>> - gpiolib_initialized = true;
>>> - gpiochip_setup_devs();
>>> + return ret;
>>> }
>>> +
>>> + gpiolib_initialized = true;
>>> + gpiochip_setup_devs();
>>> +
>>> + if (IS_ENABLED(CONFIG_OF_DYNAMIC))
>>> + WARN_ON(of_reconfig_notifier_register(&gpio_of_notifier));
>>> +
>>> return ret;
>>> }
>>> core_initcall(gpiolib_dev_init);
>>> diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
>>> index a4a759920faa48ab..7af9931e8572304a 100644
>>> --- a/drivers/gpio/gpiolib.h
>>> +++ b/drivers/gpio/gpiolib.h
>>> @@ -118,6 +118,9 @@ struct gpio_desc {
>>> const char *label;
>>> /* Name of the GPIO */
>>> const char *name;
>>> +#ifdef CONFIG_OF_DYNAMIC
>>> + struct device_node *hog;
>>> +#endif
>>> };
>>>
>>> int gpiod_request(struct gpio_desc *desc, const char *label);
>>>
>>
>>
>
>