mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 13:41:48 -04:00
gpio: shared: call gpio_chip::of_xlate() if set
OF-based GPIO controller drivers may provide a translation function that
calculates the real chip offset from whatever devicetree sources
provide. We need to take this into account in the shared GPIO management
and call of_xlate() if it's provided and adjust the entry->offset we
initially set when scanning the tree.
To that end: modify the shared GPIO API to take the GPIO chip as
argument on setup (to avoid having to rcu_dereference() it from the GPIO
device) and protect the access to entry->offset with the existing lock.
Fixes: a060b8c511 ("gpiolib: implement low-level, shared GPIO support")
Reported-by: Jon Hunter <jonathanh@nvidia.com>
Closes: https://lore.kernel.org/all/921ba8ce-b18e-4a99-966d-c763d22081e2@nvidia.com/
Reviewed-by: Linus Walleij <linusw@kernel.org>
Tested-by: Jon Hunter <jonathanh@nvidia.com>
Acked-by: Jon Hunter <jonathanh@nvidia.com>
Link: https://patch.msgid.link/20260318-gpio-shared-xlate-v2-1-0ce34c707e81@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
This commit is contained in:
@@ -506,8 +506,9 @@ static void gpio_shared_remove_adev(struct auxiliary_device *adev)
|
|||||||
auxiliary_device_uninit(adev);
|
auxiliary_device_uninit(adev);
|
||||||
}
|
}
|
||||||
|
|
||||||
int gpio_device_setup_shared(struct gpio_device *gdev)
|
int gpiochip_setup_shared(struct gpio_chip *gc)
|
||||||
{
|
{
|
||||||
|
struct gpio_device *gdev = gc->gpiodev;
|
||||||
struct gpio_shared_entry *entry;
|
struct gpio_shared_entry *entry;
|
||||||
struct gpio_shared_ref *ref;
|
struct gpio_shared_ref *ref;
|
||||||
struct gpio_desc *desc;
|
struct gpio_desc *desc;
|
||||||
@@ -532,12 +533,34 @@ int gpio_device_setup_shared(struct gpio_device *gdev)
|
|||||||
* exposing shared pins. Find them and create the proxy devices.
|
* exposing shared pins. Find them and create the proxy devices.
|
||||||
*/
|
*/
|
||||||
list_for_each_entry(entry, &gpio_shared_list, list) {
|
list_for_each_entry(entry, &gpio_shared_list, list) {
|
||||||
|
guard(mutex)(&entry->lock);
|
||||||
|
|
||||||
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
|
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (list_count_nodes(&entry->refs) <= 1)
|
if (list_count_nodes(&entry->refs) <= 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_OF)
|
||||||
|
if (is_of_node(entry->fwnode) && gc->of_xlate) {
|
||||||
|
/*
|
||||||
|
* This is the earliest that we can tranlate the
|
||||||
|
* devicetree offset to the chip offset.
|
||||||
|
*/
|
||||||
|
struct of_phandle_args gpiospec = { };
|
||||||
|
|
||||||
|
gpiospec.np = to_of_node(entry->fwnode);
|
||||||
|
gpiospec.args_count = 2;
|
||||||
|
gpiospec.args[0] = entry->offset;
|
||||||
|
|
||||||
|
ret = gc->of_xlate(gc, &gpiospec, NULL);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
entry->offset = ret;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_OF */
|
||||||
|
|
||||||
desc = &gdev->descs[entry->offset];
|
desc = &gdev->descs[entry->offset];
|
||||||
|
|
||||||
__set_bit(GPIOD_FLAG_SHARED, &desc->flags);
|
__set_bit(GPIOD_FLAG_SHARED, &desc->flags);
|
||||||
@@ -575,6 +598,8 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
|
|||||||
struct gpio_shared_ref *ref;
|
struct gpio_shared_ref *ref;
|
||||||
|
|
||||||
list_for_each_entry(entry, &gpio_shared_list, list) {
|
list_for_each_entry(entry, &gpio_shared_list, list) {
|
||||||
|
guard(mutex)(&entry->lock);
|
||||||
|
|
||||||
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
|
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ struct device;
|
|||||||
|
|
||||||
#if IS_ENABLED(CONFIG_GPIO_SHARED)
|
#if IS_ENABLED(CONFIG_GPIO_SHARED)
|
||||||
|
|
||||||
int gpio_device_setup_shared(struct gpio_device *gdev);
|
int gpiochip_setup_shared(struct gpio_chip *gc);
|
||||||
void gpio_device_teardown_shared(struct gpio_device *gdev);
|
void gpio_device_teardown_shared(struct gpio_device *gdev);
|
||||||
int gpio_shared_add_proxy_lookup(struct device *consumer, const char *con_id,
|
int gpio_shared_add_proxy_lookup(struct device *consumer, const char *con_id,
|
||||||
unsigned long lflags);
|
unsigned long lflags);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline int gpio_device_setup_shared(struct gpio_device *gdev)
|
static inline int gpiochip_setup_shared(struct gpio_chip *gc)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1211,7 +1211,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto err_remove_irqchip_mask;
|
goto err_remove_irqchip_mask;
|
||||||
|
|
||||||
ret = gpio_device_setup_shared(gdev);
|
ret = gpiochip_setup_shared(gc);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_remove_irqchip;
|
goto err_remove_irqchip;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user