From fc84abc4a9b25da0559622c41045212175d6732f Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Mon, 13 Nov 2023 18:45:58 +0530 Subject: [PATCH 01/55] gpio: tangier: use EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS() helper Use EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS() helper to export pm_ops to GPIO_TANGIER namespace, so that they can be reused. Signed-off-by: Raag Jadav Link: https://lore.kernel.org/r/20231113131600.10828-2-raag.jadav@intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-tangier.c | 3 +++ drivers/gpio/gpio-tangier.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/gpio/gpio-tangier.c b/drivers/gpio/gpio-tangier.c index 7ce3eddaed25..52d0868476e1 100644 --- a/drivers/gpio/gpio-tangier.c +++ b/drivers/gpio/gpio-tangier.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -531,6 +532,8 @@ int tng_gpio_resume(struct device *dev) } EXPORT_SYMBOL_NS_GPL(tng_gpio_resume, GPIO_TANGIER); +EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS(tng_gpio_pm_ops, tng_gpio_suspend, tng_gpio_resume, GPIO_TANGIER); + MODULE_AUTHOR("Andy Shevchenko "); MODULE_AUTHOR("Pandith N "); MODULE_AUTHOR("Raag Jadav "); diff --git a/drivers/gpio/gpio-tangier.h b/drivers/gpio/gpio-tangier.h index 16c4f22908fb..333e5f79b908 100644 --- a/drivers/gpio/gpio-tangier.h +++ b/drivers/gpio/gpio-tangier.h @@ -13,6 +13,7 @@ #define _GPIO_TANGIER_H_ #include +#include #include #include @@ -114,4 +115,6 @@ int devm_tng_gpio_probe(struct device *dev, struct tng_gpio *gpio); int tng_gpio_suspend(struct device *dev); int tng_gpio_resume(struct device *dev); +extern const struct dev_pm_ops tng_gpio_pm_ops; + #endif /* _GPIO_TANGIER_H_ */ From 49d478b41268bb2419e44d876bf66bacc55135f5 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Mon, 13 Nov 2023 18:45:59 +0530 Subject: [PATCH 02/55] gpio: elkhartlake: reuse pm_ops from Intel Tangier driver Reuse tng_gpio_pm_ops from Intel Tangier driver instead of calling them through a local copy. Signed-off-by: Raag Jadav Link: https://lore.kernel.org/r/20231113131600.10828-3-raag.jadav@intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-elkhartlake.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-elkhartlake.c b/drivers/gpio/gpio-elkhartlake.c index a9c8b16215be..887c0fe99d39 100644 --- a/drivers/gpio/gpio-elkhartlake.c +++ b/drivers/gpio/gpio-elkhartlake.c @@ -55,18 +55,6 @@ static int ehl_gpio_probe(struct platform_device *pdev) return 0; } -static int ehl_gpio_suspend(struct device *dev) -{ - return tng_gpio_suspend(dev); -} - -static int ehl_gpio_resume(struct device *dev) -{ - return tng_gpio_resume(dev); -} - -static DEFINE_SIMPLE_DEV_PM_OPS(ehl_gpio_pm_ops, ehl_gpio_suspend, ehl_gpio_resume); - static const struct platform_device_id ehl_gpio_ids[] = { { "gpio-elkhartlake" }, { } @@ -76,7 +64,7 @@ MODULE_DEVICE_TABLE(platform, ehl_gpio_ids); static struct platform_driver ehl_gpio_driver = { .driver = { .name = "gpio-elkhartlake", - .pm = pm_sleep_ptr(&ehl_gpio_pm_ops), + .pm = pm_sleep_ptr(&tng_gpio_pm_ops), }, .probe = ehl_gpio_probe, .id_table = ehl_gpio_ids, From c4a79ae280a632786d6f9c7856ef27ebaa585ea7 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Mon, 13 Nov 2023 18:46:00 +0530 Subject: [PATCH 03/55] gpio: tangier: unexport suspend/resume handles Unexport suspend/resume handles for the lack of external users and while at it, make them static, so that they can be discarded by the compiler if not used (CONFIG_PM_SLEEP=n). Signed-off-by: Raag Jadav Link: https://lore.kernel.org/r/20231113131600.10828-4-raag.jadav@intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-tangier.c | 6 ++---- drivers/gpio/gpio-tangier.h | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpio-tangier.c b/drivers/gpio/gpio-tangier.c index 52d0868476e1..158fa9054e9c 100644 --- a/drivers/gpio/gpio-tangier.c +++ b/drivers/gpio/gpio-tangier.c @@ -478,7 +478,7 @@ int devm_tng_gpio_probe(struct device *dev, struct tng_gpio *gpio) } EXPORT_SYMBOL_NS_GPL(devm_tng_gpio_probe, GPIO_TANGIER); -int tng_gpio_suspend(struct device *dev) +static int tng_gpio_suspend(struct device *dev) { struct tng_gpio *priv = dev_get_drvdata(dev); struct tng_gpio_context *ctx = priv->ctx; @@ -503,9 +503,8 @@ int tng_gpio_suspend(struct device *dev) return 0; } -EXPORT_SYMBOL_NS_GPL(tng_gpio_suspend, GPIO_TANGIER); -int tng_gpio_resume(struct device *dev) +static int tng_gpio_resume(struct device *dev) { struct tng_gpio *priv = dev_get_drvdata(dev); struct tng_gpio_context *ctx = priv->ctx; @@ -530,7 +529,6 @@ int tng_gpio_resume(struct device *dev) return 0; } -EXPORT_SYMBOL_NS_GPL(tng_gpio_resume, GPIO_TANGIER); EXPORT_NS_GPL_SIMPLE_DEV_PM_OPS(tng_gpio_pm_ops, tng_gpio_suspend, tng_gpio_resume, GPIO_TANGIER); diff --git a/drivers/gpio/gpio-tangier.h b/drivers/gpio/gpio-tangier.h index 333e5f79b908..ca7ab6cf6fa5 100644 --- a/drivers/gpio/gpio-tangier.h +++ b/drivers/gpio/gpio-tangier.h @@ -112,9 +112,6 @@ struct tng_gpio { int devm_tng_gpio_probe(struct device *dev, struct tng_gpio *gpio); -int tng_gpio_suspend(struct device *dev); -int tng_gpio_resume(struct device *dev); - extern const struct dev_pm_ops tng_gpio_pm_ops; #endif /* _GPIO_TANGIER_H_ */ From 66d6143ebff038a69d875e582e536bae02b14290 Mon Sep 17 00:00:00 2001 From: Andrei Coardos Date: Sun, 12 Nov 2023 18:34:32 -0800 Subject: [PATCH 04/55] gpio: sifive: remove unneeded call to platform_set_drvdata() This function call was found to be unnecessary as there is no equivalent platform_get_drvdata() call to access the private data of the driver. Also, the private data is defined in this driver, so there is no risk of it being accessed outside of this driver file. Reviewed-by: Alexandru Ardelean Signed-off-by: Andrei Coardos Reviewed-by: Andy Shevchenko Signed-off-by: Samuel Holland Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sifive.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c index 8decd9b5d229..067c8edb62e2 100644 --- a/drivers/gpio/gpio-sifive.c +++ b/drivers/gpio/gpio-sifive.c @@ -250,7 +250,6 @@ static int sifive_gpio_probe(struct platform_device *pdev) girq->handler = handle_bad_irq; girq->default_type = IRQ_TYPE_NONE; - platform_set_drvdata(pdev, chip); return gpiochip_add_data(&chip->gc, chip); } From 92fc925f838660eec25862a7fa7e6ef79d22f3ea Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Sat, 18 Nov 2023 12:50:37 +0530 Subject: [PATCH 05/55] gpio: tangier: simplify locking using cleanup helpers Use lock guards from cleanup.h to simplify locking. Signed-off-by: Raag Jadav Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-tangier.c | 54 ++++++++++--------------------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/drivers/gpio/gpio-tangier.c b/drivers/gpio/gpio-tangier.c index 158fa9054e9c..b75e0b12087a 100644 --- a/drivers/gpio/gpio-tangier.c +++ b/drivers/gpio/gpio-tangier.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -92,37 +93,31 @@ static int tng_gpio_get(struct gpio_chip *chip, unsigned int offset) static void tng_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct tng_gpio *priv = gpiochip_get_data(chip); - unsigned long flags; void __iomem *reg; u8 shift; reg = gpio_reg_and_bit(chip, offset, value ? GPSR : GPCR, &shift); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); writel(BIT(shift), reg); - - raw_spin_unlock_irqrestore(&priv->lock, flags); } static int tng_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) { struct tng_gpio *priv = gpiochip_get_data(chip); - unsigned long flags; void __iomem *gpdr; u32 value; u8 shift; gpdr = gpio_reg_and_bit(chip, offset, GPDR, &shift); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); value = readl(gpdr); value &= ~BIT(shift); writel(value, gpdr); - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -130,21 +125,18 @@ static int tng_gpio_direction_output(struct gpio_chip *chip, unsigned int offset int value) { struct tng_gpio *priv = gpiochip_get_data(chip); - unsigned long flags; void __iomem *gpdr; u8 shift; gpdr = gpio_reg_and_bit(chip, offset, GPDR, &shift); tng_gpio_set(chip, offset, value); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); value = readl(gpdr); value |= BIT(shift); writel(value, gpdr); - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -165,14 +157,13 @@ static int tng_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset, unsigned int debounce) { struct tng_gpio *priv = gpiochip_get_data(chip); - unsigned long flags; void __iomem *gfbr; u32 value; u8 shift; gfbr = gpio_reg_and_bit(chip, offset, GFBR, &shift); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); value = readl(gfbr); if (debounce) @@ -181,8 +172,6 @@ static int tng_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset, value |= BIT(shift); writel(value, gfbr); - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -208,27 +197,25 @@ static void tng_irq_ack(struct irq_data *d) { struct tng_gpio *priv = irq_data_get_irq_chip_data(d); irq_hw_number_t gpio = irqd_to_hwirq(d); - unsigned long flags; void __iomem *gisr; u8 shift; gisr = gpio_reg_and_bit(&priv->chip, gpio, GISR, &shift); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); + writel(BIT(shift), gisr); - raw_spin_unlock_irqrestore(&priv->lock, flags); } static void tng_irq_unmask_mask(struct tng_gpio *priv, u32 gpio, bool unmask) { - unsigned long flags; void __iomem *gimr; u32 value; u8 shift; gimr = gpio_reg_and_bit(&priv->chip, gpio, GIMR, &shift); - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); value = readl(gimr); if (unmask) @@ -236,8 +223,6 @@ static void tng_irq_unmask_mask(struct tng_gpio *priv, u32 gpio, bool unmask) else value &= ~BIT(shift); writel(value, gimr); - - raw_spin_unlock_irqrestore(&priv->lock, flags); } static void tng_irq_mask(struct irq_data *d) @@ -268,10 +253,9 @@ static int tng_irq_set_type(struct irq_data *d, unsigned int type) void __iomem *gitr = gpio_reg(&priv->chip, gpio, GITR); void __iomem *glpr = gpio_reg(&priv->chip, gpio, GLPR); u8 shift = gpio % 32; - unsigned long flags; u32 value; - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); value = readl(grer); if (type & IRQ_TYPE_EDGE_RISING) @@ -312,8 +296,6 @@ static int tng_irq_set_type(struct irq_data *d, unsigned int type) irq_set_handler_locked(d, handle_edge_irq); } - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -325,10 +307,11 @@ static int tng_irq_set_wake(struct irq_data *d, unsigned int on) void __iomem *gwmr = gpio_reg(&priv->chip, gpio, priv->wake_regs.gwmr); void __iomem *gwsr = gpio_reg(&priv->chip, gpio, priv->wake_regs.gwsr); u8 shift = gpio % 32; - unsigned long flags; u32 value; - raw_spin_lock_irqsave(&priv->lock, flags); + dev_dbg(priv->dev, "%s wake for gpio %lu\n", str_enable_disable(on), gpio); + + guard(raw_spinlock_irqsave)(&priv->lock); /* Clear the existing wake status */ writel(BIT(shift), gwsr); @@ -340,9 +323,6 @@ static int tng_irq_set_wake(struct irq_data *d, unsigned int on) value &= ~BIT(shift); writel(value, gwmr); - raw_spin_unlock_irqrestore(&priv->lock, flags); - - dev_dbg(priv->dev, "%s wake for gpio %lu\n", str_enable_disable(on), gpio); return 0; } @@ -482,10 +462,9 @@ static int tng_gpio_suspend(struct device *dev) { struct tng_gpio *priv = dev_get_drvdata(dev); struct tng_gpio_context *ctx = priv->ctx; - unsigned long flags; unsigned int base; - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); for (base = 0; base < priv->chip.ngpio; base += 32, ctx++) { /* GPLR is RO, values read will be restored using GPSR */ @@ -499,8 +478,6 @@ static int tng_gpio_suspend(struct device *dev) ctx->gwmr = readl(gpio_reg(&priv->chip, base, priv->wake_regs.gwmr)); } - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -508,10 +485,9 @@ static int tng_gpio_resume(struct device *dev) { struct tng_gpio *priv = dev_get_drvdata(dev); struct tng_gpio_context *ctx = priv->ctx; - unsigned long flags; unsigned int base; - raw_spin_lock_irqsave(&priv->lock, flags); + guard(raw_spinlock_irqsave)(&priv->lock); for (base = 0; base < priv->chip.ngpio; base += 32, ctx++) { /* GPLR is RO, values read will be restored using GPSR */ @@ -525,8 +501,6 @@ static int tng_gpio_resume(struct device *dev) writel(ctx->gwmr, gpio_reg(&priv->chip, base, priv->wake_regs.gwmr)); } - raw_spin_unlock_irqrestore(&priv->lock, flags); - return 0; } From d652049e118ffe9227155f7b9b85faf15fc91f8f Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 15 Nov 2023 17:29:01 +0100 Subject: [PATCH 06/55] gpio: mockup: initialize a managed pointer in place The preferred pattern for autopointers is to initialize them when they're declared unless it doesn't make sense. Move the declaration of the managed device pointer to where it's initialized. Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mockup.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mockup.c b/drivers/gpio/gpio-mockup.c index 4870e267a402..455eecf6380e 100644 --- a/drivers/gpio/gpio-mockup.c +++ b/drivers/gpio/gpio-mockup.c @@ -354,7 +354,6 @@ static const struct file_operations gpio_mockup_debugfs_ops = { static void gpio_mockup_debugfs_setup(struct device *dev, struct gpio_mockup_chip *chip) { - struct device *child __free(put_device) = NULL; struct gpio_mockup_dbgfs_private *priv; struct gpio_chip *gc; const char *devname; @@ -367,7 +366,7 @@ static void gpio_mockup_debugfs_setup(struct device *dev, * There can only be a single GPIO device per platform device in * gpio-mockup so using device_find_any_child() is OK. */ - child = device_find_any_child(dev); + struct device *child __free(put_device) = device_find_any_child(dev); if (!child) return; From d1f7728259ef02ac20b7afb6e7eb5a9eb1696c25 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 15 Nov 2023 17:49:59 +0100 Subject: [PATCH 07/55] gpiolib: provide gpio_device_get_label() Provide a getter for the GPIO device label string so that users don't have to dereference struct gpio_chip directly. Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 14 ++++++++++++++ include/linux/gpio/driver.h | 1 + 2 files changed, 15 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 95d2a7b2ea3e..a5faaea6915d 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -254,6 +254,20 @@ int gpio_device_get_base(struct gpio_device *gdev) } EXPORT_SYMBOL_GPL(gpio_device_get_base); +/** + * gpio_device_get_label() - Get the label of this GPIO device + * @gdev: GPIO device + * + * Returns: + * Pointer to the string containing the GPIO device label. The string's + * lifetime is tied to that of the underlying GPIO device. + */ +const char *gpio_device_get_label(struct gpio_device *gdev) +{ + return gdev->label; +} +EXPORT_SYMBOL(gpio_device_get_label); + /** * gpio_device_get_chip() - Get the gpio_chip implementation of this GPIO device * @gdev: GPIO device diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 0aed62f0c633..100c329dc986 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -786,6 +786,7 @@ struct gpio_device *gpiod_to_gpio_device(struct gpio_desc *desc); /* struct gpio_device getters */ int gpio_device_get_base(struct gpio_device *gdev); +const char *gpio_device_get_label(struct gpio_device *gdev); #else /* CONFIG_GPIOLIB */ From e6d71c7878bc6140d1b0b527116af8fd8376d3d5 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Wed, 22 Nov 2023 15:44:24 -0700 Subject: [PATCH 08/55] dt-bindings: gpio: brcmstb: drop unneeded quotes Drop unneeded quotes over simple string values to fix a soon to be enabled yamllint warning: [error] string value is redundantly quoted with any quotes (quoted-strings) Signed-off-by: Rob Herring Acked-by: Florian Fainelli Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.yaml b/Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.yaml index 4a896ff7edc5..a1e71c974e79 100644 --- a/Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/brcm,brcmstb-gpio.yaml @@ -72,7 +72,7 @@ required: - reg - gpio-controller - "#gpio-cells" - - "brcm,gpio-bank-widths" + - brcm,gpio-bank-widths additionalProperties: false From 58bfaaac03284d82a55a724bfe2d97e19f3a11d0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 27 Nov 2023 20:37:15 +0100 Subject: [PATCH 09/55] gpio: sysfs: fix forward declaration of struct gpio_device The forward declaration for struct gpio_device should be provided for both branches of the #ifdef. Fixes: 08a149c40bdb ("gpiolib: Clean up headers") Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko --- drivers/gpio/gpiolib-sysfs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.h b/drivers/gpio/gpiolib-sysfs.h index 0f213bdb4732..b794b396d6a5 100644 --- a/drivers/gpio/gpiolib-sysfs.h +++ b/drivers/gpio/gpiolib-sysfs.h @@ -3,10 +3,10 @@ #ifndef GPIOLIB_SYSFS_H #define GPIOLIB_SYSFS_H -#ifdef CONFIG_GPIO_SYSFS - struct gpio_device; +#ifdef CONFIG_GPIO_SYSFS + int gpiochip_sysfs_register(struct gpio_device *gdev); void gpiochip_sysfs_unregister(struct gpio_device *gdev); From 9a9429b9ce975bcc97b45824bdd153bee5bad46c Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 29 Nov 2023 23:37:41 +0100 Subject: [PATCH 10/55] gpio: ixp4xx: Handle clock output on pin 14 and 15 This makes it possible to provide basic clock output on pins 14 and 15. The clocks are typically used by random electronics, not modeled in the device tree, so they just need to be provided on request. In order to not disturb old systems that require that the hardware defaults are kept in the clock setting bits, we only manipulate these if either device tree property is present. Once we know a device needs one of the clocks we can set it in the device tree. Reviewed-by: Andy Shevchenko Signed-off-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ixp4xx.c | 51 +++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-ixp4xx.c b/drivers/gpio/gpio-ixp4xx.c index dde6cf3a5779..c5a9fa640566 100644 --- a/drivers/gpio/gpio-ixp4xx.c +++ b/drivers/gpio/gpio-ixp4xx.c @@ -38,6 +38,18 @@ #define IXP4XX_GPIO_STYLE_MASK GENMASK(2, 0) #define IXP4XX_GPIO_STYLE_SIZE 3 +/* + * Clock output control register defines. + */ +#define IXP4XX_GPCLK_CLK0DC_SHIFT 0 +#define IXP4XX_GPCLK_CLK0TC_SHIFT 4 +#define IXP4XX_GPCLK_CLK0_MASK GENMASK(7, 0) +#define IXP4XX_GPCLK_MUX14 BIT(8) +#define IXP4XX_GPCLK_CLK1DC_SHIFT 16 +#define IXP4XX_GPCLK_CLK1TC_SHIFT 20 +#define IXP4XX_GPCLK_CLK1_MASK GENMASK(23, 16) +#define IXP4XX_GPCLK_MUX15 BIT(24) + /** * struct ixp4xx_gpio - IXP4 GPIO state container * @dev: containing device for this instance @@ -202,6 +214,8 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev) struct ixp4xx_gpio *g; struct gpio_irq_chip *girq; struct device_node *irq_parent; + bool clk_14, clk_15; + u32 val; int ret; g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL); @@ -225,13 +239,48 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev) } g->fwnode = of_node_to_fwnode(np); + /* + * If either clock output is enabled explicitly in the device tree + * we take full control of the clock by masking off all bits for + * the clock control and selectively enabling them. Otherwise + * we leave the hardware default settings. + * + * Enable clock outputs with default timings of requested clock. + * If you need control over TC and DC, add these to the device + * tree bindings and use them here. + */ + clk_14 = of_property_read_bool(np, "intel,ixp4xx-gpio14-clkout"); + clk_15 = of_property_read_bool(np, "intel,ixp4xx-gpio15-clkout"); + /* * Make sure GPIO 14 and 15 are NOT used as clocks but GPIO on * specific machines. */ if (of_machine_is_compatible("dlink,dsm-g600-a") || of_machine_is_compatible("iom,nas-100d")) - __raw_writel(0x0, g->base + IXP4XX_REG_GPCLK); + val = 0; + else { + val = __raw_readl(g->base + IXP4XX_REG_GPCLK); + + if (clk_14 || clk_15) { + val &= ~(IXP4XX_GPCLK_MUX14 | IXP4XX_GPCLK_MUX15); + val &= ~IXP4XX_GPCLK_CLK0_MASK; + val &= ~IXP4XX_GPCLK_CLK1_MASK; + if (clk_14) { + /* IXP4XX_GPCLK_CLK0DC implicit low */ + val |= (1 << IXP4XX_GPCLK_CLK0TC_SHIFT); + val |= IXP4XX_GPCLK_MUX14; + } + + if (clk_15) { + /* IXP4XX_GPCLK_CLK1DC implicit low */ + val |= (1 << IXP4XX_GPCLK_CLK1TC_SHIFT); + val |= IXP4XX_GPCLK_MUX15; + } + } + } + + __raw_writel(val, g->base + IXP4XX_REG_GPCLK); /* * This is a very special big-endian ARM issue: when the IXP4xx is From 3fde49c5dd8b1a65c44e2d6861899ccfa9c11197 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 30 Nov 2023 10:15:38 +0100 Subject: [PATCH 11/55] dt-bindings: gpio: modepin: Describe label property Describe optional label property which can be used for better gpio identification. Signed-off-by: Michal Simek Acked-by: Krzysztof Kozlowski Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/xlnx,zynqmp-gpio-modepin.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/xlnx,zynqmp-gpio-modepin.yaml b/Documentation/devicetree/bindings/gpio/xlnx,zynqmp-gpio-modepin.yaml index 56143f1fe84a..b1fd632718d4 100644 --- a/Documentation/devicetree/bindings/gpio/xlnx,zynqmp-gpio-modepin.yaml +++ b/Documentation/devicetree/bindings/gpio/xlnx,zynqmp-gpio-modepin.yaml @@ -23,6 +23,8 @@ properties: "#gpio-cells": const: 2 + label: true + required: - compatible - gpio-controller @@ -37,6 +39,7 @@ examples: compatible = "xlnx,zynqmp-gpio-modepin"; gpio-controller; #gpio-cells = <2>; + label = "modepin"; }; }; From 52816298bd2a1e8c6fb3d9311730c00ef03c1f03 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Fri, 1 Dec 2023 15:08:40 +0100 Subject: [PATCH 12/55] dt-bindings: gpio: rockchip: add a pattern for gpio hogs Allow validating gpio-hogs defined inside the gpio controller node. Signed-off-by: Heiko Stuebner Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/rockchip,gpio-bank.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml b/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml index affd823c881d..d76987ce8e50 100644 --- a/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml +++ b/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml @@ -41,6 +41,13 @@ properties: "#interrupt-cells": const: 2 +patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + + required: + - gpio-hog + required: - compatible - reg From 6c826061c5ee42fe547d1ac4792b30125fa8cb13 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 5 Dec 2023 19:05:23 -0800 Subject: [PATCH 13/55] gpio: max730x: don't use kernel-doc marker for regular comment Use a common C comment (/*) instead of kernel-doc notation to prevent warnings from scripts/kernel-doc. gpio-max730x.c:3: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst gpio-max730x.c:3: warning: missing initial short description on line: * Copyright (C) 2006 Juergen Beisert, Pengutronix Signed-off-by: Randy Dunlap Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max730x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-max730x.c b/drivers/gpio/gpio-max730x.c index bb5cf14ae4c8..701795b9d329 100644 --- a/drivers/gpio/gpio-max730x.c +++ b/drivers/gpio/gpio-max730x.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * Copyright (C) 2006 Juergen Beisert, Pengutronix * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix * Copyright (C) 2009 Wolfram Sang, Pengutronix From c598dc3bc41ed079408633d7d66eefa440e15a12 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 5 Dec 2023 16:54:30 +0100 Subject: [PATCH 14/55] gpio: sim: fix the email address in MODULE_AUTHOR() Fix unterminated angle brackets in the email address in MODULE_AUTHOR(). Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 1928209491e1..1e8b92e9df80 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -1546,6 +1546,6 @@ static void __exit gpio_sim_exit(void) } module_exit(gpio_sim_exit); -MODULE_AUTHOR("Bartosz Golaszewski "); MODULE_DESCRIPTION("GPIO Simulator Module"); MODULE_LICENSE("GPL"); From ee25fba76acd8324f9de6628872c8c612a684209 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:00 +0100 Subject: [PATCH 15/55] gpiolib: provide gpiochip_dup_line_label() gpiochip_is_requested() not only has a misleading name but it returns a pointer to a string that is freed when the descriptor is released. Provide a new helper meant to replace it, which returns a copy of the label string instead. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/gpio/gpiolib.c | 29 +++++++++++++++++++++++++++++ include/linux/gpio/driver.h | 1 + 2 files changed, 30 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 95d2a7b2ea3e..0147c900afea 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2386,6 +2386,35 @@ const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset) } EXPORT_SYMBOL_GPL(gpiochip_is_requested); +/** + * gpiochip_dup_line_label - Get a copy of the consumer label. + * @gc: GPIO chip controlling this line. + * @offset: Hardware offset of the line. + * + * Returns: + * Pointer to a copy of the consumer label if the line is requested or NULL + * if it's not. If a valid pointer was returned, it must be freed using + * kfree(). In case of a memory allocation error, the function returns %ENOMEM. + * + * Must not be called from atomic context. + */ +char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset) +{ + const char *label; + char *copy; + + label = gpiochip_is_requested(gc, offset); + if (!label) + return NULL; + + copy = kstrdup(label, GFP_KERNEL); + if (!copy) + return ERR_PTR(-ENOMEM); + + return copy; +} +EXPORT_SYMBOL_GPL(gpiochip_dup_line_label); + /** * gpiochip_request_own_desc - Allow GPIO chip to request its own descriptor * @gc: GPIO chip diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 0aed62f0c633..5ac6dc30c547 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -532,6 +532,7 @@ struct gpio_chip { }; const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset); +char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset); /** * for_each_requested_gpio_in_range - iterates over requested GPIOs in a given range From 1610cd5f7468d4d8b54a96b82109c6ae66ee24eb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:01 +0100 Subject: [PATCH 16/55] gpio: wm831x: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/gpio/gpio-wm831x.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-wm831x.c b/drivers/gpio/gpio-wm831x.c index 7eaf8a28638c..f7d5120ff8f1 100644 --- a/drivers/gpio/gpio-wm831x.c +++ b/drivers/gpio/gpio-wm831x.c @@ -8,6 +8,7 @@ * */ +#include #include #include #include @@ -160,18 +161,21 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) for (i = 0; i < chip->ngpio; i++) { int gpio = i + chip->base; int reg; - const char *label, *pull, *powerdomain; + const char *pull, *powerdomain; /* We report the GPIO even if it's not requested since * we're also reporting things like alternate * functions which apply even when the GPIO is not in * use as a GPIO. */ - label = gpiochip_is_requested(chip, i); - if (!label) - label = "Unrequested"; + char *label __free(kfree) = gpiochip_dup_line_label(chip, i); + if (IS_ERR(label)) { + dev_err(wm831x->dev, "Failed to duplicate label\n"); + continue; + } - seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + seq_printf(s, " gpio-%-3d (%-20.20s) ", + gpio, label ?: "Unrequested"); reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i); if (reg < 0) { From abeec1ad51da0aa7cd33005f537e54c2422d1218 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:02 +0100 Subject: [PATCH 17/55] gpio: wm8994: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/gpio/gpio-wm8994.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-wm8994.c b/drivers/gpio/gpio-wm8994.c index f4a474cef32d..bf05c9b5882b 100644 --- a/drivers/gpio/gpio-wm8994.c +++ b/drivers/gpio/gpio-wm8994.c @@ -8,6 +8,7 @@ * */ +#include #include #include #include @@ -193,18 +194,20 @@ static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) for (i = 0; i < chip->ngpio; i++) { int gpio = i + chip->base; int reg; - const char *label; /* We report the GPIO even if it's not requested since * we're also reporting things like alternate * functions which apply even when the GPIO is not in * use as a GPIO. */ - label = gpiochip_is_requested(chip, i); - if (!label) - label = "Unrequested"; + char *label __free(kfree) = gpiochip_dup_line_label(chip, i); + if (IS_ERR(label)) { + dev_err(wm8994->dev, "Failed to duplicate label\n"); + continue; + } - seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, + label ?: "Unrequested"); reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i); if (reg < 0) { From f1b33ce48ae77a04021ea0a5dab09f67315cad44 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:03 +0100 Subject: [PATCH 18/55] gpio: stmpe: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/gpio/gpio-stmpe.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 27cc4da53565..6c5ee81d71b3 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -5,6 +5,7 @@ * Author: Rabin Vincent for ST-Ericsson */ +#include #include #include #include @@ -255,7 +256,6 @@ static void stmpe_dbg_show_one(struct seq_file *s, { struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc); struct stmpe *stmpe = stmpe_gpio->stmpe; - const char *label = gpiochip_is_requested(gc, offset); bool val = !!stmpe_gpio_get(gc, offset); u8 bank = offset / 8; u8 dir_reg = stmpe->regs[STMPE_IDX_GPDR_LSB + bank]; @@ -263,6 +263,10 @@ static void stmpe_dbg_show_one(struct seq_file *s, int ret; u8 dir; + char *label __free(kfree) = gpiochip_dup_line_label(gc, offset); + if (IS_ERR(label)) + return; + ret = stmpe_reg_read(stmpe, dir_reg); if (ret < 0) return; From c76ba937f5ff9b4ee670adbd421be2404745be1f Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:04 +0100 Subject: [PATCH 19/55] pinctrl: abx500: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/pinctrl/nomadik/pinctrl-abx500.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/nomadik/pinctrl-abx500.c b/drivers/pinctrl/nomadik/pinctrl-abx500.c index d3c32d809bac..80e3ac333136 100644 --- a/drivers/pinctrl/nomadik/pinctrl-abx500.c +++ b/drivers/pinctrl/nomadik/pinctrl-abx500.c @@ -6,7 +6,9 @@ * * Driver allows to use AxB5xx unused pins to be used as GPIO */ + #include +#include #include #include #include @@ -453,12 +455,11 @@ static void abx500_gpio_dbg_show_one(struct seq_file *s, unsigned offset, unsigned gpio) { struct abx500_pinctrl *pct = pinctrl_dev_get_drvdata(pctldev); - const char *label = gpiochip_is_requested(chip, offset - 1); u8 gpio_offset = offset - 1; int mode = -1; bool is_out; bool pd; - int ret; + int ret = -ENOMEM; const char *modes[] = { [ABX500_DEFAULT] = "default", @@ -474,6 +475,10 @@ static void abx500_gpio_dbg_show_one(struct seq_file *s, [ABX500_GPIO_PULL_UP] = "pull up", }; + char *label __free(kfree) = gpiochip_dup_line_label(chip, offset - 1); + if (IS_ERR(label)) + goto out; + ret = abx500_gpio_get_bit(chip, AB8500_GPIO_DIR1_REG, gpio_offset, &is_out); if (ret < 0) From caf7e135c297e6fceec0a1476da775aa458b5324 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:05 +0100 Subject: [PATCH 20/55] pinctrl: nomadik: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/pinctrl/nomadik/pinctrl-nomadik.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/nomadik/pinctrl-nomadik.c b/drivers/pinctrl/nomadik/pinctrl-nomadik.c index 863732287b1e..7911353ac97d 100644 --- a/drivers/pinctrl/nomadik/pinctrl-nomadik.c +++ b/drivers/pinctrl/nomadik/pinctrl-nomadik.c @@ -8,6 +8,7 @@ * Copyright (C) 2011-2013 Linus Walleij */ #include +#include #include #include #include @@ -917,7 +918,6 @@ static void nmk_gpio_dbg_show_one(struct seq_file *s, struct pinctrl_dev *pctldev, struct gpio_chip *chip, unsigned offset, unsigned gpio) { - const char *label = gpiochip_is_requested(chip, offset); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); int mode; bool is_out; @@ -934,6 +934,10 @@ static void nmk_gpio_dbg_show_one(struct seq_file *s, [NMK_GPIO_ALT_C+4] = "altC4", }; + char *label = gpiochip_dup_line_label(chip, offset); + if (IS_ERR(label)) + return; + clk_enable(nmk_chip->clk); is_out = !!(readl(nmk_chip->addr + NMK_GPIO_DIR) & BIT(offset)); pull = !(readl(nmk_chip->addr + NMK_GPIO_PDIS) & BIT(offset)); From c73505c8a001f8ce63b8ad92c9cd0176fe3c22a4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:06 +0100 Subject: [PATCH 21/55] pinctrl: baytrail: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Acked-by: Linus Walleij --- drivers/pinctrl/intel/pinctrl-baytrail.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/pinctrl/intel/pinctrl-baytrail.c b/drivers/pinctrl/intel/pinctrl-baytrail.c index 3cd0798ee631..3c8c02043481 100644 --- a/drivers/pinctrl/intel/pinctrl-baytrail.c +++ b/drivers/pinctrl/intel/pinctrl-baytrail.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1173,7 +1174,6 @@ static void byt_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) const char *pull_str = NULL; const char *pull = NULL; unsigned long flags; - const char *label; unsigned int pin; pin = vg->soc->pins[i].number; @@ -1200,9 +1200,10 @@ static void byt_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) seq_printf(s, "Pin %i: can't retrieve community\n", pin); continue; } - label = gpiochip_is_requested(chip, i); - if (!label) - label = "Unrequested"; + + char *label __free(kfree) = gpiochip_dup_line_label(chip, i); + if (IS_ERR(label)) + continue; switch (conf0 & BYT_PULL_ASSIGN_MASK) { case BYT_PULL_ASSIGN_UP: @@ -1231,7 +1232,7 @@ static void byt_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) seq_printf(s, " gpio-%-3d (%-20.20s) %s %s %s pad-%-3d offset:0x%03x mux:%d %s%s%s", pin, - label, + label ?: "Unrequested", val & BYT_INPUT_EN ? " " : "in", val & BYT_OUTPUT_EN ? " " : "out", str_hi_lo(val & BYT_LEVEL), From 069ced2206d23e56b89ed118e17a8a71a05e0ca3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:07 +0100 Subject: [PATCH 22/55] pinctrl: sppctl: use gpiochip_dup_line_label() Use the new gpiochip_dup_line_label() helper to safely retrieve the descriptor label. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/pinctrl/sunplus/sppctl.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pinctrl/sunplus/sppctl.c b/drivers/pinctrl/sunplus/sppctl.c index bb5ef391dbe4..ae156f779a16 100644 --- a/drivers/pinctrl/sunplus/sppctl.c +++ b/drivers/pinctrl/sunplus/sppctl.c @@ -4,6 +4,7 @@ * Copyright (C) Sunplus Tech / Tibbo Tech. */ +#include #include #include #include @@ -500,16 +501,15 @@ static int sppctl_gpio_set_config(struct gpio_chip *chip, unsigned int offset, static void sppctl_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { - const char *label; int i; for (i = 0; i < chip->ngpio; i++) { - label = gpiochip_is_requested(chip, i); - if (!label) - label = ""; + char *label __free(kfree) = gpiochip_dup_line_label(chip, i); + if (IS_ERR(label)) + continue; seq_printf(s, " gpio-%03d (%-16.16s | %-16.16s)", i + chip->base, - chip->names[i], label); + chip->names[i], label ?: ""); seq_printf(s, " %c", sppctl_gpio_get_direction(chip, i) ? 'I' : 'O'); seq_printf(s, ":%d", sppctl_gpio_get(chip, i)); seq_printf(s, " %s", sppctl_first_get(chip, i) ? "gpi" : "mux"); From 6fd9c9933475a3efd7eed2f80c7778908a560a1f Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:08 +0100 Subject: [PATCH 23/55] gpiolib: use gpiochip_dup_line_label() in for_each helpers Rework for_each_requested_gpio_in_range() to use the new helper to retrieve a dynamically allocated copy of the descriptor label and free it at the end of each iteration. We need to leverage the CLASS()' destructor to make sure that the label is freed even when breaking out of the loop. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- include/linux/gpio/driver.h | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 5ac6dc30c547..cae4cdaa87db 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -534,17 +534,38 @@ struct gpio_chip { const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset); char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset); + +struct _gpiochip_for_each_data { + const char **label; + unsigned int *i; +}; + +DEFINE_CLASS(_gpiochip_for_each_data, + struct _gpiochip_for_each_data, + if (*_T.label) kfree(*_T.label), + ({ + struct _gpiochip_for_each_data _data = { label, i }; + *_data.i = 0; + _data; + }), + const char **label, int *i) + /** * for_each_requested_gpio_in_range - iterates over requested GPIOs in a given range - * @chip: the chip to query - * @i: loop variable - * @base: first GPIO in the range - * @size: amount of GPIOs to check starting from @base - * @label: label of current GPIO + * @_chip: the chip to query + * @_i: loop variable + * @_base: first GPIO in the range + * @_size: amount of GPIOs to check starting from @base + * @_label: label of current GPIO */ -#define for_each_requested_gpio_in_range(chip, i, base, size, label) \ - for (i = 0; i < size; i++) \ - if ((label = gpiochip_is_requested(chip, base + i)) == NULL) {} else +#define for_each_requested_gpio_in_range(_chip, _i, _base, _size, _label) \ + for (CLASS(_gpiochip_for_each_data, _data)(&_label, &_i); \ + *_data.i < _size; \ + (*_data.i)++, kfree(*(_data.label)), *_data.label = NULL) \ + if ((*_data.label = \ + gpiochip_dup_line_label(_chip, _base + *_data.i)) == NULL) {} \ + else if (IS_ERR(*_data.label)) {} \ + else /* Iterates over all requested GPIO of the given @chip */ #define for_each_requested_gpio(chip, i, label) \ From f8d05e276b45e3097dfddd628fa991ce69c05c99 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 10:35:09 +0100 Subject: [PATCH 24/55] gpiolib: remove gpiochip_is_requested() We have no external users of gpiochip_is_requested(). Let's remove it and replace its internal calls with direct testing of the REQUESTED flag. Signed-off-by: Bartosz Golaszewski Acked-by: Linus Walleij --- drivers/gpio/gpiolib.c | 48 ++++++++++--------------------------- include/linux/gpio/driver.h | 1 - 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 0147c900afea..0ca33397812c 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1071,7 +1071,7 @@ void gpiochip_remove(struct gpio_chip *gc) spin_lock_irqsave(&gpio_lock, flags); for (i = 0; i < gdev->ngpio; i++) { - if (gpiochip_is_requested(gc, i)) + if (test_bit(FLAG_REQUESTED, &gdev->descs[i].flags)) break; } spin_unlock_irqrestore(&gpio_lock, flags); @@ -2359,33 +2359,6 @@ void gpiod_free(struct gpio_desc *desc) gpio_device_put(desc->gdev); } -/** - * gpiochip_is_requested - return string iff signal was requested - * @gc: controller managing the signal - * @offset: of signal within controller's 0..(ngpio - 1) range - * - * Returns NULL if the GPIO is not currently requested, else a string. - * The string returned is the label passed to gpio_request(); if none has been - * passed it is a meaningless, non-NULL constant. - * - * This function is for use by GPIO controller drivers. The label can - * help with diagnostics, and knowing that the signal is used as a GPIO - * can help avoid accidentally multiplexing it to another controller. - */ -const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset) -{ - struct gpio_desc *desc; - - desc = gpiochip_get_desc(gc, offset); - if (IS_ERR(desc)) - return NULL; - - if (test_bit(FLAG_REQUESTED, &desc->flags) == 0) - return NULL; - return desc->label; -} -EXPORT_SYMBOL_GPL(gpiochip_is_requested); - /** * gpiochip_dup_line_label - Get a copy of the consumer label. * @gc: GPIO chip controlling this line. @@ -2400,18 +2373,23 @@ EXPORT_SYMBOL_GPL(gpiochip_is_requested); */ char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset) { - const char *label; - char *copy; + struct gpio_desc *desc; + char *label; - label = gpiochip_is_requested(gc, offset); - if (!label) + desc = gpiochip_get_desc(gc, offset); + if (IS_ERR(desc)) return NULL; - copy = kstrdup(label, GFP_KERNEL); - if (!copy) + guard(spinlock_irqsave)(&gpio_lock); + + if (!test_bit(FLAG_REQUESTED, &desc->flags)) + return NULL; + + label = kstrdup(desc->label, GFP_KERNEL); + if (!label) return ERR_PTR(-ENOMEM); - return copy; + return label; } EXPORT_SYMBOL_GPL(gpiochip_dup_line_label); diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index cae4cdaa87db..d1a3cb061927 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -531,7 +531,6 @@ struct gpio_chip { #endif /* CONFIG_OF_GPIO */ }; -const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset); char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset); From d22f93c6a0df15b9d1f500ab57d97ecc99b3657a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 4 Dec 2023 09:57:19 +0100 Subject: [PATCH 25/55] gpio: sim: implement the dbg_show() callback Provide a custom implementation of the dbg_show() callback that prints all requested lines together with their label, direction, value and bias. This improves the code coverage of GPIOLIB. Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sim.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 1e8b92e9df80..c4106e37e6db 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -224,6 +226,25 @@ static void gpio_sim_free(struct gpio_chip *gc, unsigned int offset) } } +static void gpio_sim_dbg_show(struct seq_file *seq, struct gpio_chip *gc) +{ + struct gpio_sim_chip *chip = gpiochip_get_data(gc); + const char *label; + int i; + + guard(mutex)(&chip->lock); + + for_each_requested_gpio(gc, i, label) + seq_printf(seq, " gpio-%-3d (%s) %s,%s\n", + gc->base + i, + label, + test_bit(i, chip->direction_map) ? "input" : + test_bit(i, chip->value_map) ? "output-high" : + "output-low", + test_bit(i, chip->pull_map) ? "pull-up" : + "pull-down"); +} + static ssize_t gpio_sim_sysfs_val_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -460,6 +481,7 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) gc->to_irq = gpio_sim_to_irq; gc->request = gpio_sim_request; gc->free = gpio_sim_free; + gc->dbg_show = PTR_IF(IS_ENABLED(CONFIG_DEBUG_FS), gpio_sim_dbg_show); gc->can_sleep = true; ret = devm_gpiochip_add_data(dev, gc, chip); From 826a5d8c9df9605fb4fdefa45432f95580241a1f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 25 Oct 2023 21:42:57 +0300 Subject: [PATCH 26/55] device property: Implement device_is_big_endian() Some users want to use the struct device pointer to see if the device is big endian in terms of Open Firmware specifications, i.e. if it has a "big-endian" property, or if the kernel was compiled for BE *and* the device has a "native-endian" property. Provide inline helper for the users. Signed-off-by: Andy Shevchenko Acked-by: Greg Kroah-Hartman Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20231025184259.250588-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/property.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/linux/property.h b/include/linux/property.h index 9f2585d705a8..55c2692ffa8c 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -80,12 +80,38 @@ int fwnode_property_match_string(const struct fwnode_handle *fwnode, bool fwnode_device_is_available(const struct fwnode_handle *fwnode); +static inline bool fwnode_device_is_big_endian(const struct fwnode_handle *fwnode) +{ + if (fwnode_property_present(fwnode, "big-endian")) + return true; + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && + fwnode_property_present(fwnode, "native-endian")) + return true; + return false; +} + static inline bool fwnode_device_is_compatible(const struct fwnode_handle *fwnode, const char *compat) { return fwnode_property_match_string(fwnode, "compatible", compat) >= 0; } +/** + * device_is_big_endian - check if a device has BE registers + * @dev: Pointer to the struct device + * + * Returns: true if the device has a "big-endian" property, or if the kernel + * was compiled for BE *and* the device has a "native-endian" property. + * Returns false otherwise. + * + * Callers would nominally use ioread32be/iowrite32be if + * device_is_big_endian() == true, or readl/writel otherwise. + */ +static inline bool device_is_big_endian(const struct device *dev) +{ + return fwnode_device_is_big_endian(dev_fwnode(dev)); +} + /** * device_is_compatible - match 'compatible' property of the device with a given string * @dev: Pointer to the struct device From 0a10d107818cc4def73723ba3101aae0d25ea353 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 12 Dec 2023 11:55:01 +0100 Subject: [PATCH 27/55] gpiolib: allocate memory atomically with a spinlock held We will eventually switch to protecting the GPIO descriptors with a mutex but until then, we need to allocate memory for the label copy atomically while we're holding the global spinlock. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-gpio/62588146-eed6-42f7-ba26-160226b109fe@moroto.mountain/T/#u Fixes: f8d05e276b45 ("gpiolib: remove gpiochip_is_requested()") Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij --- drivers/gpio/gpiolib.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 4e190be75dc2..6efe44570333 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2399,7 +2399,15 @@ char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset) if (!test_bit(FLAG_REQUESTED, &desc->flags)) return NULL; - label = kstrdup(desc->label, GFP_KERNEL); + /* + * FIXME: Once we mark gpiod_direction_input/output() and + * gpiod_get_direction() with might_sleep(), we'll be able to protect + * the GPIO descriptors with mutex (while value setting operations will + * become lockless). + * + * Until this happens, this allocation needs to be atomic. + */ + label = kstrdup(desc->label, GFP_ATOMIC); if (!label) return ERR_PTR(-ENOMEM); From 5a2a2cda916335fff4d804e58f36b2305926841e Mon Sep 17 00:00:00 2001 From: Wang Jinchao Date: Mon, 18 Dec 2023 15:16:16 +0800 Subject: [PATCH 28/55] gpiolib: remove duplicate inclusions Remove second `#include `. Remove `#include ` too as it's included by `err.h`. Signed-off-by: Wang Jinchao Signed-off-by: Bartosz Golaszewski --- include/linux/gpio/driver.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index bd9bea7cb270..e846bd4e7559 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -722,7 +722,6 @@ int gpiochip_irqchip_add_domain(struct gpio_chip *gc, #else #include -#include static inline int gpiochip_irqchip_add_domain(struct gpio_chip *gc, struct irq_domain *domain) @@ -811,8 +810,6 @@ const char *gpio_device_get_label(struct gpio_device *gdev); #else /* CONFIG_GPIOLIB */ -#include - #include static inline struct gpio_chip *gpiod_to_chip(const struct gpio_desc *desc) From 4c7fcbf5077532b80bc233c83d56e09a6bfa16b0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 15 Dec 2023 10:09:43 +0100 Subject: [PATCH 29/55] gpio: xilinx: remove excess kernel doc The irqchip field has been removed from struct xgpio_instance so remove the doc as well. Fixes: b4510f8fd5d0 ("gpio: xilinx: Convert to immutable irq_chip") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202312150239.IyuTVvrL-lkp@intel.com/ Signed-off-by: Bartosz Golaszewski Reviewed-by: Michal Simek Reviewed-by: Randy Dunlap --- drivers/gpio/gpio-xilinx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 823198368250..7348df385198 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -52,7 +52,6 @@ * @dir: GPIO direction shadow register * @gpio_lock: Lock used for synchronization * @irq: IRQ used by GPIO device - * @irqchip: IRQ chip * @enable: GPIO IRQ enable/disable bitfield * @rising_edge: GPIO IRQ rising edge enable/disable bitfield * @falling_edge: GPIO IRQ falling edge enable/disable bitfield From f95fd4ac155733b5735c84a2e56eee8321232095 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 15 Dec 2023 16:52:59 +0100 Subject: [PATCH 30/55] gpiolib: rename static functions that are called with the lock taken Rename two functions that read or modify the global GPIO device list but don't take the lock themselves (and need to be called with it already acquired). Use the _unlocked() suffix which seems to be used quite consistently across the kernel despite there also existing the _locked() suffix for the same purpose. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko --- drivers/gpio/gpiolib.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 6efe44570333..5b744d1f31f6 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -290,7 +290,7 @@ struct gpio_chip *gpio_device_get_chip(struct gpio_device *gdev) EXPORT_SYMBOL_GPL(gpio_device_get_chip); /* dynamic allocation of GPIOs, e.g. on a hotplugged device */ -static int gpiochip_find_base(int ngpio) +static int gpiochip_find_base_unlocked(int ngpio) { struct gpio_device *gdev; int base = GPIO_DYNAMIC_BASE; @@ -363,7 +363,7 @@ EXPORT_SYMBOL_GPL(gpiod_get_direction); * Return -EBUSY if the new chip overlaps with some other chip's integer * space. */ -static int gpiodev_add_to_list(struct gpio_device *gdev) +static int gpiodev_add_to_list_unlocked(struct gpio_device *gdev) { struct gpio_device *prev, *next; @@ -907,7 +907,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, */ base = gc->base; if (base < 0) { - base = gpiochip_find_base(gc->ngpio); + base = gpiochip_find_base_unlocked(gc->ngpio); if (base < 0) { spin_unlock_irqrestore(&gpio_lock, flags); ret = base; @@ -927,7 +927,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, } gdev->base = base; - ret = gpiodev_add_to_list(gdev); + ret = gpiodev_add_to_list_unlocked(gdev); if (ret) { spin_unlock_irqrestore(&gpio_lock, flags); chip_err(gc, "GPIO integer space overlap, cannot add chip\n"); From 65a828bab15887b33336d251fd659b2ae86de6d6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 15 Dec 2023 16:53:00 +0100 Subject: [PATCH 31/55] gpiolib: use a mutex to protect the list of GPIO devices The global list of GPIO devices is never modified or accessed from atomic context so it's fine to protect it using a mutex. Add a new global lock dedicated to the gpio_devices list and use it whenever accessing or modifying it. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko --- drivers/gpio/gpiolib-sysfs.c | 45 ++++++------ drivers/gpio/gpiolib-sysfs.h | 6 ++ drivers/gpio/gpiolib.c | 139 ++++++++++++++++------------------- drivers/gpio/gpiolib.h | 2 + 4 files changed, 91 insertions(+), 101 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 6f309a3b2d9a..ae4fc013b675 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -766,6 +766,25 @@ int gpiochip_sysfs_register(struct gpio_device *gdev) return 0; } +int gpiochip_sysfs_register_all(void) +{ + struct gpio_device *gdev; + int ret; + + guard(mutex)(&gpio_devices_lock); + + list_for_each_entry(gdev, &gpio_devices, list) { + if (gdev->mockdev) + continue; + + ret = gpiochip_sysfs_register(gdev); + if (ret) + return ret; + } + + return 0; +} + void gpiochip_sysfs_unregister(struct gpio_device *gdev) { struct gpio_desc *desc; @@ -790,9 +809,7 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev) static int __init gpiolib_sysfs_init(void) { - int status; - unsigned long flags; - struct gpio_device *gdev; + int status; status = class_register(&gpio_class); if (status < 0) @@ -804,26 +821,6 @@ static int __init gpiolib_sysfs_init(void) * We run before arch_initcall() so chip->dev nodes can have * registered, and so arch_initcall() can always gpiod_export(). */ - spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(gdev, &gpio_devices, list) { - if (gdev->mockdev) - continue; - - /* - * TODO we yield gpio_lock here because - * gpiochip_sysfs_register() acquires a mutex. This is unsafe - * and needs to be fixed. - * - * Also it would be nice to use gpio_device_find() here so we - * can keep gpio_chips local to gpiolib.c, but the yield of - * gpio_lock prevents us from doing this. - */ - spin_unlock_irqrestore(&gpio_lock, flags); - status = gpiochip_sysfs_register(gdev); - spin_lock_irqsave(&gpio_lock, flags); - } - spin_unlock_irqrestore(&gpio_lock, flags); - - return status; + return gpiochip_sysfs_register_all(); } postcore_initcall(gpiolib_sysfs_init); diff --git a/drivers/gpio/gpiolib-sysfs.h b/drivers/gpio/gpiolib-sysfs.h index b794b396d6a5..ab157cec0b4b 100644 --- a/drivers/gpio/gpiolib-sysfs.h +++ b/drivers/gpio/gpiolib-sysfs.h @@ -8,6 +8,7 @@ struct gpio_device; #ifdef CONFIG_GPIO_SYSFS int gpiochip_sysfs_register(struct gpio_device *gdev); +int gpiochip_sysfs_register_all(void); void gpiochip_sysfs_unregister(struct gpio_device *gdev); #else @@ -17,6 +18,11 @@ static inline int gpiochip_sysfs_register(struct gpio_device *gdev) return 0; } +static inline int gpiochip_sysfs_register_all(void) +{ + return 0; +} + static inline void gpiochip_sysfs_unregister(struct gpio_device *gdev) { } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 5b744d1f31f6..9508396c967e 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -94,7 +96,9 @@ DEFINE_SPINLOCK(gpio_lock); static DEFINE_MUTEX(gpio_lookup_lock); static LIST_HEAD(gpio_lookup_list); + LIST_HEAD(gpio_devices); +DEFINE_MUTEX(gpio_devices_lock); static DEFINE_MUTEX(gpio_machine_hogs_mutex); static LIST_HEAD(gpio_machine_hogs); @@ -126,20 +130,15 @@ static inline void desc_set_label(struct gpio_desc *d, const char *label) struct gpio_desc *gpio_to_desc(unsigned gpio) { struct gpio_device *gdev; - unsigned long flags; - spin_lock_irqsave(&gpio_lock, flags); - - list_for_each_entry(gdev, &gpio_devices, list) { - if (gdev->base <= gpio && - gdev->base + gdev->ngpio > gpio) { - spin_unlock_irqrestore(&gpio_lock, flags); - return &gdev->descs[gpio - gdev->base]; + scoped_guard(mutex, &gpio_devices_lock) { + list_for_each_entry(gdev, &gpio_devices, list) { + if (gdev->base <= gpio && + gdev->base + gdev->ngpio > gpio) + return &gdev->descs[gpio - gdev->base]; } } - spin_unlock_irqrestore(&gpio_lock, flags); - if (!gpio_is_valid(gpio)) pr_warn("invalid GPIO %d\n", gpio); @@ -412,26 +411,21 @@ static int gpiodev_add_to_list_unlocked(struct gpio_device *gdev) static struct gpio_desc *gpio_name_to_desc(const char * const name) { struct gpio_device *gdev; - unsigned long flags; if (!name) return NULL; - spin_lock_irqsave(&gpio_lock, flags); + guard(mutex)(&gpio_devices_lock); list_for_each_entry(gdev, &gpio_devices, list) { struct gpio_desc *desc; for_each_gpio_desc(gdev->chip, desc) { - if (desc->name && !strcmp(desc->name, name)) { - spin_unlock_irqrestore(&gpio_lock, flags); + if (desc->name && !strcmp(desc->name, name)) return desc; - } } } - spin_unlock_irqrestore(&gpio_lock, flags); - return NULL; } @@ -669,11 +663,9 @@ EXPORT_SYMBOL_GPL(gpiochip_line_is_valid); static void gpiodev_release(struct device *dev) { struct gpio_device *gdev = to_gpio_device(dev); - unsigned long flags; - spin_lock_irqsave(&gpio_lock, flags); - list_del(&gdev->list); - spin_unlock_irqrestore(&gpio_lock, flags); + scoped_guard(mutex, &gpio_devices_lock) + list_del(&gdev->list); ida_free(&gpio_ida, gdev->id); kfree_const(gdev->label); @@ -831,7 +823,6 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, struct lock_class_key *request_key) { struct gpio_device *gdev; - unsigned long flags; unsigned int i; int base = 0; int ret = 0; @@ -896,48 +887,45 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, gdev->ngpio = gc->ngpio; - spin_lock_irqsave(&gpio_lock, flags); + scoped_guard(mutex, &gpio_devices_lock) { + /* + * TODO: this allocates a Linux GPIO number base in the global + * GPIO numberspace for this chip. In the long run we want to + * get *rid* of this numberspace and use only descriptors, but + * it may be a pipe dream. It will not happen before we get rid + * of the sysfs interface anyways. + */ + base = gc->base; - /* - * TODO: this allocates a Linux GPIO number base in the global - * GPIO numberspace for this chip. In the long run we want to - * get *rid* of this numberspace and use only descriptors, but - * it may be a pipe dream. It will not happen before we get rid - * of the sysfs interface anyways. - */ - base = gc->base; - if (base < 0) { - base = gpiochip_find_base_unlocked(gc->ngpio); if (base < 0) { - spin_unlock_irqrestore(&gpio_lock, flags); - ret = base; - base = 0; + base = gpiochip_find_base_unlocked(gc->ngpio); + if (base < 0) { + ret = base; + base = 0; + goto err_free_label; + } + /* + * TODO: it should not be necessary to reflect the assigned + * base outside of the GPIO subsystem. Go over drivers and + * see if anyone makes use of this, else drop this and assign + * a poison instead. + */ + gc->base = base; + } else { + dev_warn(&gdev->dev, + "Static allocation of GPIO base is deprecated, use dynamic allocation.\n"); + } + gdev->base = base; + + ret = gpiodev_add_to_list_unlocked(gdev); + if (ret) { + chip_err(gc, "GPIO integer space overlap, cannot add chip\n"); goto err_free_label; } - /* - * TODO: it should not be necessary to reflect the assigned - * base outside of the GPIO subsystem. Go over drivers and - * see if anyone makes use of this, else drop this and assign - * a poison instead. - */ - gc->base = base; - } else { - dev_warn(&gdev->dev, - "Static allocation of GPIO base is deprecated, use dynamic allocation.\n"); + + for (i = 0; i < gc->ngpio; i++) + gdev->descs[i].gdev = gdev; } - gdev->base = base; - - ret = gpiodev_add_to_list_unlocked(gdev); - if (ret) { - spin_unlock_irqrestore(&gpio_lock, flags); - chip_err(gc, "GPIO integer space overlap, cannot add chip\n"); - goto err_free_label; - } - - for (i = 0; i < gc->ngpio; i++) - gdev->descs[i].gdev = gdev; - - spin_unlock_irqrestore(&gpio_lock, flags); BLOCKING_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier); BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier); @@ -1029,9 +1017,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, goto err_print_message; } err_remove_from_list: - spin_lock_irqsave(&gpio_lock, flags); - list_del(&gdev->list); - spin_unlock_irqrestore(&gpio_lock, flags); + scoped_guard(mutex, &gpio_devices_lock) + list_del(&gdev->list); err_free_label: kfree_const(gdev->label); err_free_descs: @@ -1140,7 +1127,7 @@ struct gpio_device *gpio_device_find(void *data, */ might_sleep(); - guard(spinlock_irqsave)(&gpio_lock); + guard(mutex)(&gpio_devices_lock); list_for_each_entry(gdev, &gpio_devices, list) { if (gdev->chip && match(gdev->chip, data)) @@ -4756,35 +4743,33 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos) { - unsigned long flags; struct gpio_device *gdev = NULL; loff_t index = *pos; s->private = ""; - spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(gdev, &gpio_devices, list) - if (index-- == 0) { - spin_unlock_irqrestore(&gpio_lock, flags); + guard(mutex)(&gpio_devices_lock); + + list_for_each_entry(gdev, &gpio_devices, list) { + if (index-- == 0) return gdev; - } - spin_unlock_irqrestore(&gpio_lock, flags); + } return NULL; } static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos) { - unsigned long flags; struct gpio_device *gdev = v; void *ret = NULL; - spin_lock_irqsave(&gpio_lock, flags); - if (list_is_last(&gdev->list, &gpio_devices)) - ret = NULL; - else - ret = list_first_entry(&gdev->list, struct gpio_device, list); - spin_unlock_irqrestore(&gpio_lock, flags); + scoped_guard(mutex, &gpio_devices_lock) { + if (list_is_last(&gdev->list, &gpio_devices)) + ret = NULL; + else + ret = list_first_entry(&gdev->list, struct gpio_device, + list); + } s->private = "\n"; ++*pos; diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 3ccacf3c1288..8142b5cbc395 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -15,6 +15,7 @@ #include /* for enum gpiod_flags */ #include #include +#include #include #include @@ -136,6 +137,7 @@ int gpiod_set_transitory(struct gpio_desc *desc, bool transitory); extern spinlock_t gpio_lock; extern struct list_head gpio_devices; +extern struct mutex gpio_devices_lock; void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action); From 0e6e3c6f7cb47ea1ae15a8148a6ddb30035c210e Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Fri, 15 Dec 2023 15:39:02 +0100 Subject: [PATCH 32/55] dt-bindings: gpio: dwapb: allow gpio-ranges Allow the generic gpio-ranges property so GPIOs can be mapped to their corresponding pin. This way control of GPIO on pins that are already used by other peripherals can be denied and basic pinconf can be done on pin controllers that support it. Signed-off-by: Emil Renner Berthing Acked-by: Rob Herring Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml index eefe7b345286..ab2afc0e4153 100644 --- a/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/snps,dw-apb-gpio.yaml @@ -65,6 +65,8 @@ patternProperties: minItems: 1 maxItems: 32 + gpio-ranges: true + ngpios: default: 32 minimum: 1 From 001cf2dec38c85d2fa123c0c4e75145cdfc6486d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 25 Oct 2023 21:42:58 +0300 Subject: [PATCH 33/55] gpio: mmio: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 3ff0ea1e351c..66308b165a0d 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -58,7 +58,6 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` #include #include #include -#include #include "gpiolib.h" @@ -688,7 +687,6 @@ static void __iomem *bgpio_map(struct platform_device *pdev, return devm_ioremap_resource(&pdev->dev, r); } -#ifdef CONFIG_OF static const struct of_device_id bgpio_of_match[] = { { .compatible = "brcm,bcm6345-gpio" }, { .compatible = "wd,mbl-gpio" }, @@ -697,36 +695,27 @@ static const struct of_device_id bgpio_of_match[] = { }; MODULE_DEVICE_TABLE(of, bgpio_of_match); -static struct bgpio_pdata *bgpio_parse_dt(struct platform_device *pdev, - unsigned long *flags) +static struct bgpio_pdata *bgpio_parse_fw(struct device *dev, unsigned long *flags) { struct bgpio_pdata *pdata; - if (!pdev->dev.of_node) + if (!dev_fwnode(dev)) return NULL; - pdata = devm_kzalloc(&pdev->dev, sizeof(struct bgpio_pdata), - GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); pdata->base = -1; - if (of_device_is_big_endian(pdev->dev.of_node)) + if (device_is_big_endian(dev)) *flags |= BGPIOF_BIG_ENDIAN_BYTE_ORDER; - if (of_property_read_bool(pdev->dev.of_node, "no-output")) + if (device_property_read_bool(dev, "no-output")) *flags |= BGPIOF_NO_OUTPUT; return pdata; } -#else -static struct bgpio_pdata *bgpio_parse_dt(struct platform_device *pdev, - unsigned long *flags) -{ - return NULL; -} -#endif /* CONFIG_OF */ static int bgpio_pdev_probe(struct platform_device *pdev) { @@ -743,7 +732,7 @@ static int bgpio_pdev_probe(struct platform_device *pdev) struct gpio_chip *gc; struct bgpio_pdata *pdata; - pdata = bgpio_parse_dt(pdev, &flags); + pdata = bgpio_parse_fw(dev, &flags); if (IS_ERR(pdata)) return PTR_ERR(pdata); @@ -814,7 +803,7 @@ MODULE_DEVICE_TABLE(platform, bgpio_id_table); static struct platform_driver bgpio_driver = { .driver = { .name = "basic-mmio-gpio", - .of_match_table = of_match_ptr(bgpio_of_match), + .of_match_table = bgpio_of_match, }, .id_table = bgpio_id_table, .probe = bgpio_pdev_probe, From c9bd27c880b00bac48b4d6aacaca127d8cfc4fd6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 25 Oct 2023 21:42:59 +0300 Subject: [PATCH 34/55] gpio: mmio: Clean up headers There is a few things done: - include only the headers we are direct user of - add missing headers - group generic headers and subsystem headers - sort each group alphabetically Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 66308b165a0d..71e1af7c2184 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -40,24 +40,22 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` * `.......````.``` */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include + +#include #include "gpiolib.h" From 9344e34e7992fec95ce6210d95ac01437dd327ab Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Tue, 19 Dec 2023 08:41:54 +0800 Subject: [PATCH 35/55] gpiolib: cdev: relocate debounce_period_us from struct gpio_desc Store the debounce period for a requested line locally, rather than in the debounce_period_us field in the gpiolib struct gpio_desc. Add a global tree of lines containing supplemental line information to make the debounce period available to be reported by the GPIO_V2_GET_LINEINFO_IOCTL and the line change notifier. Signed-off-by: Kent Gibson Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 165 +++++++++++++++++++++++++++++++----- 1 file changed, 142 insertions(+), 23 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 02ffda6c1e51..aecc4241b6c8 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -461,6 +463,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) /** * struct line - contains the state of a requested line + * @node: to store the object in supinfo_tree if supplemental * @desc: the GPIO descriptor for this line. * @req: the corresponding line request * @irq: the interrupt triggered in response to events on this GPIO @@ -473,6 +476,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) * @line_seqno: the seqno for the current edge event in the sequence of * events for this line. * @work: the worker that implements software debouncing + * @debounce_period_us: the debounce period in microseconds * @sw_debounced: flag indicating if the software debouncer is active * @level: the current debounced physical level of the line * @hdesc: the Hardware Timestamp Engine (HTE) descriptor @@ -481,6 +485,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) * @last_seqno: the last sequence number before debounce period expires */ struct line { + struct rb_node node; struct gpio_desc *desc; /* * -- edge detector specific fields -- @@ -514,6 +519,15 @@ struct line { * -- debouncer specific fields -- */ struct delayed_work work; + /* + * debounce_period_us is accessed by debounce_irq_handler() and + * process_hw_ts() which are disabled when modified by + * debounce_setup(), edge_detector_setup() or edge_detector_stop() + * or can live with a stale version when updated by + * edge_detector_update(). + * The modifying functions are themselves mutually exclusive. + */ + unsigned int debounce_period_us; /* * sw_debounce is accessed by linereq_set_config(), which is the * only setter, and linereq_get_values(), which can live with a @@ -546,6 +560,17 @@ struct line { #endif /* CONFIG_HTE */ }; +/* + * a rbtree of the struct lines containing supplemental info. + * Used to populate gpio_v2_line_info with cdev specific fields not contained + * in the struct gpio_desc. + * A line is determined to contain supplemental information by + * line_has_supinfo(). + */ +static struct rb_root supinfo_tree = RB_ROOT; +/* covers supinfo_tree */ +static DEFINE_SPINLOCK(supinfo_lock); + /** * struct linereq - contains the state of a userspace line request * @gdev: the GPIO device the line request pertains to @@ -559,7 +584,8 @@ struct line { * this line request. Note that this is not used when @num_lines is 1, as * the line_seqno is then the same and is cheaper to calculate. * @config_mutex: mutex for serializing ioctl() calls to ensure consistency - * of configuration, particularly multi-step accesses to desc flags. + * of configuration, particularly multi-step accesses to desc flags and + * changes to supinfo status. * @lines: the lines held by this line request, with @num_lines elements. */ struct linereq { @@ -575,6 +601,103 @@ struct linereq { struct line lines[] __counted_by(num_lines); }; +static void supinfo_insert(struct line *line) +{ + struct rb_node **new = &(supinfo_tree.rb_node), *parent = NULL; + struct line *entry; + + guard(spinlock)(&supinfo_lock); + + while (*new) { + entry = container_of(*new, struct line, node); + + parent = *new; + if (line->desc < entry->desc) { + new = &((*new)->rb_left); + } else if (line->desc > entry->desc) { + new = &((*new)->rb_right); + } else { + /* this should never happen */ + WARN(1, "duplicate line inserted"); + return; + } + } + + rb_link_node(&line->node, parent, new); + rb_insert_color(&line->node, &supinfo_tree); +} + +static void supinfo_erase(struct line *line) +{ + guard(spinlock)(&supinfo_lock); + + rb_erase(&line->node, &supinfo_tree); +} + +static struct line *supinfo_find(struct gpio_desc *desc) +{ + struct rb_node *node = supinfo_tree.rb_node; + struct line *line; + + while (node) { + line = container_of(node, struct line, node); + if (desc < line->desc) + node = node->rb_left; + else if (desc > line->desc) + node = node->rb_right; + else + return line; + } + return NULL; +} + +static void supinfo_to_lineinfo(struct gpio_desc *desc, + struct gpio_v2_line_info *info) +{ + struct gpio_v2_line_attribute *attr; + struct line *line; + + guard(spinlock)(&supinfo_lock); + + line = supinfo_find(desc); + if (!line) + return; + + attr = &info->attrs[info->num_attrs]; + attr->id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; + attr->debounce_period_us = READ_ONCE(line->debounce_period_us); + info->num_attrs++; +} + +static inline bool line_has_supinfo(struct line *line) +{ + return READ_ONCE(line->debounce_period_us); +} + +/* + * Checks line_has_supinfo() before and after the change to avoid unnecessary + * supinfo_tree access. + * Called indirectly by linereq_create() or linereq_set_config() so line + * is already protected from concurrent changes. + */ +static void line_set_debounce_period(struct line *line, + unsigned int debounce_period_us) +{ + bool was_suppl = line_has_supinfo(line); + + WRITE_ONCE(line->debounce_period_us, debounce_period_us); + + /* if supinfo status is unchanged then we're done */ + if (line_has_supinfo(line) == was_suppl) + return; + + /* supinfo status has changed, so update the tree */ + if (was_suppl) + supinfo_erase(line); + else + supinfo_insert(line); +} + #define GPIO_V2_LINE_BIAS_FLAGS \ (GPIO_V2_LINE_FLAG_BIAS_PULL_UP | \ GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN | \ @@ -723,7 +846,7 @@ static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p) line->total_discard_seq++; line->last_seqno = ts->seq; mod_delayed_work(system_wq, &line->work, - usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); + usecs_to_jiffies(READ_ONCE(line->debounce_period_us))); } else { if (unlikely(ts->seq < line->line_seqno)) return HTE_CB_HANDLED; @@ -864,7 +987,7 @@ static irqreturn_t debounce_irq_handler(int irq, void *p) struct line *line = p; mod_delayed_work(system_wq, &line->work, - usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); + usecs_to_jiffies(READ_ONCE(line->debounce_period_us))); return IRQ_HANDLED; } @@ -946,7 +1069,7 @@ static int debounce_setup(struct line *line, unsigned int debounce_period_us) /* try hardware */ ret = gpiod_set_debounce(line->desc, debounce_period_us); if (!ret) { - WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us); + line_set_debounce_period(line, debounce_period_us); return ret; } if (ret != -ENOTSUPP) @@ -1025,8 +1148,7 @@ static void edge_detector_stop(struct line *line) cancel_delayed_work_sync(&line->work); WRITE_ONCE(line->sw_debounced, 0); WRITE_ONCE(line->edflags, 0); - if (line->desc) - WRITE_ONCE(line->desc->debounce_period_us, 0); + line_set_debounce_period(line, 0); /* do not change line->level - see comment in debounced_value() */ } @@ -1051,7 +1173,7 @@ static int edge_detector_setup(struct line *line, ret = debounce_setup(line, debounce_period_us); if (ret) return ret; - WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us); + line_set_debounce_period(line, debounce_period_us); } /* detection disabled or sw debouncer will provide edge detection */ @@ -1093,12 +1215,12 @@ static int edge_detector_update(struct line *line, gpio_v2_line_config_debounce_period(lc, line_idx); if ((active_edflags == edflags) && - (READ_ONCE(line->desc->debounce_period_us) == debounce_period_us)) + (READ_ONCE(line->debounce_period_us) == debounce_period_us)) return 0; /* sw debounced and still will be...*/ if (debounce_period_us && READ_ONCE(line->sw_debounced)) { - WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us); + line_set_debounce_period(line, debounce_period_us); return 0; } @@ -1573,6 +1695,7 @@ static ssize_t linereq_read(struct file *file, char __user *buf, static void linereq_free(struct linereq *lr) { + struct line *line; unsigned int i; if (lr->device_unregistered_nb.notifier_call) @@ -1580,10 +1703,14 @@ static void linereq_free(struct linereq *lr) &lr->device_unregistered_nb); for (i = 0; i < lr->num_lines; i++) { - if (lr->lines[i].desc) { - edge_detector_stop(&lr->lines[i]); - gpiod_free(lr->lines[i].desc); - } + line = &lr->lines[i]; + if (!line->desc) + continue; + + edge_detector_stop(line); + if (line_has_supinfo(line)) + supinfo_erase(line); + gpiod_free(line->desc); } kfifo_free(&lr->events); kfree(lr->label); @@ -2274,8 +2401,6 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, struct gpio_chip *gc = desc->gdev->chip; bool ok_for_pinctrl; unsigned long flags; - u32 debounce_period_us; - unsigned int num_attrs = 0; memset(info, 0, sizeof(*info)); info->offset = gpio_chip_hwgpio(desc); @@ -2341,14 +2466,6 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, else if (test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags)) info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE; - debounce_period_us = READ_ONCE(desc->debounce_period_us); - if (debounce_period_us) { - info->attrs[num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; - info->attrs[num_attrs].debounce_period_us = debounce_period_us; - num_attrs++; - } - info->num_attrs = num_attrs; - spin_unlock_irqrestore(&gpio_lock, flags); } @@ -2455,6 +2572,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, return -EBUSY; } gpio_desc_to_lineinfo(desc, &lineinfo); + supinfo_to_lineinfo(desc, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { if (watch) @@ -2545,6 +2663,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, chg.event_type = action; chg.timestamp_ns = ktime_get_ns(); gpio_desc_to_lineinfo(desc, &chg.info); + supinfo_to_lineinfo(desc, &chg.info); ret = kfifo_in_spinlocked(&cdev->events, &chg, 1, &cdev->wait.lock); if (ret) From d8543cbaf979271bee97278b1f95240661b96500 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Tue, 19 Dec 2023 08:41:55 +0800 Subject: [PATCH 36/55] gpiolib: remove debounce_period_us from struct gpio_desc cdev is the only user of the debounce_period_us field in struct gpio_desc, and it no longer uses it, so remove it. Signed-off-by: Kent Gibson Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 3 --- drivers/gpio/gpiolib.h | 5 ----- 2 files changed, 8 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 9508396c967e..c9ca809b55de 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2331,9 +2331,6 @@ static bool gpiod_free_commit(struct gpio_desc *desc) clear_bit(FLAG_IS_HOGGED, &desc->flags); #ifdef CONFIG_OF_DYNAMIC desc->hog = NULL; -#endif -#ifdef CONFIG_GPIO_CDEV - WRITE_ONCE(desc->debounce_period_us, 0); #endif ret = true; } diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 8142b5cbc395..0ce7451a6b24 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -149,7 +149,6 @@ void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action); * @label: Name of the consumer * @name: Line name * @hog: Pointer to the device node that hogs this line (if any) - * @debounce_period_us: Debounce period in microseconds * * These are obtained using gpiod_get() and are preferable to the old * integer-based handles. @@ -187,10 +186,6 @@ struct gpio_desc { #ifdef CONFIG_OF_DYNAMIC struct device_node *hog; #endif -#ifdef CONFIG_GPIO_CDEV - /* debounce period in microseconds */ - unsigned int debounce_period_us; -#endif }; #define gpiod_not_found(desc) (IS_ERR(desc) && PTR_ERR(desc) == -ENOENT) From 0ebeaab4d59eb373e17ffbc50d8c4e9665418cbe Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Tue, 19 Dec 2023 08:41:57 +0800 Subject: [PATCH 37/55] gpiolib: cdev: fully adopt guard() and scoped_guard() Use guard() or scoped_guard() for critical sections rather than discrete lock/unlock pairs. Signed-off-by: Kent Gibson Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 139 ++++++++++++++---------------------- 1 file changed, 55 insertions(+), 84 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index aecc4241b6c8..84471b703402 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -748,13 +748,13 @@ static void linereq_put_event(struct linereq *lr, { bool overflow = false; - spin_lock(&lr->wait.lock); - if (kfifo_is_full(&lr->events)) { - overflow = true; - kfifo_skip(&lr->events); + scoped_guard(spinlock, &lr->wait.lock) { + if (kfifo_is_full(&lr->events)) { + overflow = true; + kfifo_skip(&lr->events); + } + kfifo_in(&lr->events, le, 1); } - kfifo_in(&lr->events, le, 1); - spin_unlock(&lr->wait.lock); if (!overflow) wake_up_poll(&lr->wait, EPOLLIN); else @@ -1487,18 +1487,13 @@ static long linereq_set_values_unlocked(struct linereq *lr, static long linereq_set_values(struct linereq *lr, void __user *ip) { struct gpio_v2_line_values lv; - int ret; if (copy_from_user(&lv, ip, sizeof(lv))) return -EFAULT; - mutex_lock(&lr->config_mutex); + guard(mutex)(&lr->config_mutex); - ret = linereq_set_values_unlocked(lr, &lv); - - mutex_unlock(&lr->config_mutex); - - return ret; + return linereq_set_values_unlocked(lr, &lv); } static long linereq_set_config_unlocked(struct linereq *lr, @@ -1556,13 +1551,9 @@ static long linereq_set_config(struct linereq *lr, void __user *ip) if (ret) return ret; - mutex_lock(&lr->config_mutex); + guard(mutex)(&lr->config_mutex); - ret = linereq_set_config_unlocked(lr, &lc); - - mutex_unlock(&lr->config_mutex); - - return ret; + return linereq_set_config_unlocked(lr, &lc); } static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd, @@ -1644,28 +1635,22 @@ static ssize_t linereq_read_unlocked(struct file *file, char __user *buf, return -EINVAL; do { - spin_lock(&lr->wait.lock); - if (kfifo_is_empty(&lr->events)) { - if (bytes_read) { - spin_unlock(&lr->wait.lock); - return bytes_read; + scoped_guard(spinlock, &lr->wait.lock) { + if (kfifo_is_empty(&lr->events)) { + if (bytes_read) + return bytes_read; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible_locked(lr->wait, + !kfifo_is_empty(&lr->events)); + if (ret) + return ret; } - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&lr->wait.lock); - return -EAGAIN; - } - - ret = wait_event_interruptible_locked(lr->wait, - !kfifo_is_empty(&lr->events)); - if (ret) { - spin_unlock(&lr->wait.lock); - return ret; - } + ret = kfifo_out(&lr->events, &le, 1); } - - ret = kfifo_out(&lr->events, &le, 1); - spin_unlock(&lr->wait.lock); if (ret != 1) { /* * This should never happen - we were holding the @@ -2015,28 +2000,22 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, return -EINVAL; do { - spin_lock(&le->wait.lock); - if (kfifo_is_empty(&le->events)) { - if (bytes_read) { - spin_unlock(&le->wait.lock); - return bytes_read; + scoped_guard(spinlock, &le->wait.lock) { + if (kfifo_is_empty(&le->events)) { + if (bytes_read) + return bytes_read; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible_locked(le->wait, + !kfifo_is_empty(&le->events)); + if (ret) + return ret; } - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&le->wait.lock); - return -EAGAIN; - } - - ret = wait_event_interruptible_locked(le->wait, - !kfifo_is_empty(&le->events)); - if (ret) { - spin_unlock(&le->wait.lock); - return ret; - } + ret = kfifo_out(&le->events, &ge, 1); } - - ret = kfifo_out(&le->events, &ge, 1); - spin_unlock(&le->wait.lock); if (ret != 1) { /* * This should never happen - we were holding the lock @@ -2732,38 +2711,30 @@ static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, #endif do { - spin_lock(&cdev->wait.lock); - if (kfifo_is_empty(&cdev->events)) { - if (bytes_read) { - spin_unlock(&cdev->wait.lock); - return bytes_read; - } + scoped_guard(spinlock, &cdev->wait.lock) { + if (kfifo_is_empty(&cdev->events)) { + if (bytes_read) + return bytes_read; - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&cdev->wait.lock); - return -EAGAIN; - } + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; - ret = wait_event_interruptible_locked(cdev->wait, - !kfifo_is_empty(&cdev->events)); - if (ret) { - spin_unlock(&cdev->wait.lock); - return ret; + ret = wait_event_interruptible_locked(cdev->wait, + !kfifo_is_empty(&cdev->events)); + if (ret) + return ret; } - } #ifdef CONFIG_GPIO_CDEV_V1 - /* must be after kfifo check so watch_abi_version is set */ - if (atomic_read(&cdev->watch_abi_version) == 2) - event_size = sizeof(struct gpio_v2_line_info_changed); - else - event_size = sizeof(struct gpioline_info_changed); - if (count < event_size) { - spin_unlock(&cdev->wait.lock); - return -EINVAL; - } + /* must be after kfifo check so watch_abi_version is set */ + if (atomic_read(&cdev->watch_abi_version) == 2) + event_size = sizeof(struct gpio_v2_line_info_changed); + else + event_size = sizeof(struct gpioline_info_changed); + if (count < event_size) + return -EINVAL; #endif - ret = kfifo_out(&cdev->events, &event, 1); - spin_unlock(&cdev->wait.lock); + ret = kfifo_out(&cdev->events, &event, 1); + } if (ret != 1) { ret = -EIO; break; From 193b6b0902bf57ff13bbadaa037a288c73c9d126 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Tue, 19 Dec 2023 08:41:58 +0800 Subject: [PATCH 38/55] gpiolib: cdev: improve documentation of get/set values Add documentation of the algorithm used to perform scatter/gather of the requested lines and values in linereq_get_values() and linereq_set_values_unlocked() to improve maintainability. Signed-off-by: Kent Gibson Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 84471b703402..e3fdf7b86d10 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1394,9 +1394,18 @@ static long linereq_get_values(struct linereq *lr, void __user *ip) if (copy_from_user(&lv, ip, sizeof(lv))) return -EFAULT; + /* + * gpiod_get_array_value_complex() requires compacted desc and val + * arrays, rather than the sparse ones in lv. + * Calculation of num_get and construction of the desc array is + * optimized to avoid allocation for the desc array for the common + * num_get == 1 case. + */ + /* scan requested lines to calculate the subset to get */ for (num_get = 0, i = 0; i < lr->num_lines; i++) { if (lv.mask & BIT_ULL(i)) { num_get++; + /* capture desc for the num_get == 1 case */ descs = &lr->lines[i].desc; } } @@ -1405,6 +1414,7 @@ static long linereq_get_values(struct linereq *lr, void __user *ip) return -EINVAL; if (num_get != 1) { + /* build compacted desc array */ descs = kmalloc_array(num_get, sizeof(*descs), GFP_KERNEL); if (!descs) return -ENOMEM; @@ -1425,6 +1435,7 @@ static long linereq_get_values(struct linereq *lr, void __user *ip) lv.bits = 0; for (didx = 0, i = 0; i < lr->num_lines; i++) { + /* unpack compacted vals for the response */ if (lv.mask & BIT_ULL(i)) { if (lr->lines[i].sw_debounced) val = debounced_value(&lr->lines[i]); @@ -1450,14 +1461,25 @@ static long linereq_set_values_unlocked(struct linereq *lr, unsigned int i, didx, num_set; int ret; + /* + * gpiod_set_array_value_complex() requires compacted desc and val + * arrays, rather than the sparse ones in lv. + * Calculation of num_set and construction of the descs and vals arrays + * is optimized to minimize scanning the lv->mask, and to avoid + * allocation for the desc array for the common num_set == 1 case. + */ bitmap_zero(vals, GPIO_V2_LINES_MAX); + /* scan requested lines to determine the subset to be set */ for (num_set = 0, i = 0; i < lr->num_lines; i++) { if (lv->mask & BIT_ULL(i)) { + /* setting inputs is not allowed */ if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags)) return -EPERM; + /* add to compacted values */ if (lv->bits & BIT_ULL(i)) __set_bit(num_set, vals); num_set++; + /* capture desc for the num_set == 1 case */ descs = &lr->lines[i].desc; } } @@ -1465,7 +1487,7 @@ static long linereq_set_values_unlocked(struct linereq *lr, return -EINVAL; if (num_set != 1) { - /* build compacted desc array and values */ + /* build compacted desc array */ descs = kmalloc_array(num_set, sizeof(*descs), GFP_KERNEL); if (!descs) return -ENOMEM; From 1cdc605c7d70a390ff75a814a26c6f45d75778be Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Tue, 19 Dec 2023 08:41:56 +0800 Subject: [PATCH 39/55] gpiolib: cdev: reduce locking in gpio_desc_to_lineinfo() Reduce the time holding the gpio_lock by snapshotting the desc flags, rather than testing them individually while holding the lock. Accept that the calculation of the used field is inherently racy, and only check the availability of the line from pinctrl if other checks pass, so avoiding the check for lines that are otherwise in use. Signed-off-by: Kent Gibson Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 74 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index e3fdf7b86d10..744734405912 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2400,74 +2400,72 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, struct gpio_v2_line_info *info) { struct gpio_chip *gc = desc->gdev->chip; - bool ok_for_pinctrl; - unsigned long flags; + unsigned long dflags; memset(info, 0, sizeof(*info)); info->offset = gpio_chip_hwgpio(desc); - /* - * This function takes a mutex so we must check this before taking - * the spinlock. - * - * FIXME: find a non-racy way to retrieve this information. Maybe a - * lock common to both frameworks? - */ - ok_for_pinctrl = pinctrl_gpio_can_use_line(gc, info->offset); + scoped_guard(spinlock_irqsave, &gpio_lock) { + if (desc->name) + strscpy(info->name, desc->name, sizeof(info->name)); - spin_lock_irqsave(&gpio_lock, flags); + if (desc->label) + strscpy(info->consumer, desc->label, + sizeof(info->consumer)); - if (desc->name) - strscpy(info->name, desc->name, sizeof(info->name)); - - if (desc->label) - strscpy(info->consumer, desc->label, sizeof(info->consumer)); + dflags = READ_ONCE(desc->flags); + } /* - * Userspace only need to know that the kernel is using this GPIO so - * it can't use it. + * Userspace only need know that the kernel is using this GPIO so it + * can't use it. + * The calculation of the used flag is slightly racy, as it may read + * desc, gc and pinctrl state without a lock covering all three at + * once. Worst case if the line is in transition and the calculation + * is inconsistent then it looks to the user like they performed the + * read on the other side of the transition - but that can always + * happen. + * The definitive test that a line is available to userspace is to + * request it. */ - info->flags = 0; - if (test_bit(FLAG_REQUESTED, &desc->flags) || - test_bit(FLAG_IS_HOGGED, &desc->flags) || - test_bit(FLAG_USED_AS_IRQ, &desc->flags) || - test_bit(FLAG_EXPORT, &desc->flags) || - test_bit(FLAG_SYSFS, &desc->flags) || + if (test_bit(FLAG_REQUESTED, &dflags) || + test_bit(FLAG_IS_HOGGED, &dflags) || + test_bit(FLAG_USED_AS_IRQ, &dflags) || + test_bit(FLAG_EXPORT, &dflags) || + test_bit(FLAG_SYSFS, &dflags) || !gpiochip_line_is_valid(gc, info->offset) || - !ok_for_pinctrl) + !pinctrl_gpio_can_use_line(gc, info->offset)) info->flags |= GPIO_V2_LINE_FLAG_USED; - if (test_bit(FLAG_IS_OUT, &desc->flags)) + if (test_bit(FLAG_IS_OUT, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_OUTPUT; else info->flags |= GPIO_V2_LINE_FLAG_INPUT; - if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) + if (test_bit(FLAG_ACTIVE_LOW, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW; - if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) + if (test_bit(FLAG_OPEN_DRAIN, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; - if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) + if (test_bit(FLAG_OPEN_SOURCE, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; - if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) + if (test_bit(FLAG_BIAS_DISABLE, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; - if (test_bit(FLAG_PULL_DOWN, &desc->flags)) + if (test_bit(FLAG_PULL_DOWN, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; - if (test_bit(FLAG_PULL_UP, &desc->flags)) + if (test_bit(FLAG_PULL_UP, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; - if (test_bit(FLAG_EDGE_RISING, &desc->flags)) + if (test_bit(FLAG_EDGE_RISING, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; - if (test_bit(FLAG_EDGE_FALLING, &desc->flags)) + if (test_bit(FLAG_EDGE_FALLING, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; - if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &desc->flags)) + if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; - else if (test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags)) + else if (test_bit(FLAG_EVENT_CLOCK_HTE, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE; - - spin_unlock_irqrestore(&gpio_lock, flags); } struct gpio_chardev_data { From ede7511e7c22c9542a699ddff9f32de74e0bb972 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Thu, 21 Dec 2023 09:20:37 +0800 Subject: [PATCH 40/55] gpiolib: cdev: include overflow.h struct_size() is used to calculate struct linereq size, so explicitly include overflow.h. Signed-off-by: Kent Gibson Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 9155c54acc1e..942fe115b726 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include From b718fbfea9df4f715ddd4c61a671226fb11bb232 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Thu, 21 Dec 2023 09:20:38 +0800 Subject: [PATCH 41/55] gpiolib: cdev: allocate linereq using kvzalloc() The size of struct linereq may exceed a page, so allocate space for it using kvzalloc() instead of kzalloc() to handle the case where memory is heavily fragmented and kzalloc() cannot find a sufficient contiguous region. Signed-off-by: Kent Gibson Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 942fe115b726..5424c878627e 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1723,7 +1723,7 @@ static void linereq_free(struct linereq *lr) kfifo_free(&lr->events); kfree(lr->label); gpio_device_put(lr->gdev); - kfree(lr); + kvfree(lr); } static int linereq_release(struct inode *inode, struct file *file) @@ -1788,7 +1788,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) if (ret) return ret; - lr = kzalloc(struct_size(lr, lines, ulr.num_lines), GFP_KERNEL); + lr = kvzalloc(struct_size(lr, lines, ulr.num_lines), GFP_KERNEL); if (!lr) return -ENOMEM; lr->num_lines = ulr.num_lines; From 32d8e3b6453d0ebd4eb8c31c593f432f6fdad4dd Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Thu, 21 Dec 2023 09:20:39 +0800 Subject: [PATCH 42/55] gpiolib: cdev: replace locking wrappers for config_mutex with guards After the adoption of guard(), the locking wrappers that hold the config_mutex for linereq_set_values() and linereq_set_config() no longer add value, so combine them into the functions they wrap. Signed-off-by: Kent Gibson Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 103 +++++++++++++++--------------------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 5424c878627e..9ff2b447cc20 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1454,14 +1454,19 @@ static long linereq_get_values(struct linereq *lr, void __user *ip) return 0; } -static long linereq_set_values_unlocked(struct linereq *lr, - struct gpio_v2_line_values *lv) +static long linereq_set_values(struct linereq *lr, void __user *ip) { DECLARE_BITMAP(vals, GPIO_V2_LINES_MAX); + struct gpio_v2_line_values lv; struct gpio_desc **descs; unsigned int i, didx, num_set; int ret; + if (copy_from_user(&lv, ip, sizeof(lv))) + return -EFAULT; + + guard(mutex)(&lr->config_mutex); + /* * gpiod_set_array_value_complex() requires compacted desc and val * arrays, rather than the sparse ones in lv. @@ -1472,12 +1477,12 @@ static long linereq_set_values_unlocked(struct linereq *lr, bitmap_zero(vals, GPIO_V2_LINES_MAX); /* scan requested lines to determine the subset to be set */ for (num_set = 0, i = 0; i < lr->num_lines; i++) { - if (lv->mask & BIT_ULL(i)) { + if (lv.mask & BIT_ULL(i)) { /* setting inputs is not allowed */ if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags)) return -EPERM; /* add to compacted values */ - if (lv->bits & BIT_ULL(i)) + if (lv.bits & BIT_ULL(i)) __set_bit(num_set, vals); num_set++; /* capture desc for the num_set == 1 case */ @@ -1493,7 +1498,7 @@ static long linereq_set_values_unlocked(struct linereq *lr, if (!descs) return -ENOMEM; for (didx = 0, i = 0; i < lr->num_lines; i++) { - if (lv->mask & BIT_ULL(i)) { + if (lv.mask & BIT_ULL(i)) { descs[didx] = lr->lines[i].desc; didx++; } @@ -1507,66 +1512,15 @@ static long linereq_set_values_unlocked(struct linereq *lr, return ret; } -static long linereq_set_values(struct linereq *lr, void __user *ip) -{ - struct gpio_v2_line_values lv; - - if (copy_from_user(&lv, ip, sizeof(lv))) - return -EFAULT; - - guard(mutex)(&lr->config_mutex); - - return linereq_set_values_unlocked(lr, &lv); -} - -static long linereq_set_config_unlocked(struct linereq *lr, - struct gpio_v2_line_config *lc) +static long linereq_set_config(struct linereq *lr, void __user *ip) { + struct gpio_v2_line_config lc; struct gpio_desc *desc; struct line *line; unsigned int i; u64 flags, edflags; int ret; - for (i = 0; i < lr->num_lines; i++) { - line = &lr->lines[i]; - desc = lr->lines[i].desc; - flags = gpio_v2_line_config_flags(lc, i); - gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags); - edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS; - /* - * Lines have to be requested explicitly for input - * or output, else the line will be treated "as is". - */ - if (flags & GPIO_V2_LINE_FLAG_OUTPUT) { - int val = gpio_v2_line_config_output_value(lc, i); - - edge_detector_stop(line); - ret = gpiod_direction_output(desc, val); - if (ret) - return ret; - } else if (flags & GPIO_V2_LINE_FLAG_INPUT) { - ret = gpiod_direction_input(desc); - if (ret) - return ret; - - ret = edge_detector_update(line, lc, i, edflags); - if (ret) - return ret; - } - - WRITE_ONCE(line->edflags, edflags); - - gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG); - } - return 0; -} - -static long linereq_set_config(struct linereq *lr, void __user *ip) -{ - struct gpio_v2_line_config lc; - int ret; - if (copy_from_user(&lc, ip, sizeof(lc))) return -EFAULT; @@ -1576,7 +1530,38 @@ static long linereq_set_config(struct linereq *lr, void __user *ip) guard(mutex)(&lr->config_mutex); - return linereq_set_config_unlocked(lr, &lc); + for (i = 0; i < lr->num_lines; i++) { + line = &lr->lines[i]; + desc = lr->lines[i].desc; + flags = gpio_v2_line_config_flags(&lc, i); + gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags); + edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS; + /* + * Lines have to be requested explicitly for input + * or output, else the line will be treated "as is". + */ + if (flags & GPIO_V2_LINE_FLAG_OUTPUT) { + int val = gpio_v2_line_config_output_value(&lc, i); + + edge_detector_stop(line); + ret = gpiod_direction_output(desc, val); + if (ret) + return ret; + } else if (flags & GPIO_V2_LINE_FLAG_INPUT) { + ret = gpiod_direction_input(desc); + if (ret) + return ret; + + ret = edge_detector_update(line, &lc, i, edflags); + if (ret) + return ret; + } + + WRITE_ONCE(line->edflags, edflags); + + gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG); + } + return 0; } static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd, From 20bddcb40b2b6d6028fb4224fab4aadb7d7b2674 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Thu, 21 Dec 2023 09:20:40 +0800 Subject: [PATCH 43/55] gpiolib: cdev: replace locking wrappers for gpio_device with guards Replace the wrapping functions that inhibit removal of the gpio chip with equivalent guards. Signed-off-by: Kent Gibson Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 205 +++++++++--------------------------- 1 file changed, 47 insertions(+), 158 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 9ff2b447cc20..2a88736629ef 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -65,45 +66,6 @@ typedef long (*ioctl_fn)(struct file *, unsigned int, unsigned long); typedef ssize_t (*read_fn)(struct file *, char __user *, size_t count, loff_t *); -static __poll_t call_poll_locked(struct file *file, - struct poll_table_struct *wait, - struct gpio_device *gdev, poll_fn func) -{ - __poll_t ret; - - down_read(&gdev->sem); - ret = func(file, wait); - up_read(&gdev->sem); - - return ret; -} - -static long call_ioctl_locked(struct file *file, unsigned int cmd, - unsigned long arg, struct gpio_device *gdev, - ioctl_fn func) -{ - long ret; - - down_read(&gdev->sem); - ret = func(file, cmd, arg); - up_read(&gdev->sem); - - return ret; -} - -static ssize_t call_read_locked(struct file *file, char __user *buf, - size_t count, loff_t *f_ps, - struct gpio_device *gdev, read_fn func) -{ - ssize_t ret; - - down_read(&gdev->sem); - ret = func(file, buf, count, f_ps); - up_read(&gdev->sem); - - return ret; -} - /* * GPIO line handle management */ @@ -238,8 +200,8 @@ static long linehandle_set_config(struct linehandle_state *lh, return 0; } -static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +static long linehandle_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { struct linehandle_state *lh = file->private_data; void __user *ip = (void __user *)arg; @@ -248,6 +210,8 @@ static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned int i; int ret; + guard(rwsem_read)(&lh->gdev->sem); + if (!lh->gdev->chip) return -ENODEV; @@ -297,15 +261,6 @@ static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, } } -static long linehandle_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct linehandle_state *lh = file->private_data; - - return call_ioctl_locked(file, cmd, arg, lh->gdev, - linehandle_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long linehandle_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -1564,12 +1519,14 @@ static long linereq_set_config(struct linereq *lr, void __user *ip) return 0; } -static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +static long linereq_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { struct linereq *lr = file->private_data; void __user *ip = (void __user *)arg; + guard(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return -ENODEV; @@ -1585,15 +1542,6 @@ static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd, } } -static long linereq_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct linereq *lr = file->private_data; - - return call_ioctl_locked(file, cmd, arg, lr->gdev, - linereq_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long linereq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -1602,12 +1550,14 @@ static long linereq_ioctl_compat(struct file *file, unsigned int cmd, } #endif -static __poll_t linereq_poll_unlocked(struct file *file, - struct poll_table_struct *wait) +static __poll_t linereq_poll(struct file *file, + struct poll_table_struct *wait) { struct linereq *lr = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -1620,22 +1570,16 @@ static __poll_t linereq_poll_unlocked(struct file *file, return events; } -static __poll_t linereq_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct linereq *lr = file->private_data; - - return call_poll_locked(file, wait, lr->gdev, linereq_poll_unlocked); -} - -static ssize_t linereq_read_unlocked(struct file *file, char __user *buf, - size_t count, loff_t *f_ps) +static ssize_t linereq_read(struct file *file, char __user *buf, + size_t count, loff_t *f_ps) { struct linereq *lr = file->private_data; struct gpio_v2_line_event le; ssize_t bytes_read = 0; int ret; + guard(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return -ENODEV; @@ -1677,15 +1621,6 @@ static ssize_t linereq_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -static ssize_t linereq_read(struct file *file, char __user *buf, - size_t count, loff_t *f_ps) -{ - struct linereq *lr = file->private_data; - - return call_read_locked(file, buf, count, f_ps, lr->gdev, - linereq_read_unlocked); -} - static void linereq_free(struct linereq *lr) { struct line *line; @@ -1938,12 +1873,14 @@ struct lineevent_state { (GPIOEVENT_REQUEST_RISING_EDGE | \ GPIOEVENT_REQUEST_FALLING_EDGE) -static __poll_t lineevent_poll_unlocked(struct file *file, - struct poll_table_struct *wait) +static __poll_t lineevent_poll(struct file *file, + struct poll_table_struct *wait) { struct lineevent_state *le = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -1955,14 +1892,6 @@ static __poll_t lineevent_poll_unlocked(struct file *file, return events; } -static __poll_t lineevent_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct lineevent_state *le = file->private_data; - - return call_poll_locked(file, wait, le->gdev, lineevent_poll_unlocked); -} - static int lineevent_unregistered_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -1979,8 +1908,8 @@ struct compat_gpioeevent_data { u32 id; }; -static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, - size_t count, loff_t *f_ps) +static ssize_t lineevent_read(struct file *file, char __user *buf, + size_t count, loff_t *f_ps) { struct lineevent_state *le = file->private_data; struct gpioevent_data ge; @@ -1988,6 +1917,8 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, ssize_t ge_size; int ret; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return -ENODEV; @@ -2042,15 +1973,6 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -static ssize_t lineevent_read(struct file *file, char __user *buf, - size_t count, loff_t *f_ps) -{ - struct lineevent_state *le = file->private_data; - - return call_read_locked(file, buf, count, f_ps, le->gdev, - lineevent_read_unlocked); -} - static void lineevent_free(struct lineevent_state *le) { if (le->device_unregistered_nb.notifier_call) @@ -2071,13 +1993,15 @@ static int lineevent_release(struct inode *inode, struct file *file) return 0; } -static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +static long lineevent_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { struct lineevent_state *le = file->private_data; void __user *ip = (void __user *)arg; struct gpiohandle_data ghd; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return -ENODEV; @@ -2103,15 +2027,6 @@ static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd, return -EINVAL; } -static long lineevent_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct lineevent_state *le = file->private_data; - - return call_ioctl_locked(file, cmd, arg, le->gdev, - lineevent_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long lineevent_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -2584,12 +2499,17 @@ static int lineinfo_unwatch(struct gpio_chardev_data *cdev, void __user *ip) return 0; } -static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg) +/* + * gpio_ioctl() - ioctl handler for the GPIO chardev + */ +static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct gpio_chardev_data *cdev = file->private_data; struct gpio_device *gdev = cdev->gdev; void __user *ip = (void __user *)arg; + guard(rwsem_read)(&gdev->sem); + /* We fail any subsequent ioctl():s when the chip is gone */ if (!gdev->chip) return -ENODEV; @@ -2621,17 +2541,6 @@ static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned lo } } -/* - * gpio_ioctl() - ioctl handler for the GPIO chardev - */ -static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_ioctl_locked(file, cmd, arg, cdev->gdev, - gpio_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long gpio_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -2679,12 +2588,14 @@ static int gpio_device_unregistered_notify(struct notifier_block *nb, return NOTIFY_OK; } -static __poll_t lineinfo_watch_poll_unlocked(struct file *file, - struct poll_table_struct *pollt) +static __poll_t lineinfo_watch_poll(struct file *file, + struct poll_table_struct *pollt) { struct gpio_chardev_data *cdev = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&cdev->gdev->sem); + if (!cdev->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -2697,17 +2608,8 @@ static __poll_t lineinfo_watch_poll_unlocked(struct file *file, return events; } -static __poll_t lineinfo_watch_poll(struct file *file, - struct poll_table_struct *pollt) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_poll_locked(file, pollt, cdev->gdev, - lineinfo_watch_poll_unlocked); -} - -static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, - size_t count, loff_t *off) +static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, + size_t count, loff_t *off) { struct gpio_chardev_data *cdev = file->private_data; struct gpio_v2_line_info_changed event; @@ -2715,6 +2617,8 @@ static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, int ret; size_t event_size; + guard(rwsem_read)(&cdev->gdev->sem); + if (!cdev->gdev->chip) return -ENODEV; @@ -2777,15 +2681,6 @@ static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, - size_t count, loff_t *off) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_read_locked(file, buf, count, off, cdev->gdev, - lineinfo_watch_read_unlocked); -} - /** * gpio_chrdev_open() - open the chardev for ioctl operations * @inode: inode for this chardev @@ -2799,17 +2694,15 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) struct gpio_chardev_data *cdev; int ret = -ENOMEM; - down_read(&gdev->sem); + guard(rwsem_read)(&gdev->sem); /* Fail on open if the backing gpiochip is gone */ - if (!gdev->chip) { - ret = -ENODEV; - goto out_unlock; - } + if (!gdev->chip) + return -ENODEV; cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); if (!cdev) - goto out_unlock; + return -ENODEV; cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL); if (!cdev->watched_lines) @@ -2838,8 +2731,6 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) if (ret) goto out_unregister_device_notifier; - up_read(&gdev->sem); - return ret; out_unregister_device_notifier: @@ -2853,8 +2744,6 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) bitmap_free(cdev->watched_lines); out_free_cdev: kfree(cdev); -out_unlock: - up_read(&gdev->sem); return ret; } From 7dd1871e5049bbd40ee78ac94b1678ba5caf2486 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 21 Dec 2023 19:57:02 +0100 Subject: [PATCH 44/55] gpio: tps65219: don't use CONFIG_DEBUG_GPIO CONFIG_DEBUG_GPIO should only be used to enable debug log messages and for core GPIOLIB debugging. Don't use it to control the execution of potentially buggy code. Just put it under an always-false #if. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Acked-by: Linus Walleij --- drivers/gpio/gpio-tps65219.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 7b38aa360112..cd1f17041f8c 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -96,16 +96,16 @@ static int tps65219_gpio_change_direction(struct gpio_chip *gc, unsigned int off * Below can be used for test purpose only. */ - if (IS_ENABLED(CONFIG_DEBUG_GPIO)) { - int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, - TPS65219_GPIO0_DIR_MASK, direction); - if (ret) { - dev_err(dev, - "GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n", - direction, offset); - return ret; - } +#if 0 + int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, + TPS65219_GPIO0_DIR_MASK, direction); + if (ret) { + dev_err(dev, + "GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n", + direction, offset); + return ret; } +#endif dev_err(dev, "GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n", From 5d5dfc50e5689d5b09de4a323f84c28a6700d156 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 19 Dec 2023 21:11:02 +0100 Subject: [PATCH 45/55] gpiolib: remove extra_checks extra_checks is only used in a few places. It also depends on a non-standard DEBUG define one needs to add to the source file. The overhead of removing it should be minimal (we already use pure might_sleep() in the code anyway) so drop it. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij --- drivers/gpio/gpiolib.c | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index c9ca809b55de..837e9919bf07 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -47,19 +47,6 @@ * GPIOs can sometimes cost only an instruction or two per bit. */ - -/* When debugging, extend minimal trust to callers and platform code. - * Also emit diagnostic messages that may help initial bringup, when - * board setup or driver bugs are most common. - * - * Otherwise, minimize overhead in what may be bitbanging codepaths. - */ -#ifdef DEBUG -#define extra_checks 1 -#else -#define extra_checks 0 -#endif - /* Device and char device-related information */ static DEFINE_IDA(gpio_ida); static dev_t gpio_devt; @@ -2351,7 +2338,7 @@ void gpiod_free(struct gpio_desc *desc) return; if (!gpiod_free_commit(desc)) - WARN_ON(extra_checks); + WARN_ON(1); module_put(desc->gdev->owner); gpio_device_put(desc->gdev); @@ -3729,7 +3716,7 @@ EXPORT_SYMBOL_GPL(gpiochip_line_is_persistent); */ int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc) { - might_sleep_if(extra_checks); + might_sleep(); VALIDATE_DESC(desc); return gpiod_get_raw_value_commit(desc); } @@ -3748,7 +3735,7 @@ int gpiod_get_value_cansleep(const struct gpio_desc *desc) { int value; - might_sleep_if(extra_checks); + might_sleep(); VALIDATE_DESC(desc); value = gpiod_get_raw_value_commit(desc); if (value < 0) @@ -3779,7 +3766,7 @@ int gpiod_get_raw_array_value_cansleep(unsigned int array_size, struct gpio_array *array_info, unsigned long *value_bitmap) { - might_sleep_if(extra_checks); + might_sleep(); if (!desc_array) return -EINVAL; return gpiod_get_array_value_complex(true, true, array_size, @@ -3805,7 +3792,7 @@ int gpiod_get_array_value_cansleep(unsigned int array_size, struct gpio_array *array_info, unsigned long *value_bitmap) { - might_sleep_if(extra_checks); + might_sleep(); if (!desc_array) return -EINVAL; return gpiod_get_array_value_complex(false, true, array_size, @@ -3826,7 +3813,7 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_value_cansleep); */ void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value) { - might_sleep_if(extra_checks); + might_sleep(); VALIDATE_DESC_VOID(desc); gpiod_set_raw_value_commit(desc, value); } @@ -3844,7 +3831,7 @@ EXPORT_SYMBOL_GPL(gpiod_set_raw_value_cansleep); */ void gpiod_set_value_cansleep(struct gpio_desc *desc, int value) { - might_sleep_if(extra_checks); + might_sleep(); VALIDATE_DESC_VOID(desc); gpiod_set_value_nocheck(desc, value); } @@ -3867,7 +3854,7 @@ int gpiod_set_raw_array_value_cansleep(unsigned int array_size, struct gpio_array *array_info, unsigned long *value_bitmap) { - might_sleep_if(extra_checks); + might_sleep(); if (!desc_array) return -EINVAL; return gpiod_set_array_value_complex(true, true, array_size, desc_array, @@ -3909,7 +3896,7 @@ int gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_array *array_info, unsigned long *value_bitmap) { - might_sleep_if(extra_checks); + might_sleep(); if (!desc_array) return -EINVAL; return gpiod_set_array_value_complex(false, true, array_size, From 0338f6a6fb659f083eca7dd5967bb668d14707f8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 21 Dec 2023 10:15:46 +0100 Subject: [PATCH 46/55] gpiolib: drop tabs from local variable declarations Older code has an annoying habit of putting tabs between the type and the name of the variable. This doesn't really add to readability and newer code doesn't do it so make the entire file consistent. While at it: convert 'unsigned' to 'unsigned int'. Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko --- drivers/gpio/gpiolib.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 837e9919bf07..e21497b989a1 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1036,8 +1036,8 @@ EXPORT_SYMBOL_GPL(gpiochip_add_data_with_key); void gpiochip_remove(struct gpio_chip *gc) { struct gpio_device *gdev = gc->gpiodev; - unsigned long flags; - unsigned int i; + unsigned long flags; + unsigned int i; down_write(&gdev->sem); @@ -2173,10 +2173,10 @@ EXPORT_SYMBOL_GPL(gpiochip_remove_pin_ranges); */ static int gpiod_request_commit(struct gpio_desc *desc, const char *label) { - struct gpio_chip *gc = desc->gdev->chip; - int ret; - unsigned long flags; - unsigned offset; + struct gpio_chip *gc = desc->gdev->chip; + unsigned long flags; + unsigned int offset; + int ret; if (label) { label = kstrdup_const(label, GFP_KERNEL); @@ -2288,9 +2288,9 @@ int gpiod_request(struct gpio_desc *desc, const char *label) static bool gpiod_free_commit(struct gpio_desc *desc) { - bool ret = false; - unsigned long flags; - struct gpio_chip *gc; + struct gpio_chip *gc; + unsigned long flags; + bool ret = false; might_sleep(); @@ -2564,8 +2564,8 @@ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce) */ int gpiod_direction_input(struct gpio_desc *desc) { - struct gpio_chip *gc; - int ret = 0; + struct gpio_chip *gc; + int ret = 0; VALIDATE_DESC(desc); gc = desc->gdev->chip; @@ -2914,7 +2914,7 @@ static int gpio_chip_get_value(struct gpio_chip *gc, const struct gpio_desc *des static int gpiod_get_raw_value_commit(const struct gpio_desc *desc) { - struct gpio_chip *gc; + struct gpio_chip *gc; int value; gc = desc->gdev->chip; @@ -3209,7 +3209,7 @@ static void gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value) { - struct gpio_chip *gc; + struct gpio_chip *gc; gc = desc->gdev->chip; trace_gpio_value(desc_to_gpio(desc), 0, value); @@ -4696,13 +4696,11 @@ core_initcall(gpiolib_dev_init); static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) { - struct gpio_chip *gc = gdev->chip; - struct gpio_desc *desc; - unsigned gpio = gdev->base; - int value; - bool is_out; - bool is_irq; - bool active_low; + struct gpio_chip *gc = gdev->chip; + bool active_low, is_irq, is_out; + unsigned int gpio = gdev->base; + struct gpio_desc *desc; + int value; for_each_gpio_desc(gc, desc) { if (test_bit(FLAG_REQUESTED, &desc->flags)) { From 513246a34b8dc5d5280a40c1cbef112594220ba6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 21 Dec 2023 10:15:47 +0100 Subject: [PATCH 47/55] gpio: sysfs: drop tabs from local variable declarations Older code has an annoying habit of putting tabs between the type and the name of the variable. This doesn't really add to readability and newer code doesn't do it so make the entire file consistent. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko --- drivers/gpio/gpiolib-sysfs.c | 65 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 1e91e7395d01..2d29ae37d953 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -165,10 +165,10 @@ static irqreturn_t gpio_sysfs_irq(int irq, void *priv) /* Caller holds gpiod-data mutex. */ static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags) { - struct gpiod_data *data = dev_get_drvdata(dev); - struct gpio_desc *desc = data->desc; - unsigned long irq_flags; - int ret; + struct gpiod_data *data = dev_get_drvdata(dev); + struct gpio_desc *desc = data->desc; + unsigned long irq_flags; + int ret; data->irq = gpiod_to_irq(desc); if (data->irq < 0) @@ -259,7 +259,7 @@ static ssize_t edge_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct gpiod_data *data = dev_get_drvdata(dev); - ssize_t status = size; + ssize_t status = size; int flags; flags = sysfs_match_string(trigger_names, buf); @@ -292,10 +292,11 @@ static DEVICE_ATTR_RW(edge); /* Caller holds gpiod-data mutex. */ static int gpio_sysfs_set_active_low(struct device *dev, int value) { - struct gpiod_data *data = dev_get_drvdata(dev); - struct gpio_desc *desc = data->desc; - int status = 0; - unsigned int flags = data->irq_flags; + struct gpiod_data *data = dev_get_drvdata(dev); + unsigned int flags = data->irq_flags; + struct gpio_desc *desc = data->desc; + int status = 0; + if (!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) == !!value) return 0; @@ -331,9 +332,9 @@ static ssize_t active_low_show(struct device *dev, static ssize_t active_low_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { - struct gpiod_data *data = dev_get_drvdata(dev); - ssize_t status; - long value; + struct gpiod_data *data = dev_get_drvdata(dev); + ssize_t status; + long value; status = kstrtol(buf, 0, &value); if (status) @@ -399,7 +400,7 @@ static const struct attribute_group *gpio_groups[] = { static ssize_t base_show(struct device *dev, struct device_attribute *attr, char *buf) { - const struct gpio_chip *chip = dev_get_drvdata(dev); + const struct gpio_chip *chip = dev_get_drvdata(dev); return sysfs_emit(buf, "%d\n", chip->base); } @@ -408,7 +409,7 @@ static DEVICE_ATTR_RO(base); static ssize_t label_show(struct device *dev, struct device_attribute *attr, char *buf) { - const struct gpio_chip *chip = dev_get_drvdata(dev); + const struct gpio_chip *chip = dev_get_drvdata(dev); return sysfs_emit(buf, "%s\n", chip->label ?: ""); } @@ -417,7 +418,7 @@ static DEVICE_ATTR_RO(label); static ssize_t ngpio_show(struct device *dev, struct device_attribute *attr, char *buf) { - const struct gpio_chip *chip = dev_get_drvdata(dev); + const struct gpio_chip *chip = dev_get_drvdata(dev); return sysfs_emit(buf, "%u\n", chip->ngpio); } @@ -441,11 +442,10 @@ static ssize_t export_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t len) { - long gpio; - struct gpio_desc *desc; - int status; - struct gpio_chip *gc; - int offset; + struct gpio_desc *desc; + struct gpio_chip *gc; + int status, offset; + long gpio; status = kstrtol(buf, 0, &gpio); if (status < 0) @@ -496,9 +496,9 @@ static ssize_t unexport_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t len) { - long gpio; - struct gpio_desc *desc; - int status; + struct gpio_desc *desc; + int status; + long gpio; status = kstrtol(buf, 0, &gpio); if (status < 0) @@ -559,14 +559,13 @@ 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; - const char *ioname = NULL; - struct device *dev; - int offset; + const char *ioname = NULL; + struct gpio_device *gdev; + struct gpiod_data *data; + struct gpio_chip *chip; + unsigned long flags; + struct device *dev; + int status, offset; /* can't export until sysfs is available ... */ if (!class_is_registered(&gpio_class)) { @@ -733,9 +732,9 @@ EXPORT_SYMBOL_GPL(gpiod_unexport); int gpiochip_sysfs_register(struct gpio_device *gdev) { - struct device *dev; - struct device *parent; struct gpio_chip *chip = gdev->chip; + struct device *parent; + struct device *dev; /* * Many systems add gpio chips for SOC support very early, From f34fd6ee1be84c6e64574e9eb58f89d32c7f98a4 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Fri, 29 Dec 2023 14:07:51 +0100 Subject: [PATCH 48/55] gpio: dwapb: Use generic request, free and set_config This way GPIO will be denied on pins already claimed by other devices and basic pin configuration (pull-up, pull-down etc.) can be done through the userspace GPIO API. Signed-off-by: Emil Renner Berthing Reviewed-by: Linus Walleij Acked-by: Serge Semin Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-dwapb.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c index 8c59332429c2..798235791f70 100644 --- a/drivers/gpio/gpio-dwapb.c +++ b/drivers/gpio/gpio-dwapb.c @@ -416,11 +416,12 @@ static int dwapb_gpio_set_config(struct gpio_chip *gc, unsigned offset, { u32 debounce; - if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE) - return -ENOTSUPP; + if (pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE) { + debounce = pinconf_to_config_argument(config); + return dwapb_gpio_set_debounce(gc, offset, debounce); + } - debounce = pinconf_to_config_argument(config); - return dwapb_gpio_set_debounce(gc, offset, debounce); + return gpiochip_generic_config(gc, offset, config); } static int dwapb_convert_irqs(struct dwapb_gpio_port_irqchip *pirq, @@ -530,10 +531,14 @@ static int dwapb_gpio_add_port(struct dwapb_gpio *gpio, port->gc.fwnode = pp->fwnode; port->gc.ngpio = pp->ngpio; port->gc.base = pp->gpio_base; + port->gc.request = gpiochip_generic_request; + port->gc.free = gpiochip_generic_free; /* Only port A support debounce */ if (pp->idx == 0) port->gc.set_config = dwapb_gpio_set_config; + else + port->gc.set_config = gpiochip_generic_config; /* Only port A can provide interrupts in all configurations of the IP */ if (pp->idx == 0) From 0f57b21300c8c7d3500de995ab3018cc7ec41249 Mon Sep 17 00:00:00 2001 From: Wenhua Lin Date: Tue, 2 Jan 2024 16:28:29 +0800 Subject: [PATCH 49/55] gpio: pmic-eic-sprd: Configure the bit corresponding to the EIC through offset A bank PMIC EIC contains 16 EICs, and the operating registers are BIT0-BIT15, such as BIT0 of the register operated by EIC0. Using the one-dimensional array reg[CACHE_NR_REGS] for maintenance will cause the configuration of other EICs to be affected when operating a certain EIC. In order to solve this problem, configure the bit corresponding to the EIC through offset. Signed-off-by: Wenhua Lin Reviewed-by: Chunyan Zhang Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pmic-eic-sprd.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-pmic-eic-sprd.c b/drivers/gpio/gpio-pmic-eic-sprd.c index 01c0fd0a9d8c..d9b228bea42e 100644 --- a/drivers/gpio/gpio-pmic-eic-sprd.c +++ b/drivers/gpio/gpio-pmic-eic-sprd.c @@ -151,8 +151,8 @@ static void sprd_pmic_eic_irq_mask(struct irq_data *data) struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); u32 offset = irqd_to_hwirq(data); - pmic_eic->reg[REG_IE] = 0; - pmic_eic->reg[REG_TRIG] = 0; + pmic_eic->reg[REG_IE] &= ~BIT(offset); + pmic_eic->reg[REG_TRIG] &= ~BIT(offset); gpiochip_disable_irq(chip, offset); } @@ -165,8 +165,8 @@ static void sprd_pmic_eic_irq_unmask(struct irq_data *data) gpiochip_enable_irq(chip, offset); - pmic_eic->reg[REG_IE] = 1; - pmic_eic->reg[REG_TRIG] = 1; + pmic_eic->reg[REG_IE] |= BIT(offset); + pmic_eic->reg[REG_TRIG] |= BIT(offset); } static int sprd_pmic_eic_irq_set_type(struct irq_data *data, @@ -174,13 +174,14 @@ static int sprd_pmic_eic_irq_set_type(struct irq_data *data, { struct gpio_chip *chip = irq_data_get_irq_chip_data(data); struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + u32 offset = irqd_to_hwirq(data); switch (flow_type) { case IRQ_TYPE_LEVEL_HIGH: - pmic_eic->reg[REG_IEV] = 1; + pmic_eic->reg[REG_IEV] |= BIT(offset); break; case IRQ_TYPE_LEVEL_LOW: - pmic_eic->reg[REG_IEV] = 0; + pmic_eic->reg[REG_IEV] &= ~BIT(offset); break; case IRQ_TYPE_EDGE_RISING: case IRQ_TYPE_EDGE_FALLING: @@ -222,15 +223,15 @@ static void sprd_pmic_eic_bus_sync_unlock(struct irq_data *data) sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, 1); } else { sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, - pmic_eic->reg[REG_IEV]); + !!(pmic_eic->reg[REG_IEV] & BIT(offset))); } /* Set irq unmask */ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IE, - pmic_eic->reg[REG_IE]); + !!(pmic_eic->reg[REG_IE] & BIT(offset))); /* Generate trigger start pulse for debounce EIC */ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_TRIG, - pmic_eic->reg[REG_TRIG]); + !!(pmic_eic->reg[REG_TRIG] & BIT(offset))); mutex_unlock(&pmic_eic->buslock); } From ed062044955beb759d6f038bc7170081650b95b2 Mon Sep 17 00:00:00 2001 From: Tzuyi Chang Date: Thu, 28 Dec 2023 18:47:59 +0800 Subject: [PATCH 50/55] dt-bindings: gpio: realtek: Add realtek,rtd-gpio Add the device tree bindings for the Realtek DHC(Digital Home Center) RTD SoCs GPIO controllers. Signed-off-by: Tzuyi Chang Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/realtek,rtd-gpio.yaml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/realtek,rtd-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/realtek,rtd-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,rtd-gpio.yaml new file mode 100644 index 000000000000..dd768db37a98 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/realtek,rtd-gpio.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2023 Realtek Semiconductor Corporation +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/realtek,rtd-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek DHC GPIO controller + +maintainers: + - Tzuyi Chang + +description: + The GPIO controller is designed for the Realtek DHC (Digital Home Center) + RTD series SoC family, which are high-definition media processor SoCs. + +properties: + compatible: + enum: + - realtek,rtd1295-misc-gpio + - realtek,rtd1295-iso-gpio + - realtek,rtd1315e-iso-gpio + - realtek,rtd1319-iso-gpio + - realtek,rtd1319d-iso-gpio + - realtek,rtd1395-iso-gpio + - realtek,rtd1619-iso-gpio + - realtek,rtd1619b-iso-gpio + + reg: + items: + - description: GPIO controller registers + - description: GPIO interrupt registers + + interrupts: + items: + - description: Interrupt number of the assert GPIO interrupt, which is + triggered when there is a rising edge. + - description: Interrupt number of the deassert GPIO interrupt, which is + triggered when there is a falling edge. + + gpio-ranges: true + + gpio-controller: true + + "#gpio-cells": + const: 2 + +required: + - compatible + - reg + - interrupts + - gpio-ranges + - gpio-controller + - "#gpio-cells" + +additionalProperties: false + +examples: + - | + gpio@100 { + compatible = "realtek,rtd1319d-iso-gpio"; + reg = <0x100 0x100>, + <0x0 0xb0>; + interrupt-parent = <&iso_irq_mux>; + interrupts = <19>, <20>; + gpio-ranges = <&pinctrl 0 0 82>; + gpio-controller; + #gpio-cells = <2>; + }; From eee636bff0dcb52c39300dd88ff7e8ecaff4492a Mon Sep 17 00:00:00 2001 From: Tzuyi Chang Date: Thu, 28 Dec 2023 18:48:00 +0800 Subject: [PATCH 51/55] gpio: rtd: Add support for Realtek DHC(Digital Home Center) RTD SoCs This driver enables configuration of GPIO direction, GPIO values, GPIO debounce settings and handles GPIO interrupts. Signed-off-by: Tzuyi Chang Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 13 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-rtd.c | 604 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 618 insertions(+) create mode 100644 drivers/gpio/gpio-rtd.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b3a133ed31ee..4c6bae8dc789 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -553,6 +553,19 @@ config GPIO_ROCKCHIP help Say yes here to support GPIO on Rockchip SoCs. +config GPIO_RTD + tristate "Realtek DHC GPIO support" + depends on ARCH_REALTEK + default y + select GPIOLIB_IRQCHIP + help + This option enables support for GPIOs found on Realtek DHC(Digital + Home Center) SoCs family, including RTD1295, RTD1315E, RTD1319, + RTD1319D, RTD1395, RTD1619 and RTD1619B. + + Say yes here to support GPIO functionality and GPIO interrupt on + Realtek DHC SoCs. + config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" depends on MFD_SYSCON diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index eb73b5d633eb..16bb40717e87 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -137,6 +137,7 @@ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o obj-$(CONFIG_GPIO_REG) += gpio-reg.o obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.o +obj-$(CONFIG_GPIO_RTD) += gpio-rtd.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o diff --git a/drivers/gpio/gpio-rtd.c b/drivers/gpio/gpio-rtd.c new file mode 100644 index 000000000000..a7939bd0aa56 --- /dev/null +++ b/drivers/gpio/gpio-rtd.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Realtek DHC gpio driver + * + * Copyright (c) 2023 Realtek Semiconductor Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RTD_GPIO_DEBOUNCE_1US 0 +#define RTD_GPIO_DEBOUNCE_10US 1 +#define RTD_GPIO_DEBOUNCE_100US 2 +#define RTD_GPIO_DEBOUNCE_1MS 3 +#define RTD_GPIO_DEBOUNCE_10MS 4 +#define RTD_GPIO_DEBOUNCE_20MS 5 +#define RTD_GPIO_DEBOUNCE_30MS 6 + +/** + * struct rtd_gpio_info - Specific GPIO register information + * @name: GPIO device name + * @gpio_base: GPIO base number + * @num_gpios: The number of GPIOs + * @dir_offset: Offset for GPIO direction registers + * @dato_offset: Offset for GPIO data output registers + * @dati_offset: Offset for GPIO data input registers + * @ie_offset: Offset for GPIO interrupt enable registers + * @dp_offset: Offset for GPIO detection polarity registers + * @gpa_offset: Offset for GPIO assert interrupt status registers + * @gpda_offset: Offset for GPIO deassert interrupt status registers + * @deb_offset: Offset for GPIO debounce registers + * @deb_val: Register values representing the GPIO debounce time + * @get_deb_setval: Used to get the corresponding value for setting the debounce register + */ +struct rtd_gpio_info { + const char *name; + unsigned int gpio_base; + unsigned int num_gpios; + u8 *dir_offset; + u8 *dato_offset; + u8 *dati_offset; + u8 *ie_offset; + u8 *dp_offset; + u8 *gpa_offset; + u8 *gpda_offset; + u8 *deb_offset; + u8 *deb_val; + u8 (*get_deb_setval)(const struct rtd_gpio_info *info, + unsigned int offset, u8 deb_index, + u8 *reg_offset, u8 *shift); +}; + +struct rtd_gpio { + struct gpio_chip gpio_chip; + const struct rtd_gpio_info *info; + void __iomem *base; + void __iomem *irq_base; + unsigned int irqs[2]; + raw_spinlock_t lock; +}; + +static u8 rtd_gpio_get_deb_setval(const struct rtd_gpio_info *info, unsigned int offset, + u8 deb_index, u8 *reg_offset, u8 *shift) +{ + *reg_offset = info->deb_offset[offset / 8]; + *shift = (offset % 8) * 4; + return info->deb_val[deb_index]; +} + +static u8 rtd1295_misc_gpio_get_deb_setval(const struct rtd_gpio_info *info, unsigned int offset, + u8 deb_index, u8 *reg_offset, u8 *shift) +{ + *reg_offset = info->deb_offset[0]; + *shift = (offset % 8) * 4; + return info->deb_val[deb_index]; +} + +static u8 rtd1295_iso_gpio_get_deb_setval(const struct rtd_gpio_info *info, unsigned int offset, + u8 deb_index, u8 *reg_offset, u8 *shift) +{ + *reg_offset = info->deb_offset[0]; + *shift = 0; + return info->deb_val[deb_index]; +} + +static const struct rtd_gpio_info rtd_iso_gpio_info = { + .name = "rtd_iso_gpio", + .gpio_base = 0, + .num_gpios = 82, + .dir_offset = (u8 []){ 0x0, 0x18, 0x2c }, + .dato_offset = (u8 []){ 0x4, 0x1c, 0x30 }, + .dati_offset = (u8 []){ 0x8, 0x20, 0x34 }, + .ie_offset = (u8 []){ 0xc, 0x24, 0x38 }, + .dp_offset = (u8 []){ 0x10, 0x28, 0x3c }, + .gpa_offset = (u8 []){ 0x8, 0xe0, 0x90 }, + .gpda_offset = (u8 []){ 0xc, 0xe4, 0x94 }, + .deb_offset = (u8 []){ 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c, + 0x60, 0x64, 0x68, 0x6c }, + .deb_val = (u8 []){ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }, + .get_deb_setval = rtd_gpio_get_deb_setval, +}; + +static const struct rtd_gpio_info rtd1619_iso_gpio_info = { + .name = "rtd1619_iso_gpio", + .gpio_base = 0, + .num_gpios = 86, + .dir_offset = (u8 []){ 0x0, 0x18, 0x2c }, + .dato_offset = (u8 []){ 0x4, 0x1c, 0x30 }, + .dati_offset = (u8 []){ 0x8, 0x20, 0x34 }, + .ie_offset = (u8 []){ 0xc, 0x24, 0x38 }, + .dp_offset = (u8 []){ 0x10, 0x28, 0x3c }, + .gpa_offset = (u8 []){ 0x8, 0xe0, 0x90 }, + .gpda_offset = (u8 []){ 0xc, 0xe4, 0x94 }, + .deb_offset = (u8 []){ 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c, + 0x60, 0x64, 0x68, 0x6c }, + .deb_val = (u8 []){ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }, + .get_deb_setval = rtd_gpio_get_deb_setval, +}; + +static const struct rtd_gpio_info rtd1395_iso_gpio_info = { + .name = "rtd1395_iso_gpio", + .gpio_base = 0, + .num_gpios = 57, + .dir_offset = (u8 []){ 0x0, 0x18 }, + .dato_offset = (u8 []){ 0x4, 0x1c }, + .dati_offset = (u8 []){ 0x8, 0x20 }, + .ie_offset = (u8 []){ 0xc, 0x24 }, + .dp_offset = (u8 []){ 0x10, 0x28 }, + .gpa_offset = (u8 []){ 0x8, 0xe0 }, + .gpda_offset = (u8 []){ 0xc, 0xe4 }, + .deb_offset = (u8 []){ 0x30, 0x34, 0x38, 0x3c, 0x40, 0x44, 0x48, 0x4c }, + .deb_val = (u8 []){ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }, + .get_deb_setval = rtd_gpio_get_deb_setval, +}; + +static const struct rtd_gpio_info rtd1295_misc_gpio_info = { + .name = "rtd1295_misc_gpio", + .gpio_base = 0, + .num_gpios = 101, + .dir_offset = (u8 []){ 0x0, 0x4, 0x8, 0xc }, + .dato_offset = (u8 []){ 0x10, 0x14, 0x18, 0x1c }, + .dati_offset = (u8 []){ 0x20, 0x24, 0x28, 0x2c }, + .ie_offset = (u8 []){ 0x30, 0x34, 0x38, 0x3c }, + .dp_offset = (u8 []){ 0x40, 0x44, 0x48, 0x4c }, + .gpa_offset = (u8 []){ 0x40, 0x44, 0xa4, 0xb8 }, + .gpda_offset = (u8 []){ 0x54, 0x58, 0xa8, 0xbc}, + .deb_offset = (u8 []){ 0x50 }, + .deb_val = (u8 []){ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }, + .get_deb_setval = rtd1295_misc_gpio_get_deb_setval, +}; + +static const struct rtd_gpio_info rtd1295_iso_gpio_info = { + .name = "rtd1295_iso_gpio", + .gpio_base = 101, + .num_gpios = 35, + .dir_offset = (u8 []){ 0x0, 0x18 }, + .dato_offset = (u8 []){ 0x4, 0x1c }, + .dati_offset = (u8 []){ 0x8, 0x20 }, + .ie_offset = (u8 []){ 0xc, 0x24 }, + .dp_offset = (u8 []){ 0x10, 0x28 }, + .gpa_offset = (u8 []){ 0x8, 0xe0 }, + .gpda_offset = (u8 []){ 0xc, 0xe4 }, + .deb_offset = (u8 []){ 0x14 }, + .deb_val = (u8 []){ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }, + .get_deb_setval = rtd1295_iso_gpio_get_deb_setval, +}; + +static int rtd_gpio_dir_offset(struct rtd_gpio *data, unsigned int offset) +{ + return data->info->dir_offset[offset / 32]; +} + +static int rtd_gpio_dato_offset(struct rtd_gpio *data, unsigned int offset) +{ + return data->info->dato_offset[offset / 32]; +} + +static int rtd_gpio_dati_offset(struct rtd_gpio *data, unsigned int offset) +{ + return data->info->dati_offset[offset / 32]; +} + +static int rtd_gpio_ie_offset(struct rtd_gpio *data, unsigned int offset) +{ + return data->info->ie_offset[offset / 32]; +} + +static int rtd_gpio_dp_offset(struct rtd_gpio *data, unsigned int offset) +{ + return data->info->dp_offset[offset / 32]; +} + + +static int rtd_gpio_gpa_offset(struct rtd_gpio *data, unsigned int offset) +{ + /* Each GPIO assert interrupt status register contains 31 GPIOs. */ + return data->info->gpa_offset[offset / 31]; +} + +static int rtd_gpio_gpda_offset(struct rtd_gpio *data, unsigned int offset) +{ + /* Each GPIO deassert interrupt status register contains 31 GPIOs. */ + return data->info->gpda_offset[offset / 31]; +} + +static int rtd_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset, + unsigned int debounce) +{ + struct rtd_gpio *data = gpiochip_get_data(chip); + u8 deb_val, deb_index, reg_offset, shift; + unsigned int write_en; + u32 val; + + switch (debounce) { + case 1: + deb_index = RTD_GPIO_DEBOUNCE_1US; + break; + case 10: + deb_index = RTD_GPIO_DEBOUNCE_10US; + break; + case 100: + deb_index = RTD_GPIO_DEBOUNCE_100US; + break; + case 1000: + deb_index = RTD_GPIO_DEBOUNCE_1MS; + break; + case 10000: + deb_index = RTD_GPIO_DEBOUNCE_10MS; + break; + case 20000: + deb_index = RTD_GPIO_DEBOUNCE_20MS; + break; + case 30000: + deb_index = RTD_GPIO_DEBOUNCE_30MS; + break; + default: + return -ENOTSUPP; + } + + deb_val = data->info->get_deb_setval(data->info, offset, deb_index, ®_offset, &shift); + write_en = BIT(shift + 3); + val = (deb_val << shift) | write_en; + + guard(raw_spinlock_irqsave)(&data->lock); + writel_relaxed(val, data->base + reg_offset); + + return 0; +} + +static int rtd_gpio_set_config(struct gpio_chip *chip, unsigned int offset, + unsigned long config) +{ + int debounce; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_BIAS_DISABLE: + case PIN_CONFIG_BIAS_PULL_UP: + case PIN_CONFIG_BIAS_PULL_DOWN: + return gpiochip_generic_config(chip, offset, config); + case PIN_CONFIG_INPUT_DEBOUNCE: + debounce = pinconf_to_config_argument(config); + return rtd_gpio_set_debounce(chip, offset, debounce); + default: + return -ENOTSUPP; + } +} + +static void rtd_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct rtd_gpio *data = gpiochip_get_data(chip); + u32 mask = BIT(offset % 32); + int dato_reg_offset; + u32 val; + + dato_reg_offset = rtd_gpio_dato_offset(data, offset); + + guard(raw_spinlock_irqsave)(&data->lock); + + val = readl_relaxed(data->base + dato_reg_offset); + if (value) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, data->base + dato_reg_offset); +} + +static int rtd_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct rtd_gpio *data = gpiochip_get_data(chip); + int dato_reg_offset = rtd_gpio_dato_offset(data, offset); + int dati_reg_offset = rtd_gpio_dati_offset(data, offset); + int dir_reg_offset = rtd_gpio_dir_offset(data, offset); + int dat_reg_offset; + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = readl_relaxed(data->base + dir_reg_offset); + dat_reg_offset = (val & BIT(offset % 32)) ? dato_reg_offset : dati_reg_offset; + val = readl_relaxed(data->base + dat_reg_offset); + + return !!(val & BIT(offset % 32)); +} + +static int rtd_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct rtd_gpio *data = gpiochip_get_data(chip); + int reg_offset; + u32 val; + + reg_offset = rtd_gpio_dir_offset(data, offset); + val = readl_relaxed(data->base + reg_offset); + if (val & BIT(offset % 32)) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + +static int rtd_gpio_set_direction(struct gpio_chip *chip, unsigned int offset, bool out) +{ + struct rtd_gpio *data = gpiochip_get_data(chip); + u32 mask = BIT(offset % 32); + int reg_offset; + u32 val; + + reg_offset = rtd_gpio_dir_offset(data, offset); + + guard(raw_spinlock_irqsave)(&data->lock); + + val = readl_relaxed(data->base + reg_offset); + if (out) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, data->base + reg_offset); + + return 0; +} + +static int rtd_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + return rtd_gpio_set_direction(chip, offset, false); +} + +static int rtd_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) +{ + rtd_gpio_set(chip, offset, value); + + return rtd_gpio_set_direction(chip, offset, true); +} + +static bool rtd_gpio_check_ie(struct rtd_gpio *data, int irq) +{ + int mask = BIT(irq % 32); + int ie_reg_offset; + u32 enable; + + ie_reg_offset = rtd_gpio_ie_offset(data, irq); + enable = readl_relaxed(data->base + ie_reg_offset); + + return enable & mask; +} + +static void rtd_gpio_irq_handle(struct irq_desc *desc) +{ + int (*get_reg_offset)(struct rtd_gpio *gpio, unsigned int offset); + struct rtd_gpio *data = irq_desc_get_handler_data(desc); + struct irq_domain *domain = data->gpio_chip.irq.domain; + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int irq = irq_desc_get_irq(desc); + unsigned long status; + int reg_offset, i, j; + unsigned int hwirq; + + if (irq == data->irqs[0]) + get_reg_offset = &rtd_gpio_gpa_offset; + else if (irq == data->irqs[1]) + get_reg_offset = &rtd_gpio_gpda_offset; + + chained_irq_enter(chip, desc); + + /* Each GPIO interrupt status register contains 31 GPIOs. */ + for (i = 0; i < data->info->num_gpios; i += 31) { + reg_offset = get_reg_offset(data, i); + + /* + * Bit 0 is the write_en bit, bit 0 to 31 corresponds to 31 GPIOs. + * When bit 0 is set to 0, write 1 to the other bits to clear the status. + * When bit 0 is set to 1, write 1 to the other bits to set the status. + */ + status = readl_relaxed(data->irq_base + reg_offset); + status &= ~BIT(0); + writel_relaxed(status, data->irq_base + reg_offset); + + for_each_set_bit(j, &status, 32) { + hwirq = i + j - 1; + if (rtd_gpio_check_ie(data, hwirq)) { + int girq = irq_find_mapping(domain, hwirq); + u32 irq_type = irq_get_trigger_type(girq); + + if ((irq == data->irqs[1]) && (irq_type != IRQ_TYPE_EDGE_BOTH)) + break; + generic_handle_domain_irq(domain, hwirq); + } + } + } + + chained_irq_exit(chip, desc); +} + +static void rtd_gpio_enable_irq(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + /* Bit 0 is write_en and bit 1 to 31 is correspond to 31 GPIOs. */ + u32 clr_mask = BIT(hwirq % 31) << 1; + + u32 ie_mask = BIT(hwirq % 32); + int gpda_reg_offset; + int gpa_reg_offset; + int ie_reg_offset; + u32 val; + + ie_reg_offset = rtd_gpio_ie_offset(data, hwirq); + gpa_reg_offset = rtd_gpio_gpa_offset(data, hwirq); + gpda_reg_offset = rtd_gpio_gpda_offset(data, hwirq); + + gpiochip_enable_irq(gc, hwirq); + + guard(raw_spinlock_irqsave)(&data->lock); + + writel_relaxed(clr_mask, data->irq_base + gpa_reg_offset); + writel_relaxed(clr_mask, data->irq_base + gpda_reg_offset); + + val = readl_relaxed(data->base + ie_reg_offset); + val |= ie_mask; + writel_relaxed(val, data->base + ie_reg_offset); +} + +static void rtd_gpio_disable_irq(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 ie_mask = BIT(hwirq % 32); + int ie_reg_offset; + u32 val; + + ie_reg_offset = rtd_gpio_ie_offset(data, hwirq); + + scoped_guard(raw_spinlock_irqsave, &data->lock) { + val = readl_relaxed(data->base + ie_reg_offset); + val &= ~ie_mask; + writel_relaxed(val, data->base + ie_reg_offset); + } + + gpiochip_disable_irq(gc, hwirq); +} + +static int rtd_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 mask = BIT(hwirq % 32); + int dp_reg_offset; + bool polarity; + u32 val; + + dp_reg_offset = rtd_gpio_dp_offset(data, hwirq); + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + polarity = 1; + break; + + case IRQ_TYPE_EDGE_FALLING: + polarity = 0; + break; + + case IRQ_TYPE_EDGE_BOTH: + polarity = 1; + break; + + default: + return -EINVAL; + } + + scoped_guard(raw_spinlock_irqsave, &data->lock) { + val = readl_relaxed(data->base + dp_reg_offset); + if (polarity) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, data->base + dp_reg_offset); + } + + irq_set_handler_locked(d, handle_simple_irq); + + return 0; +} + +static const struct irq_chip rtd_gpio_irq_chip = { + .name = "rtd-gpio", + .irq_enable = rtd_gpio_enable_irq, + .irq_disable = rtd_gpio_disable_irq, + .irq_set_type = rtd_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, +}; + +static int rtd_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_irq_chip *irq_chip; + struct rtd_gpio *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->irqs[0] = platform_get_irq(pdev, 0); + if (data->irqs[0] < 0) + return data->irqs[0]; + + data->irqs[1] = platform_get_irq(pdev, 1); + if (data->irqs[1] < 0) + return data->irqs[1]; + + data->info = device_get_match_data(dev); + if (!data->info) + return -EINVAL; + + raw_spin_lock_init(&data->lock); + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->irq_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(data->irq_base)) + return PTR_ERR(data->irq_base); + + data->gpio_chip.label = dev_name(dev); + data->gpio_chip.base = -1; + data->gpio_chip.ngpio = data->info->num_gpios; + data->gpio_chip.request = gpiochip_generic_request; + data->gpio_chip.free = gpiochip_generic_free; + data->gpio_chip.get_direction = rtd_gpio_get_direction; + data->gpio_chip.direction_input = rtd_gpio_direction_input; + data->gpio_chip.direction_output = rtd_gpio_direction_output; + data->gpio_chip.set = rtd_gpio_set; + data->gpio_chip.get = rtd_gpio_get; + data->gpio_chip.set_config = rtd_gpio_set_config; + data->gpio_chip.parent = dev; + + irq_chip = &data->gpio_chip.irq; + irq_chip->handler = handle_bad_irq; + irq_chip->default_type = IRQ_TYPE_NONE; + irq_chip->parent_handler = rtd_gpio_irq_handle; + irq_chip->parent_handler_data = data; + irq_chip->num_parents = 2; + irq_chip->parents = data->irqs; + + gpio_irq_chip_set_chip(irq_chip, &rtd_gpio_irq_chip); + + return devm_gpiochip_add_data(dev, &data->gpio_chip, data); +} + +static const struct of_device_id rtd_gpio_of_matches[] = { + { .compatible = "realtek,rtd1295-misc-gpio", .data = &rtd1295_misc_gpio_info }, + { .compatible = "realtek,rtd1295-iso-gpio", .data = &rtd1295_iso_gpio_info }, + { .compatible = "realtek,rtd1395-iso-gpio", .data = &rtd1395_iso_gpio_info }, + { .compatible = "realtek,rtd1619-iso-gpio", .data = &rtd1619_iso_gpio_info }, + { .compatible = "realtek,rtd1319-iso-gpio", .data = &rtd_iso_gpio_info }, + { .compatible = "realtek,rtd1619b-iso-gpio", .data = &rtd_iso_gpio_info }, + { .compatible = "realtek,rtd1319d-iso-gpio", .data = &rtd_iso_gpio_info }, + { .compatible = "realtek,rtd1315e-iso-gpio", .data = &rtd_iso_gpio_info }, + { } +}; +MODULE_DEVICE_TABLE(of, rtd_gpio_of_matches); + +static struct platform_driver rtd_gpio_platform_driver = { + .driver = { + .name = "gpio-rtd", + .of_match_table = rtd_gpio_of_matches, + }, + .probe = rtd_gpio_probe, +}; +module_platform_driver(rtd_gpio_platform_driver); + +MODULE_DESCRIPTION("Realtek DHC SoC gpio driver"); +MODULE_LICENSE("GPL v2"); From a0e4375cb07d888cc86ef4aa7266a3baf1cfcc58 Mon Sep 17 00:00:00 2001 From: Jim Liu Date: Fri, 29 Dec 2023 15:45:06 +0800 Subject: [PATCH 52/55] dt-bindings: gpio: add NPCM sgpio driver bindings Add dt-bindings document for the Nuvoton NPCM7xx sgpio driver Signed-off-by: Jim Liu Reviewed-by: Linus Walleij Reviewed-by: Rob Herring Reviewed-by: Paul Menzel Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/nuvoton,sgpio.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/nuvoton,sgpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/nuvoton,sgpio.yaml b/Documentation/devicetree/bindings/gpio/nuvoton,sgpio.yaml new file mode 100644 index 000000000000..9e32e54aeb24 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/nuvoton,sgpio.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/nuvoton,sgpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton SGPIO controller + +maintainers: + - Jim LIU + +description: | + This SGPIO controller is for NUVOTON NPCM7xx and NPCM8xx SoC and detailed + information is in the NPCM7XX/8XX SERIAL I/O EXPANSION INTERFACE section. + Nuvoton NPCM7xx SGPIO module is combines a serial to parallel IC (HC595) + and a parallel to serial IC (HC165). + Clock is a division of the APB3 clock. + This interface has 4 pins (D_out , D_in, S_CLK, LDSH). + NPCM7xx/NPCM8xx have two sgpio modules. Each module can support up + to 64 output pins, and up to 64 input pins, the pin is only for GPI or GPO. + GPIO pins can be programmed to support the following options + - Support interrupt option for each input port and various interrupt + sensitivity options (level-high, level-low, edge-high, edge-low) + - ngpios is number of nuvoton,input-ngpios GPIO lines and nuvoton,output-ngpios GPIO lines. + nuvoton,input-ngpios GPIO lines is only for GPI. + nuvoton,output-ngpios GPIO lines is only for GPO. + +properties: + compatible: + enum: + - nuvoton,npcm750-sgpio + - nuvoton,npcm845-sgpio + + reg: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + nuvoton,input-ngpios: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The numbers of GPIO's exposed. GPIO lines are only for GPI. + minimum: 0 + maximum: 64 + + nuvoton,output-ngpios: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The numbers of GPIO's exposed. GPIO lines are only for GPO. + minimum: 0 + maximum: 64 + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + - interrupts + - nuvoton,input-ngpios + - nuvoton,output-ngpios + - clocks + +additionalProperties: false + +examples: + - | + #include + #include + gpio8: gpio@101000 { + compatible = "nuvoton,npcm750-sgpio"; + reg = <0x101000 0x200>; + clocks = <&clk NPCM7XX_CLK_APB3>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + nuvoton,input-ngpios = <64>; + nuvoton,output-ngpios = <64>; + }; From c4f8457d17ce590c71aef53d75d49c313eb72cbc Mon Sep 17 00:00:00 2001 From: Jim Liu Date: Fri, 29 Dec 2023 15:45:08 +0800 Subject: [PATCH 53/55] gpio: nuvoton: Add Nuvoton NPCM sgpio driver Add Nuvoton BMC NPCM7xx/NPCM8xx sgpio driver support. Nuvoton NPCM SGPIO module is combine serial to parallel IC (HC595) and parallel to serial IC (HC165), and use APB3 clock to control it. This interface has 4 pins (D_out , D_in, S_CLK, LDSH). BMC can use this driver to increase 64 GPI pins and 64 GPO pins to use. Signed-off-by: Jim Liu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-npcm-sgpio.c | 619 +++++++++++++++++++++++++++++++++ 3 files changed, 627 insertions(+) create mode 100644 drivers/gpio/gpio-npcm-sgpio.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4c6bae8dc789..1301cec94f12 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -478,6 +478,13 @@ config GPIO_MXS select GPIO_GENERIC select GENERIC_IRQ_CHIP +config GPIO_NPCM_SGPIO + bool "Nuvoton SGPIO support" + depends on ARCH_NPCM || COMPILE_TEST + select GPIOLIB_IRQCHIP + help + Say Y here to support Nuvoton NPCM7XX/NPCM8XX SGPIO functionality. + config GPIO_OCTEON tristate "Cavium OCTEON GPIO" depends on CAVIUM_OCTEON_SOC diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 16bb40717e87..9e40af196aae 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -116,6 +116,7 @@ obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o +obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o diff --git a/drivers/gpio/gpio-npcm-sgpio.c b/drivers/gpio/gpio-npcm-sgpio.c new file mode 100644 index 000000000000..d31788b43abc --- /dev/null +++ b/drivers/gpio/gpio-npcm-sgpio.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NPCM Serial GPIO Driver + * + * Copyright (C) 2021 Nuvoton Technologies + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_NR_HW_SGPIO 64 + +#define NPCM_IOXCFG1 0x2A +#define NPCM_IOXCFG1_SFT_CLK GENMASK(3, 0) +#define NPCM_IOXCFG1_SCLK_POL BIT(4) +#define NPCM_IOXCFG1_LDSH_POL BIT(5) + +#define NPCM_IOXCTS 0x28 +#define NPCM_IOXCTS_IOXIF_EN BIT(7) +#define NPCM_IOXCTS_RD_MODE GENMASK(2, 1) +#define NPCM_IOXCTS_RD_MODE_PERIODIC BIT(2) + +#define NPCM_IOXCFG2 0x2B +#define NPCM_IOXCFG2_PORT GENMASK(3, 0) + +#define NPCM_IXOEVCFG_MASK GENMASK(1, 0) +#define NPCM_IXOEVCFG_FALLING BIT(1) +#define NPCM_IXOEVCFG_RISING BIT(0) +#define NPCM_IXOEVCFG_BOTH (NPCM_IXOEVCFG_FALLING | NPCM_IXOEVCFG_RISING) + +#define NPCM_CLK_MHZ (8 * HZ_PER_MHZ) +#define NPCM_750_OPT 6 +#define NPCM_845_OPT 5 + +#define GPIO_BANK(x) ((x) / 8) +#define GPIO_BIT(x) ((x) % 8) + +/* + * Select the frequency of shift clock. + * The shift clock is a division of the APB clock. + */ +struct npcm_clk_cfg { + unsigned int *sft_clk; + unsigned int *clk_sel; + unsigned int cfg_opt; +}; + +struct npcm_sgpio { + struct gpio_chip chip; + struct clk *pclk; + struct irq_chip intc; + raw_spinlock_t lock; + + void __iomem *base; + int irq; + u8 nin_sgpio; + u8 nout_sgpio; + u8 in_port; + u8 out_port; + u8 int_type[MAX_NR_HW_SGPIO]; +}; + +struct npcm_sgpio_bank { + u8 rdata_reg; + u8 wdata_reg; + u8 event_config; + u8 event_status; +}; + +enum npcm_sgpio_reg { + READ_DATA, + WRITE_DATA, + EVENT_CFG, + EVENT_STS, +}; + +static const struct npcm_sgpio_bank npcm_sgpio_banks[] = { + { + .wdata_reg = 0x00, + .rdata_reg = 0x08, + .event_config = 0x10, + .event_status = 0x20, + }, + { + .wdata_reg = 0x01, + .rdata_reg = 0x09, + .event_config = 0x12, + .event_status = 0x21, + }, + { + .wdata_reg = 0x02, + .rdata_reg = 0x0a, + .event_config = 0x14, + .event_status = 0x22, + }, + { + .wdata_reg = 0x03, + .rdata_reg = 0x0b, + .event_config = 0x16, + .event_status = 0x23, + }, + { + .wdata_reg = 0x04, + .rdata_reg = 0x0c, + .event_config = 0x18, + .event_status = 0x24, + }, + { + .wdata_reg = 0x05, + .rdata_reg = 0x0d, + .event_config = 0x1a, + .event_status = 0x25, + }, + { + .wdata_reg = 0x06, + .rdata_reg = 0x0e, + .event_config = 0x1c, + .event_status = 0x26, + }, + { + .wdata_reg = 0x07, + .rdata_reg = 0x0f, + .event_config = 0x1e, + .event_status = 0x27, + }, +}; + +static void __iomem *bank_reg(struct npcm_sgpio *gpio, + const struct npcm_sgpio_bank *bank, + const enum npcm_sgpio_reg reg) +{ + switch (reg) { + case READ_DATA: + return gpio->base + bank->rdata_reg; + case WRITE_DATA: + return gpio->base + bank->wdata_reg; + case EVENT_CFG: + return gpio->base + bank->event_config; + case EVENT_STS: + return gpio->base + bank->event_status; + default: + /* actually if code runs to here, it's an error case */ + dev_WARN(gpio->chip.parent, "Getting here is an error condition"); + return NULL; + } +} + +static const struct npcm_sgpio_bank *offset_to_bank(unsigned int offset) +{ + unsigned int bank = GPIO_BANK(offset); + + return &npcm_sgpio_banks[bank]; +} + +static void npcm_sgpio_irqd_to_data(struct irq_data *d, + struct npcm_sgpio **gpio, + const struct npcm_sgpio_bank **bank, + u8 *bit, unsigned int *offset) +{ + struct npcm_sgpio *internal; + + *offset = irqd_to_hwirq(d); + internal = irq_data_get_irq_chip_data(d); + + *gpio = internal; + *offset -= internal->nout_sgpio; + *bank = offset_to_bank(*offset); + *bit = GPIO_BIT(*offset); +} + +static int npcm_sgpio_init_port(struct npcm_sgpio *gpio) +{ + u8 in_port, out_port, set_port, reg; + + in_port = GPIO_BANK(gpio->nin_sgpio); + if (GPIO_BIT(gpio->nin_sgpio) > 0) + in_port += 1; + + out_port = GPIO_BANK(gpio->nout_sgpio); + if (GPIO_BIT(gpio->nout_sgpio) > 0) + out_port += 1; + + gpio->in_port = in_port; + gpio->out_port = out_port; + set_port = (out_port & NPCM_IOXCFG2_PORT) << 4 | + (in_port & NPCM_IOXCFG2_PORT); + iowrite8(set_port, gpio->base + NPCM_IOXCFG2); + + reg = ioread8(gpio->base + NPCM_IOXCFG2); + + return reg == set_port ? 0 : -EINVAL; + +} + +static int npcm_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset) +{ + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + + return offset < gpio->nout_sgpio ? -EINVAL : 0; + +} + +static int npcm_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val) +{ + gc->set(gc, offset, val); + + return 0; +} + +static int npcm_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + + if (offset < gpio->nout_sgpio) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + +static void npcm_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val) +{ + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + const struct npcm_sgpio_bank *bank = offset_to_bank(offset); + void __iomem *addr; + u8 reg = 0; + + addr = bank_reg(gpio, bank, WRITE_DATA); + reg = ioread8(addr); + + if (val) + reg |= BIT(GPIO_BIT(offset)); + else + reg &= ~BIT(GPIO_BIT(offset)); + + iowrite8(reg, addr); +} + +static int npcm_sgpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + const struct npcm_sgpio_bank *bank; + void __iomem *addr; + u8 reg; + + if (offset < gpio->nout_sgpio) { + bank = offset_to_bank(offset); + addr = bank_reg(gpio, bank, WRITE_DATA); + } else { + offset -= gpio->nout_sgpio; + bank = offset_to_bank(offset); + addr = bank_reg(gpio, bank, READ_DATA); + } + + reg = ioread8(addr); + + return !!(reg & BIT(GPIO_BIT(offset))); +} + +static void npcm_sgpio_setup_enable(struct npcm_sgpio *gpio, bool enable) +{ + u8 reg; + + reg = ioread8(gpio->base + NPCM_IOXCTS); + reg = (reg & ~NPCM_IOXCTS_RD_MODE) | NPCM_IOXCTS_RD_MODE_PERIODIC; + + if (enable) + reg |= NPCM_IOXCTS_IOXIF_EN; + else + reg &= ~NPCM_IOXCTS_IOXIF_EN; + + iowrite8(reg, gpio->base + NPCM_IOXCTS); +} + +static int npcm_sgpio_setup_clk(struct npcm_sgpio *gpio, + const struct npcm_clk_cfg *clk_cfg) +{ + unsigned long apb_freq; + u32 val; + u8 tmp; + int i; + + apb_freq = clk_get_rate(gpio->pclk); + tmp = ioread8(gpio->base + NPCM_IOXCFG1) & ~NPCM_IOXCFG1_SFT_CLK; + + for (i = clk_cfg->cfg_opt-1; i > 0; i--) { + val = apb_freq / clk_cfg->sft_clk[i]; + if (NPCM_CLK_MHZ > val) { + iowrite8(clk_cfg->clk_sel[i] | tmp, + gpio->base + NPCM_IOXCFG1); + return 0; + } + } + + return -EINVAL; +} + +static void npcm_sgpio_irq_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + + /* input GPIOs in the high range */ + bitmap_set(valid_mask, gpio->nout_sgpio, gpio->nin_sgpio); + bitmap_clear(valid_mask, 0, gpio->nout_sgpio); +} + +static void npcm_sgpio_irq_set_mask(struct irq_data *d, bool set) +{ + const struct npcm_sgpio_bank *bank; + struct npcm_sgpio *gpio; + unsigned long flags; + void __iomem *addr; + unsigned int offset; + u16 reg, type; + u8 bit; + + npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); + addr = bank_reg(gpio, bank, EVENT_CFG); + + reg = ioread16(addr); + if (set) { + reg &= ~(NPCM_IXOEVCFG_MASK << (bit * 2)); + } else { + type = gpio->int_type[offset]; + reg |= (type << (bit * 2)); + } + + raw_spin_lock_irqsave(&gpio->lock, flags); + + npcm_sgpio_setup_enable(gpio, false); + + iowrite16(reg, addr); + + npcm_sgpio_setup_enable(gpio, true); + + addr = bank_reg(gpio, bank, EVENT_STS); + reg = ioread8(addr); + reg |= BIT(bit); + iowrite8(reg, addr); + + raw_spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void npcm_sgpio_irq_ack(struct irq_data *d) +{ + const struct npcm_sgpio_bank *bank; + struct npcm_sgpio *gpio; + unsigned long flags; + void __iomem *status_addr; + unsigned int offset; + u8 bit; + + npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); + status_addr = bank_reg(gpio, bank, EVENT_STS); + raw_spin_lock_irqsave(&gpio->lock, flags); + iowrite8(BIT(bit), status_addr); + raw_spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void npcm_sgpio_irq_mask(struct irq_data *d) +{ + npcm_sgpio_irq_set_mask(d, true); +} + +static void npcm_sgpio_irq_unmask(struct irq_data *d) +{ + npcm_sgpio_irq_set_mask(d, false); +} + +static int npcm_sgpio_set_type(struct irq_data *d, unsigned int type) +{ + const struct npcm_sgpio_bank *bank; + irq_flow_handler_t handler; + struct npcm_sgpio *gpio; + unsigned long flags; + void __iomem *addr; + unsigned int offset; + u16 reg, val; + u8 bit; + + npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_BOTH: + val = NPCM_IXOEVCFG_BOTH; + break; + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_LEVEL_HIGH: + val = NPCM_IXOEVCFG_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + case IRQ_TYPE_LEVEL_LOW: + val = NPCM_IXOEVCFG_FALLING; + break; + default: + return -EINVAL; + } + + if (type & IRQ_TYPE_LEVEL_MASK) + handler = handle_level_irq; + else + handler = handle_edge_irq; + + gpio->int_type[offset] = val; + + raw_spin_lock_irqsave(&gpio->lock, flags); + npcm_sgpio_setup_enable(gpio, false); + addr = bank_reg(gpio, bank, EVENT_CFG); + reg = ioread16(addr); + + reg |= (val << (bit * 2)); + + iowrite16(reg, addr); + npcm_sgpio_setup_enable(gpio, true); + raw_spin_unlock_irqrestore(&gpio->lock, flags); + + irq_set_handler_locked(d, handler); + + return 0; +} + +static void npcm_sgpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *gc = irq_desc_get_handler_data(desc); + struct irq_chip *ic = irq_desc_get_chip(desc); + struct npcm_sgpio *gpio = gpiochip_get_data(gc); + unsigned int i, j, girq; + unsigned long reg; + + chained_irq_enter(ic, desc); + + for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) { + const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i]; + + reg = ioread8(bank_reg(gpio, bank, EVENT_STS)); + for_each_set_bit(j, ®, 8) { + girq = irq_find_mapping(gc->irq.domain, + i * 8 + gpio->nout_sgpio + j); + generic_handle_domain_irq(gc->irq.domain, girq); + } + } + + chained_irq_exit(ic, desc); +} + +static const struct irq_chip sgpio_irq_chip = { + .name = "sgpio-irq", + .irq_ack = npcm_sgpio_irq_ack, + .irq_mask = npcm_sgpio_irq_mask, + .irq_unmask = npcm_sgpio_irq_unmask, + .irq_set_type = npcm_sgpio_set_type, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int npcm_sgpio_setup_irqs(struct npcm_sgpio *gpio, + struct platform_device *pdev) +{ + int rc, i; + struct gpio_irq_chip *irq; + + rc = platform_get_irq(pdev, 0); + if (rc < 0) + return rc; + + gpio->irq = rc; + + npcm_sgpio_setup_enable(gpio, false); + + /* Disable IRQ and clear Interrupt status registers for all SGPIO Pins. */ + for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) { + const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i]; + + iowrite16(0, bank_reg(gpio, bank, EVENT_CFG)); + iowrite8(0xff, bank_reg(gpio, bank, EVENT_STS)); + } + + irq = &gpio->chip.irq; + gpio_irq_chip_set_chip(irq, &sgpio_irq_chip); + irq->init_valid_mask = npcm_sgpio_irq_init_valid_mask; + irq->handler = handle_bad_irq; + irq->default_type = IRQ_TYPE_NONE; + irq->parent_handler = npcm_sgpio_irq_handler; + irq->parent_handler_data = gpio; + irq->parents = &gpio->irq; + irq->num_parents = 1; + + return 0; +} + +static int npcm_sgpio_probe(struct platform_device *pdev) +{ + struct npcm_sgpio *gpio; + const struct npcm_clk_cfg *clk_cfg; + int rc; + u32 nin_gpios, nout_gpios; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gpio->base)) + return PTR_ERR(gpio->base); + + clk_cfg = device_get_match_data(&pdev->dev); + if (!clk_cfg) + return -EINVAL; + + rc = device_property_read_u32(&pdev->dev, "nuvoton,input-ngpios", + &nin_gpios); + if (rc < 0) + return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n"); + + rc = device_property_read_u32(&pdev->dev, "nuvoton,output-ngpios", + &nout_gpios); + if (rc < 0) + return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n"); + + gpio->nin_sgpio = nin_gpios; + gpio->nout_sgpio = nout_gpios; + if (gpio->nin_sgpio > MAX_NR_HW_SGPIO || + gpio->nout_sgpio > MAX_NR_HW_SGPIO) + return dev_err_probe(&pdev->dev, -EINVAL, "Number of GPIOs exceeds the maximum of %d: input: %d output: %d\n", MAX_NR_HW_SGPIO, nin_gpios, nout_gpios); + + gpio->pclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(gpio->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(gpio->pclk), "Could not get pclk\n"); + + rc = npcm_sgpio_setup_clk(gpio, clk_cfg); + if (rc < 0) + return dev_err_probe(&pdev->dev, rc, "Failed to setup clock\n"); + + raw_spin_lock_init(&gpio->lock); + gpio->chip.parent = &pdev->dev; + gpio->chip.ngpio = gpio->nin_sgpio + gpio->nout_sgpio; + gpio->chip.direction_input = npcm_sgpio_dir_in; + gpio->chip.direction_output = npcm_sgpio_dir_out; + gpio->chip.get_direction = npcm_sgpio_get_direction; + gpio->chip.get = npcm_sgpio_get; + gpio->chip.set = npcm_sgpio_set; + gpio->chip.label = dev_name(&pdev->dev); + gpio->chip.base = -1; + + rc = npcm_sgpio_init_port(gpio); + if (rc < 0) + return rc; + + rc = npcm_sgpio_setup_irqs(gpio, pdev); + if (rc < 0) + return rc; + + rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, "GPIO registering failed\n"); + + npcm_sgpio_setup_enable(gpio, true); + + return 0; +} + +static unsigned int npcm750_SFT_CLK[NPCM_750_OPT] = { + 1024, 32, 8, 4, 3, 2, +}; + +static unsigned int npcm750_CLK_SEL[NPCM_750_OPT] = { + 0x00, 0x05, 0x07, 0x0C, 0x0D, 0x0E, +}; + +static unsigned int npcm845_SFT_CLK[NPCM_845_OPT] = { + 1024, 32, 16, 8, 4, +}; + +static unsigned int npcm845_CLK_SEL[NPCM_845_OPT] = { + 0x00, 0x05, 0x06, 0x07, 0x0C, +}; + +static struct npcm_clk_cfg npcm750_sgpio_pdata = { + .sft_clk = npcm750_SFT_CLK, + .clk_sel = npcm750_CLK_SEL, + .cfg_opt = NPCM_750_OPT, +}; + +static const struct npcm_clk_cfg npcm845_sgpio_pdata = { + .sft_clk = npcm845_SFT_CLK, + .clk_sel = npcm845_CLK_SEL, + .cfg_opt = NPCM_845_OPT, +}; + +static const struct of_device_id npcm_sgpio_of_table[] = { + { .compatible = "nuvoton,npcm750-sgpio", .data = &npcm750_sgpio_pdata, }, + { .compatible = "nuvoton,npcm845-sgpio", .data = &npcm845_sgpio_pdata, }, + {} +}; +MODULE_DEVICE_TABLE(of, npcm_sgpio_of_table); + +static struct platform_driver npcm_sgpio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = npcm_sgpio_of_table, + }, + .probe = npcm_sgpio_probe, +}; +module_platform_driver(npcm_sgpio_driver); + +MODULE_AUTHOR("Jim Liu "); +MODULE_AUTHOR("Joseph Liu "); +MODULE_DESCRIPTION("Nuvoton NPCM Serial GPIO Driver"); +MODULE_LICENSE("GPL v2"); From 48e1b4d369cfe2729138a128afa6b8a55d093eaf Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 2 Jan 2024 16:59:47 +0100 Subject: [PATCH 54/55] gpiolib: remove the GPIO device from the list when it's unregistered If we wait until the GPIO device's .release() callback gets invoked before we remove it from the global device list, then we risk that someone will look it up using gpio_device_find() between where we dropped the last reference and before .release() is done taking a reference again to an object that's being released. The device must be removed when it's being unregistered - just like how we remove it from the GPIO bus. Fixes: ff2b13592299 ("gpio: make the gpiochip a real device") Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij --- drivers/gpio/gpiolib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e21497b989a1..e019c4243809 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -651,9 +651,6 @@ static void gpiodev_release(struct device *dev) { struct gpio_device *gdev = to_gpio_device(dev); - scoped_guard(mutex, &gpio_devices_lock) - list_del(&gdev->list); - ida_free(&gpio_ida, gdev->id); kfree_const(gdev->label); kfree(gdev->descs); @@ -1068,6 +1065,9 @@ void gpiochip_remove(struct gpio_chip *gc) dev_crit(&gdev->dev, "REMOVING GPIOCHIP WITH GPIOS STILL REQUESTED\n"); + scoped_guard(mutex, &gpio_devices_lock) + list_del(&gdev->list); + /* * The gpiochip side puts its use of the device to rest here: * if there are no userspace clients, the chardev and device will From 1979a28075470ef82472a5656ecc969f901e0d3b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 2 Jan 2024 16:59:48 +0100 Subject: [PATCH 55/55] gpiolib: replace the GPIO device mutex with a read-write semaphore There are only two spots where we modify (add to or remove objects from) the GPIO device list. Readers should be able to access it concurrently. Replace the mutex with a read-write semaphore and adjust the locking operations accordingly. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij --- drivers/gpio/gpiolib-sysfs.c | 2 +- drivers/gpio/gpiolib.c | 18 +++++++++--------- drivers/gpio/gpiolib.h | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 2d29ae37d953..4dbf298bb5dd 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -773,7 +773,7 @@ int gpiochip_sysfs_register_all(void) struct gpio_device *gdev; int ret; - guard(mutex)(&gpio_devices_lock); + guard(rwsem_read)(&gpio_devices_sem); list_for_each_entry(gdev, &gpio_devices, list) { if (gdev->mockdev) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e019c4243809..4c93cf73a826 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -85,7 +85,7 @@ static DEFINE_MUTEX(gpio_lookup_lock); static LIST_HEAD(gpio_lookup_list); LIST_HEAD(gpio_devices); -DEFINE_MUTEX(gpio_devices_lock); +DECLARE_RWSEM(gpio_devices_sem); static DEFINE_MUTEX(gpio_machine_hogs_mutex); static LIST_HEAD(gpio_machine_hogs); @@ -118,7 +118,7 @@ struct gpio_desc *gpio_to_desc(unsigned gpio) { struct gpio_device *gdev; - scoped_guard(mutex, &gpio_devices_lock) { + scoped_guard(rwsem_read, &gpio_devices_sem) { list_for_each_entry(gdev, &gpio_devices, list) { if (gdev->base <= gpio && gdev->base + gdev->ngpio > gpio) @@ -402,7 +402,7 @@ static struct gpio_desc *gpio_name_to_desc(const char * const name) if (!name) return NULL; - guard(mutex)(&gpio_devices_lock); + guard(rwsem_read)(&gpio_devices_sem); list_for_each_entry(gdev, &gpio_devices, list) { struct gpio_desc *desc; @@ -871,7 +871,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, gdev->ngpio = gc->ngpio; - scoped_guard(mutex, &gpio_devices_lock) { + scoped_guard(rwsem_write, &gpio_devices_sem) { /* * TODO: this allocates a Linux GPIO number base in the global * GPIO numberspace for this chip. In the long run we want to @@ -1001,7 +1001,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, goto err_print_message; } err_remove_from_list: - scoped_guard(mutex, &gpio_devices_lock) + scoped_guard(rwsem_write, &gpio_devices_sem) list_del(&gdev->list); err_free_label: kfree_const(gdev->label); @@ -1065,7 +1065,7 @@ void gpiochip_remove(struct gpio_chip *gc) dev_crit(&gdev->dev, "REMOVING GPIOCHIP WITH GPIOS STILL REQUESTED\n"); - scoped_guard(mutex, &gpio_devices_lock) + scoped_guard(rwsem_write, &gpio_devices_sem) list_del(&gdev->list); /* @@ -1114,7 +1114,7 @@ struct gpio_device *gpio_device_find(void *data, */ might_sleep(); - guard(mutex)(&gpio_devices_lock); + guard(rwsem_read)(&gpio_devices_sem); list_for_each_entry(gdev, &gpio_devices, list) { if (gdev->chip && match(gdev->chip, data)) @@ -4730,7 +4730,7 @@ static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos) s->private = ""; - guard(mutex)(&gpio_devices_lock); + guard(rwsem_read)(&gpio_devices_sem); list_for_each_entry(gdev, &gpio_devices, list) { if (index-- == 0) @@ -4745,7 +4745,7 @@ static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos) struct gpio_device *gdev = v; void *ret = NULL; - scoped_guard(mutex, &gpio_devices_lock) { + scoped_guard(rwsem_read, &gpio_devices_sem) { if (list_is_last(&gdev->list, &gpio_devices)) ret = NULL; else diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 0ce7451a6b24..97df54abf57a 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -137,7 +137,7 @@ int gpiod_set_transitory(struct gpio_desc *desc, bool transitory); extern spinlock_t gpio_lock; extern struct list_head gpio_devices; -extern struct mutex gpio_devices_lock; +extern struct rw_semaphore gpio_devices_sem; void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action);