diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 15:08:49 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 15:08:49 +0100 |
| commit | c3e8e6a6abe0f480dab7c5ea1c41c015b512dd3f (patch) | |
| tree | f740ff70dfd4d4fa4f866f28614b9a1538b21d65 | |
| parent | 1849082347faa6cf48aa6dcb13c615f81001ef2c (diff) | |
| parent | 8fe6fa0f223f44b8b869319d1b9c383749aa147b (diff) | |
| download | linux-next-c3e8e6a6abe0f480dab7c5ea1c41c015b512dd3f.tar.gz linux-next-c3e8e6a6abe0f480dab7c5ea1c41c015b512dd3f.zip | |
Merge branch 'gpio/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git
| -rw-r--r-- | Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml | 71 | ||||
| -rw-r--r-- | drivers/gpio/Kconfig | 14 | ||||
| -rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-amd-fch.c | 22 | ||||
| -rw-r--r-- | drivers/gpio/gpio-mt7621.c | 40 | ||||
| -rw-r--r-- | drivers/gpio/gpio-mvebu.c | 40 | ||||
| -rw-r--r-- | drivers/gpio/gpio-nomadik.c | 64 | ||||
| -rw-r--r-- | drivers/gpio/gpio-pca9570.c | 2 | ||||
| -rw-r--r-- | drivers/gpio/gpio-rcar.c | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-rockchip.c | 16 | ||||
| -rw-r--r-- | drivers/gpio/gpio-rtd1625.c | 611 | ||||
| -rw-r--r-- | drivers/gpio/gpio-sifive.c | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-tb10x.c | 5 | ||||
| -rw-r--r-- | drivers/gpio/gpiolib-cdev.c | 9 | ||||
| -rw-r--r-- | drivers/gpio/gpiolib-kunit.c | 280 |
15 files changed, 1063 insertions, 114 deletions
diff --git a/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml new file mode 100644 index 000000000000..f13c910b73c6 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml @@ -0,0 +1,71 @@ +# 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,rtd1625-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek DHC RTD1625 GPIO controller + +maintainers: + - Tzuyi Chang <tychang@realtek.com> + +description: | + GPIO controller for the Realtek RTD1625 SoC, featuring a per-pin register + architecture that differs significantly from earlier RTD series controllers. + Each GPIO has dedicated registers for configuration (direction, input/output + values, debounce), and interrupt control supporting edge and level detection + modes. + +properties: + compatible: + enum: + - realtek,rtd1625-iso-gpio + - realtek,rtd1625-isom-gpio + + reg: + maxItems: 1 + + 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. + - description: Interrupt number of the level-sensitive GPIO interrupt, + triggered by a configured logic level. + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + gpio-ranges: true + + gpio-controller: true + + "#gpio-cells": + const: 2 + +required: + - compatible + - reg + - gpio-ranges + - gpio-controller + - "#gpio-cells" + +additionalProperties: false + +examples: + - | + gpio@89100 { + compatible = "realtek,rtd1625-isom-gpio"; + reg = <0x89100 0x30>; + interrupt-parent = <&iso_m_irq_mux>; + interrupts = <0>, <1>, <2>; + interrupt-controller; + #interrupt-cells = <2>; + gpio-ranges = <&isom_pinctrl 0 0 4>; + gpio-controller; + #gpio-cells = <2>; + }; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 28cf6d2e83c2..f03c05288376 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -646,7 +646,7 @@ config GPIO_ROCKCHIP config GPIO_RTD tristate "Realtek DHC GPIO support" depends on ARCH_REALTEK || COMPILE_TEST - default y + default ARCH_REALTEK select GPIOLIB_IRQCHIP help This option enables support for GPIOs found on Realtek DHC(Digital @@ -656,6 +656,18 @@ config GPIO_RTD Say yes here to support GPIO functionality and GPIO interrupt on Realtek DHC SoCs. +config GPIO_RTD1625 + tristate "Realtek DHC RTD1625 GPIO support" + depends on ARCH_REALTEK || COMPILE_TEST + default ARCH_REALTEK + select GPIOLIB_IRQCHIP + help + This option enables support for the GPIO controller on Realtek + DHC (Digital Home Center) RTD1625 SoC. + + Say yes here to support both basic GPIO line functionality + and GPIO interrupt handling capabilities for this platform. + config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" depends on OF diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 4d0e900402fc..fa14581e3995 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -160,6 +160,7 @@ 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_GPIO_RTD1625) += gpio-rtd1625.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-amd-fch.c b/drivers/gpio/gpio-amd-fch.c index 9f329938202b..9b9d75acf35a 100644 --- a/drivers/gpio/gpio-amd-fch.c +++ b/drivers/gpio/gpio-amd-fch.c @@ -48,13 +48,11 @@ static void __iomem *amd_fch_gpio_addr(struct amd_fch_gpio_priv *priv, static int amd_fch_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, offset); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); writel_relaxed(readl_relaxed(ptr) & ~AMD_FCH_GPIO_FLAG_DIRECTION, ptr); - spin_unlock_irqrestore(&priv->lock, flags); return 0; } @@ -62,12 +60,11 @@ static int amd_fch_gpio_direction_input(struct gpio_chip *gc, static int amd_fch_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, int value) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); u32 val; - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); val = readl_relaxed(ptr); if (value) @@ -77,33 +74,28 @@ static int amd_fch_gpio_direction_output(struct gpio_chip *gc, writel_relaxed(val | AMD_FCH_GPIO_FLAG_DIRECTION, ptr); - spin_unlock_irqrestore(&priv->lock, flags); - return 0; } static int amd_fch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) { int ret; - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_DIRECTION); - spin_unlock_irqrestore(&priv->lock, flags); return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; } static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); u32 mask; - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); mask = readl_relaxed(ptr); if (value) @@ -112,22 +104,18 @@ static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) mask &= ~AMD_FCH_GPIO_FLAG_WRITE; writel_relaxed(mask, ptr); - spin_unlock_irqrestore(&priv->lock, flags); - return 0; } static int amd_fch_gpio_get(struct gpio_chip *gc, unsigned int offset) { - unsigned long flags; u32 val; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, offset); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); val = readl_relaxed(ptr); - spin_unlock_irqrestore(&priv->lock, flags); return FIELD_GET(AMD_FCH_GPIO_FLAG_READ, val); } diff --git a/drivers/gpio/gpio-mt7621.c b/drivers/gpio/gpio-mt7621.c index 1b0b5247d3c9..87086c322f08 100644 --- a/drivers/gpio/gpio-mt7621.c +++ b/drivers/gpio/gpio-mt7621.c @@ -68,7 +68,7 @@ mt7621_gpio_gc_to_priv(struct gpio_chip *gc) } static inline struct mtk_gc * -to_mediatek_gpio(struct gpio_chip *chip) +to_mt7621_gpio(struct gpio_chip *chip) { struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(chip); @@ -137,7 +137,7 @@ mt7621_gpio_hwirq_to_offset(irq_hw_number_t hwirq, struct mtk_gc *bank) } static void -mediatek_gpio_irq_unmask(struct irq_data *d) +mt7621_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -159,7 +159,7 @@ mediatek_gpio_irq_unmask(struct irq_data *d) } static void -mediatek_gpio_irq_mask(struct irq_data *d) +mt7621_gpio_irq_mask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -181,7 +181,7 @@ mediatek_gpio_irq_mask(struct irq_data *d) } static int -mediatek_gpio_irq_type(struct irq_data *d, unsigned int type) +mt7621_gpio_irq_type(struct irq_data *d, unsigned int type) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -245,11 +245,11 @@ mt7621_gpio_irq_relres(struct irq_data *d) } static int -mediatek_gpio_xlate(struct gpio_chip *chip, +mt7621_gpio_xlate(struct gpio_chip *chip, const struct of_phandle_args *spec, u32 *flags) { int gpio = spec->args[0]; - struct mtk_gc *rg = to_mediatek_gpio(chip); + struct mtk_gc *rg = to_mt7621_gpio(chip); if (rg->bank != gpio / MTK_BANK_WIDTH) return -EINVAL; @@ -264,10 +264,10 @@ static const struct irq_chip mt7621_irq_chip = { .name = "mt7621-gpio", .irq_request_resources = mt7621_gpio_irq_reqres, .irq_release_resources = mt7621_gpio_irq_relres, - .irq_mask_ack = mediatek_gpio_irq_mask, - .irq_mask = mediatek_gpio_irq_mask, - .irq_unmask = mediatek_gpio_irq_unmask, - .irq_set_type = mediatek_gpio_irq_type, + .irq_mask_ack = mt7621_gpio_irq_mask, + .irq_mask = mt7621_gpio_irq_mask, + .irq_unmask = mt7621_gpio_irq_unmask, + .irq_set_type = mt7621_gpio_irq_type, .flags = IRQCHIP_IMMUTABLE, }; @@ -380,7 +380,7 @@ mt7621_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) } static int -mediatek_gpio_bank_probe(struct device *dev, int bank) +mt7621_gpio_bank_probe(struct device *dev, int bank) { struct gpio_generic_chip_config config; struct mtk *mtk = dev_get_drvdata(dev); @@ -416,7 +416,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) } rg->chip.gc.of_gpio_n_cells = 2; - rg->chip.gc.of_xlate = mediatek_gpio_xlate; + rg->chip.gc.of_xlate = mt7621_gpio_xlate; rg->chip.gc.ngpio = MTK_BANK_WIDTH; rg->chip.gc.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d", dev_name(dev), bank); @@ -443,7 +443,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) } static int -mediatek_gpio_probe(struct platform_device *pdev) +mt7621_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk *mtk; @@ -477,7 +477,7 @@ mediatek_gpio_probe(struct platform_device *pdev) return ret; for (i = 0; i < MTK_BANK_CNT; i++) { - ret = mediatek_gpio_bank_probe(dev, i); + ret = mt7621_gpio_bank_probe(dev, i); if (ret) return ret; } @@ -485,18 +485,18 @@ mediatek_gpio_probe(struct platform_device *pdev) return 0; } -static const struct of_device_id mediatek_gpio_match[] = { +static const struct of_device_id mt7621_gpio_match[] = { { .compatible = "mediatek,mt7621-gpio" }, {}, }; -MODULE_DEVICE_TABLE(of, mediatek_gpio_match); +MODULE_DEVICE_TABLE(of, mt7621_gpio_match); -static struct platform_driver mediatek_gpio_driver = { - .probe = mediatek_gpio_probe, +static struct platform_driver mt7621_gpio_driver = { + .probe = mt7621_gpio_probe, .driver = { .name = "mt7621_gpio", - .of_match_table = mediatek_gpio_match, + .of_match_table = mt7621_gpio_match, }, }; -builtin_platform_driver(mediatek_gpio_driver); +builtin_platform_driver(mt7621_gpio_driver); diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index 689dc6354c2d..5b4408fcc10b 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -37,6 +37,7 @@ #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/irqchip/chained_irq.h> @@ -114,7 +115,7 @@ struct mvebu_gpio_chip { struct regmap *regs; u32 offset; struct regmap *percpu_regs; - int irqbase; + int bank_irq[4]; struct irq_domain *domain; int soc_variant; @@ -604,6 +605,34 @@ static const struct regmap_config mvebu_gpio_regmap_config = { }; /* + * Forward wake-up configuration to the parent bank IRQ. + * @d: interrupt data + * @enable: enable as wake-up if non-zero + * + * Return: 0 on success, or a negative error code. + */ +static int mvebu_gpio_set_wake_irq(struct irq_data *d, unsigned int enable) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + int bank; + int irq; + + bank = d->hwirq / 8; + if (bank >= ARRAY_SIZE(mvchip->bank_irq)) + return -EINVAL; + + irq = mvchip->bank_irq[bank]; + if (irq <= 0) + return -EINVAL; + + if (enable) + return enable_irq_wake(irq); + + return disable_irq_wake(irq); +} + +/* * Functions implementing the pwm_chip methods */ static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip) @@ -1252,7 +1281,7 @@ static int mvebu_gpio_probe(struct platform_device *pdev) err = irq_alloc_domain_generic_chips( mvchip->domain, ngpios, 2, np->name, handle_level_irq, - IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, 0); + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, IRQ_GC_INIT_NESTED_LOCK); if (err) { dev_err(&pdev->dev, "couldn't allocate irq chips %s (DT).\n", mvchip->chip.label); @@ -1270,6 +1299,8 @@ static int mvebu_gpio_probe(struct platform_device *pdev) ct->chip.irq_mask = mvebu_gpio_level_irq_mask; ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask; ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq; + ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND; ct->chip.name = mvchip->chip.label; ct = &gc->chip_types[1]; @@ -1278,6 +1309,8 @@ static int mvebu_gpio_probe(struct platform_device *pdev) ct->chip.irq_mask = mvebu_gpio_edge_irq_mask; ct->chip.irq_unmask = mvebu_gpio_edge_irq_unmask; ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq; + ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND; ct->handler = handle_edge_irq; ct->chip.name = mvchip->chip.label; @@ -1286,13 +1319,14 @@ static int mvebu_gpio_probe(struct platform_device *pdev) * interrupt handlers, with each handler dealing with 8 GPIO * pins. */ - for (i = 0; i < 4; i++) { + for (i = 0; i < ARRAY_SIZE(mvchip->bank_irq); i++) { int irq = platform_get_irq_optional(pdev, i); if (irq < 0) continue; irq_set_chained_handler_and_data(irq, mvebu_gpio_irq_handler, mvchip); + mvchip->bank_irq[i] = irq; } return 0; diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c index 5dc9f9d5912a..208fed631975 100644 --- a/drivers/gpio/gpio-nomadik.c +++ b/drivers/gpio/gpio-nomadik.c @@ -519,22 +519,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, gpio_dev = bus_find_device_by_fwnode(&platform_bus_type, fwnode); if (!gpio_dev) { - dev_err(dev, "populate \"%pfwP\": device not found\n", fwnode); - return ERR_PTR(-ENODEV); + ret = -ENODEV; + dev_err_probe(dev, ret, "populate \"%pfwP\": device not found\n", fwnode); + return ERR_PTR(ret); } gpio_pdev = to_platform_device(gpio_dev); if (device_property_read_u32(gpio_dev, "gpio-bank", &id)) { - dev_err(dev, "populate: gpio-bank property not found\n"); - platform_device_put(gpio_pdev); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + dev_err_probe(dev, ret, "populate: gpio-bank property not found\n"); + goto err_put_pdev; } #ifdef CONFIG_PINCTRL_NOMADIK if (id >= ARRAY_SIZE(nmk_gpio_chips)) { - dev_err(dev, "populate: invalid id: %u\n", id); - platform_device_put(gpio_pdev); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + dev_err_probe(dev, ret, "populate: invalid id: %u\n", id); + goto err_put_pdev; } /* Already populated? */ nmk_chip = nmk_gpio_chips[id]; @@ -546,8 +547,8 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, nmk_chip = devm_kzalloc(dev, sizeof(*nmk_chip), GFP_KERNEL); if (!nmk_chip) { - platform_device_put(gpio_pdev); - return ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto err_put_pdev; } if (device_property_read_u32(gpio_dev, "ngpios", &ngpio)) { @@ -568,16 +569,17 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) { - platform_device_put(gpio_pdev); - return ERR_CAST(base); + ret = PTR_ERR(base); + goto err_put_pdev; } nmk_chip->addr = base; /* NOTE: do not use devm_ here! */ clk = clk_get_optional(gpio_dev, NULL); if (IS_ERR(clk)) { - platform_device_put(gpio_pdev); - return ERR_CAST(clk); + ret = PTR_ERR(clk); + dev_err_probe(dev, ret, "failed getting clock\n"); + goto err_put_pdev; } clk_prepare(clk); nmk_chip->clk = clk; @@ -585,12 +587,9 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, /* NOTE: do not use devm_ here! */ reset = reset_control_get_optional_shared(gpio_dev, NULL); if (IS_ERR(reset)) { - clk_unprepare(clk); - clk_put(clk); - platform_device_put(gpio_pdev); - dev_err(dev, "failed getting reset control: %pe\n", - reset); - return ERR_CAST(reset); + ret = PTR_ERR(reset); + dev_err_probe(dev, ret, "failed getting reset control\n"); + goto err_unprepare_clk; } /* @@ -600,18 +599,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, */ ret = reset_control_deassert(reset); if (ret) { - reset_control_put(reset); - clk_unprepare(clk); - clk_put(clk); - platform_device_put(gpio_pdev); - dev_err(dev, "failed reset deassert: %d\n", ret); - return ERR_PTR(ret); + dev_err_probe(dev, ret, "failed reset deassert\n"); + goto err_put_reset; } #ifdef CONFIG_PINCTRL_NOMADIK nmk_gpio_chips[id] = nmk_chip; #endif return nmk_chip; + +err_put_reset: + reset_control_put(reset); +err_unprepare_clk: + clk_unprepare(clk); + clk_put(clk); +err_put_pdev: + platform_device_put(gpio_pdev); + return ERR_PTR(ret); } static void nmk_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) @@ -647,10 +651,8 @@ static int nmk_gpio_probe(struct platform_device *pdev) int ret; nmk_chip = nmk_gpio_populate_chip(dev_fwnode(dev), pdev); - if (IS_ERR(nmk_chip)) { - dev_err(dev, "could not populate nmk chip struct\n"); + if (IS_ERR(nmk_chip)) return PTR_ERR(nmk_chip); - } supports_sleepmode = device_property_read_bool(dev, "st,supports-sleepmode"); @@ -693,7 +695,7 @@ static int nmk_gpio_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, nmk_gpio_irq_handler, IRQF_SHARED, dev_name(dev), nmk_chip); if (ret) { - dev_err(dev, "failed requesting IRQ\n"); + dev_err_probe(dev, ret, "failed requesting IRQ\n"); return ret; } @@ -709,8 +711,6 @@ static int nmk_gpio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, nmk_chip); - dev_info(dev, "chip registered\n"); - return 0; } diff --git a/drivers/gpio/gpio-pca9570.c b/drivers/gpio/gpio-pca9570.c index 7a47a9aa0414..db027c10494f 100644 --- a/drivers/gpio/gpio-pca9570.c +++ b/drivers/gpio/gpio-pca9570.c @@ -133,7 +133,7 @@ static int pca9570_probe(struct i2c_client *client) gpio->chip.get = pca9570_get; gpio->chip.set = pca9570_set; gpio->chip.base = -1; - gpio->chip_data = device_get_match_data(&client->dev); + gpio->chip_data = i2c_get_match_data(client); gpio->chip.ngpio = gpio->chip_data->ngpio; gpio->chip.can_sleep = true; diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c index 86777e097fd8..09bebde5c426 100644 --- a/drivers/gpio/gpio-rcar.c +++ b/drivers/gpio/gpio-rcar.c @@ -59,7 +59,6 @@ struct gpio_rcar_priv { #define MSKCLR 0x1c /* Interrupt Mask Clear Register */ #define POSNEG 0x20 /* Positive/Negative Logic Select Register */ #define EDGLEVEL 0x24 /* Edge/level Select Register */ -#define FILONOFF 0x28 /* Chattering Prevention On/Off Register */ #define OUTDTSEL 0x40 /* Output Data Select Register */ #define BOTHEDGE 0x4c /* One Edge/Both Edge Select Register */ #define INEN 0x50 /* General Input Enable Register */ diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c index 9478a58f1caa..77b239a9a601 100644 --- a/drivers/gpio/gpio-rockchip.c +++ b/drivers/gpio/gpio-rockchip.c @@ -647,21 +647,17 @@ static void rockchip_clk_put(void *data) static int rockchip_get_bank_data(struct rockchip_pin_bank *bank) { - struct resource res; + struct platform_device *pdev = to_platform_device(bank->dev); int id = 0, ret; - if (of_address_to_resource(bank->of_node, 0, &res)) { - dev_err(bank->dev, "cannot find IO resource for bank\n"); - return -ENOENT; - } - - bank->reg_base = devm_ioremap_resource(bank->dev, &res); + bank->reg_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(bank->reg_base)) return PTR_ERR(bank->reg_base); - bank->irq = irq_of_parse_and_map(bank->of_node, 0); - if (!bank->irq) - return -EINVAL; + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + bank->irq = ret; bank->clk = devm_clk_get_enabled(bank->dev, NULL); if (IS_ERR(bank->clk)) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c new file mode 100644 index 000000000000..409e540bf40b --- /dev/null +++ b/drivers/gpio/gpio-rtd1625.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Realtek DHC RTD1625 gpio driver + * + * Copyright (c) 2023-2026 Realtek Semiconductor Corp. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define RTD1625_GPIO_DIR BIT(0) +#define RTD1625_GPIO_OUT BIT(2) +#define RTD1625_GPIO_IN BIT(4) +#define RTD1625_GPIO_EDGE_INT_DP BIT(6) +#define RTD1625_GPIO_EDGE_INT_EN BIT(8) +#define RTD1625_GPIO_LEVEL_INT_EN BIT(16) +#define RTD1625_GPIO_LEVEL_INT_DP BIT(18) +#define RTD1625_GPIO_DEBOUNCE GENMASK(30, 28) +#define RTD1625_GPIO_DEBOUNCE_WREN BIT(31) + +#define RTD1625_GPIO_WREN(x) ((x) << 1) + +/* Write-enable masks for all GPIO configs and reserved hardware bits */ +#define RTD1625_ISO_GPIO_WREN_ALL 0x8000aa8a +#define RTD1625_ISOM_GPIO_WREN_ALL 0x800aaa8a + +#define RTD1625_GPIO_DEBOUNCE_1US 0 +#define RTD1625_GPIO_DEBOUNCE_10US 1 +#define RTD1625_GPIO_DEBOUNCE_100US 2 +#define RTD1625_GPIO_DEBOUNCE_1MS 3 +#define RTD1625_GPIO_DEBOUNCE_10MS 4 +#define RTD1625_GPIO_DEBOUNCE_20MS 5 +#define RTD1625_GPIO_DEBOUNCE_30MS 6 +#define RTD1625_GPIO_DEBOUNCE_50MS 7 + +#define GPIO_CONTROL(gpio) ((gpio) * 4) + +enum rtd1625_irq_index { + RTD1625_IRQ_ASSERT, + RTD1625_IRQ_DEASSERT, + RTD1625_IRQ_LEVEL, + RTD1625_MAX_IRQS +}; + +/** + * struct rtd1625_gpio_info - Specific GPIO register information + * @num_gpios: The number of GPIOs + * @irq_type_support: Supported IRQ types + * @gpa_offset: Offset for GPIO assert interrupt status registers + * @gpda_offset: Offset for GPIO deassert interrupt status registers + * @level_offset: Offset of level interrupt status register + * @write_en_all: Write-enable mask for all configurable bits + */ +struct rtd1625_gpio_info { + unsigned int num_gpios; + unsigned int irq_type_support; + unsigned int base_offset; + unsigned int gpa_offset; + unsigned int gpda_offset; + unsigned int level_offset; + unsigned int write_en_all; +}; + +struct rtd1625_gpio { + struct gpio_chip gpio_chip; + const struct rtd1625_gpio_info *info; + void __iomem *base; + void __iomem *irq_base; + unsigned int irqs[RTD1625_MAX_IRQS]; + raw_spinlock_t lock; + unsigned int *save_regs; +}; + +static unsigned int rtd1625_gpio_gpa_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->gpa_offset + ((offset / 32) * 4); +} + +static unsigned int rtd1625_gpio_gpda_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->gpda_offset + ((offset / 32) * 4); +} + +static unsigned int rtd1625_gpio_level_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->level_offset + ((offset / 32) * 4); +} + +static int rtd1625_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset, + unsigned int debounce) +{ + struct rtd1625_gpio *data = gpiochip_get_data(chip); + u8 deb_val; + u32 val; + + switch (debounce) { + case 1: + deb_val = RTD1625_GPIO_DEBOUNCE_1US; + break; + case 10: + deb_val = RTD1625_GPIO_DEBOUNCE_10US; + break; + case 100: + deb_val = RTD1625_GPIO_DEBOUNCE_100US; + break; + case 1000: + deb_val = RTD1625_GPIO_DEBOUNCE_1MS; + break; + case 10000: + deb_val = RTD1625_GPIO_DEBOUNCE_10MS; + break; + case 20000: + deb_val = RTD1625_GPIO_DEBOUNCE_20MS; + break; + case 30000: + deb_val = RTD1625_GPIO_DEBOUNCE_30MS; + break; + case 50000: + deb_val = RTD1625_GPIO_DEBOUNCE_50MS; + break; + default: + return -ENOTSUPP; + } + + val = FIELD_PREP(RTD1625_GPIO_DEBOUNCE, deb_val) | RTD1625_GPIO_DEBOUNCE_WREN; + + guard(raw_spinlock_irqsave)(&data->lock); + + writel_relaxed(val, data->base + GPIO_CONTROL(offset)); + + return 0; +} + +static int rtd1625_gpio_set_config(struct gpio_chip *chip, unsigned int offset, + unsigned long config) +{ + u32 debounce; + + if (pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE) { + debounce = pinconf_to_config_argument(config); + return rtd1625_gpio_set_debounce(chip, offset, debounce); + } + + return gpiochip_generic_config(chip, offset, config); +} + +static int rtd1625_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct rtd1625_gpio *data = gpiochip_get_data(chip); + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_OUT); + + if (value) + val |= RTD1625_GPIO_OUT; + + guard(raw_spinlock_irqsave)(&data->lock); + + writel_relaxed(val, data->base + GPIO_CONTROL(offset)); + + return 0; +} + +static int rtd1625_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct rtd1625_gpio *data = gpiochip_get_data(chip); + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = readl_relaxed(data->base + GPIO_CONTROL(offset)); + + if (val & RTD1625_GPIO_DIR) + return !!(val & RTD1625_GPIO_OUT); + else + return !!(val & RTD1625_GPIO_IN); +} + +static int rtd1625_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct rtd1625_gpio *data = gpiochip_get_data(chip); + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = readl_relaxed(data->base + GPIO_CONTROL(offset)); + + if (val & RTD1625_GPIO_DIR) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + +static int rtd1625_gpio_set_direction(struct gpio_chip *chip, unsigned int offset, bool out) +{ + struct rtd1625_gpio *data = gpiochip_get_data(chip); + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_DIR); + + if (out) + val |= RTD1625_GPIO_DIR; + + guard(raw_spinlock_irqsave)(&data->lock); + + writel_relaxed(val, data->base + GPIO_CONTROL(offset)); + + return 0; +} + +static int rtd1625_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + return rtd1625_gpio_set_direction(chip, offset, false); +} + +static int rtd1625_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) +{ + rtd1625_gpio_set(chip, offset, value); + + return rtd1625_gpio_set_direction(chip, offset, true); +} + +static void rtd1625_gpio_irq_handle(struct irq_desc *desc) +{ + unsigned int (*get_reg_offset)(struct rtd1625_gpio *gpio, unsigned int offset); + struct rtd1625_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; + unsigned int reg_offset, i, j; + unsigned int girq; + irq_hw_number_t hwirq; + u32 irq_type; + + if (irq == data->irqs[RTD1625_IRQ_ASSERT]) + get_reg_offset = &rtd1625_gpio_gpa_offset; + else if (irq == data->irqs[RTD1625_IRQ_DEASSERT]) + get_reg_offset = &rtd1625_gpio_gpda_offset; + else if (irq == data->irqs[2]) + get_reg_offset = &rtd1625_gpio_level_offset; + else + return; + + chained_irq_enter(chip, desc); + + for (i = 0; i < data->info->num_gpios; i += 32) { + reg_offset = get_reg_offset(data, i); + status = readl_relaxed(data->irq_base + reg_offset); + + /* + * Hardware quirk: The controller fires both "assert" and "de-assert" + * interrupts simultaneously on any edge toggle. + * We must pre-clear edge interrupts here. If we drop an unwanted + * de-assert interrupt below, it will never reach the IRQ core + * (generic_handle_domain_irq), meaning ->irq_ack() won't be called. + * Failing to clear it here leads to an interrupt storm. + */ + if (irq != data->irqs[RTD1625_IRQ_LEVEL]) + writel_relaxed(status, data->irq_base + reg_offset); + + for_each_set_bit(j, &status, 32) { + hwirq = i + j; + girq = irq_find_mapping(domain, hwirq); + irq_type = irq_get_trigger_type(girq); + + /* + * Filter out the hardware-forced de-assert interrupt unless + * the user explicitly requested IRQ_TYPE_EDGE_BOTH. + */ + if (irq == data->irqs[RTD1625_IRQ_DEASSERT] && + irq_type != IRQ_TYPE_EDGE_BOTH) + continue; + + generic_handle_domain_irq(domain, hwirq); + } + } + + chained_irq_exit(chip, desc); +} + +static void rtd1625_gpio_ack_irq(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + u32 bit_mask = BIT(hwirq % 32); + int reg_offset; + + if (irq_type & IRQ_TYPE_LEVEL_MASK) { + reg_offset = rtd1625_gpio_level_offset(data, hwirq); + writel_relaxed(bit_mask, data->irq_base + reg_offset); + } +} + +static void rtd1625_gpio_enable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + int gpda_reg_offset = rtd1625_gpio_gpda_offset(data, hwirq); + int gpa_reg_offset = rtd1625_gpio_gpa_offset(data, hwirq); + u32 clr_mask = BIT(hwirq % 32); + u32 val; + + 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 = RTD1625_GPIO_EDGE_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN); + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); +} + +static void rtd1625_gpio_disable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN); + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); +} + +static void rtd1625_gpio_enable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + int level_reg_offset = rtd1625_gpio_level_offset(data, hwirq); + u32 clr_mask = BIT(hwirq % 32); + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + writel_relaxed(clr_mask, data->irq_base + level_reg_offset); + val = RTD1625_GPIO_LEVEL_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN); + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); +} + +static void rtd1625_gpio_disable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN); + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); +} + +static void rtd1625_gpio_enable_irq(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd1625_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + + gpiochip_enable_irq(gc, hwirq); + + if (irq_type & IRQ_TYPE_EDGE_BOTH) + rtd1625_gpio_enable_edge_irq(data, hwirq); + else if (irq_type & IRQ_TYPE_LEVEL_MASK) + rtd1625_gpio_enable_level_irq(data, hwirq); +} + +static void rtd1625_gpio_disable_irq(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd1625_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + + if (irq_type & IRQ_TYPE_EDGE_BOTH) + rtd1625_gpio_disable_edge_irq(data, hwirq); + else if (irq_type & IRQ_TYPE_LEVEL_MASK) + rtd1625_gpio_disable_level_irq(data, hwirq); + + gpiochip_disable_irq(gc, hwirq); +} + +static int rtd1625_gpio_irq_set_level_type(struct irq_data *d, bool level) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd1625_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_DP); + + if (!(data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK)) + return -EINVAL; + + if (level) + val |= RTD1625_GPIO_LEVEL_INT_DP; + + scoped_guard(raw_spinlock_irqsave, &data->lock) + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); + + irq_set_handler_locked(d, handle_level_irq); + + return 0; +} + +static int rtd1625_gpio_irq_set_edge_type(struct irq_data *d, bool polarity) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rtd1625_gpio *data = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_DP); + + if (!(data->info->irq_type_support & IRQ_TYPE_EDGE_BOTH)) + return -EINVAL; + + if (polarity) + val |= RTD1625_GPIO_EDGE_INT_DP; + + scoped_guard(raw_spinlock_irqsave, &data->lock) + writel_relaxed(val, data->base + GPIO_CONTROL(hwirq)); + + irq_set_handler_locked(d, handle_edge_irq); + + return 0; +} + +static int rtd1625_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + return rtd1625_gpio_irq_set_edge_type(d, 1); + + case IRQ_TYPE_EDGE_FALLING: + return rtd1625_gpio_irq_set_edge_type(d, 0); + + case IRQ_TYPE_EDGE_BOTH: + return rtd1625_gpio_irq_set_edge_type(d, 1); + + case IRQ_TYPE_LEVEL_HIGH: + return rtd1625_gpio_irq_set_level_type(d, 0); + + case IRQ_TYPE_LEVEL_LOW: + return rtd1625_gpio_irq_set_level_type(d, 1); + + default: + return -EINVAL; + } +} + +static struct irq_chip rtd1625_iso_gpio_irq_chip = { + .name = "rtd1625-gpio", + .irq_ack = rtd1625_gpio_ack_irq, + .irq_mask = rtd1625_gpio_disable_irq, + .irq_unmask = rtd1625_gpio_enable_irq, + .irq_set_type = rtd1625_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int rtd1625_gpio_setup_irq(struct platform_device *pdev, struct rtd1625_gpio *data) +{ + struct gpio_irq_chip *irq_chip; + unsigned int num_irqs; + int irq; + + /* + * Interrupt support is optional. All IRQs must be provided together. + * If index 0 is missing, we assume no interrupts are configured in DT + * and fall back to basic GPIO operation. + */ + irq = platform_get_irq_optional(pdev, 0); + if (irq == -ENXIO) + return 0; + if (irq < 0) + return irq; + + num_irqs = (data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK) ? 3 : 2; + data->irqs[RTD1625_IRQ_ASSERT] = irq; + + for (unsigned int i = 1; i < num_irqs; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + data->irqs[i] = irq; + } + + irq_chip = &data->gpio_chip.irq; + irq_chip->handler = handle_bad_irq; + irq_chip->default_type = IRQ_TYPE_NONE; + irq_chip->parent_handler = rtd1625_gpio_irq_handle; + irq_chip->parent_handler_data = data; + irq_chip->num_parents = num_irqs; + irq_chip->parents = data->irqs; + + gpio_irq_chip_set_chip(irq_chip, &rtd1625_iso_gpio_irq_chip); + + return 0; +} + +static int rtd1625_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rtd1625_gpio *data; + void __iomem *irq_base; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->info = device_get_match_data(dev); + if (!data->info) + return -EINVAL; + + raw_spin_lock_init(&data->lock); + + irq_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(irq_base)) + return PTR_ERR(irq_base); + + data->irq_base = irq_base; + data->base = irq_base + data->info->base_offset; + + data->save_regs = devm_kcalloc(dev, data->info->num_gpios, sizeof(*data->save_regs), + GFP_KERNEL); + if (!data->save_regs) + return -ENOMEM; + + 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 = rtd1625_gpio_get_direction; + data->gpio_chip.direction_input = rtd1625_gpio_direction_input; + data->gpio_chip.direction_output = rtd1625_gpio_direction_output; + data->gpio_chip.set = rtd1625_gpio_set; + data->gpio_chip.get = rtd1625_gpio_get; + data->gpio_chip.set_config = rtd1625_gpio_set_config; + data->gpio_chip.parent = dev; + + ret = rtd1625_gpio_setup_irq(pdev, data); + if (ret) + return ret; + + platform_set_drvdata(pdev, data); + + return devm_gpiochip_add_data(dev, &data->gpio_chip, data); +} + +static const struct rtd1625_gpio_info rtd1625_iso_gpio_info = { + .num_gpios = 166, + .irq_type_support = IRQ_TYPE_EDGE_BOTH, + .base_offset = 0x100, + .gpa_offset = 0x000, + .gpda_offset = 0x020, + .write_en_all = RTD1625_ISO_GPIO_WREN_ALL, +}; + +static const struct rtd1625_gpio_info rtd1625_isom_gpio_info = { + .num_gpios = 4, + .irq_type_support = IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_LOW | + IRQ_TYPE_LEVEL_HIGH, + .base_offset = 0x20, + .gpa_offset = 0x00, + .gpda_offset = 0x04, + .level_offset = 0x18, + .write_en_all = RTD1625_ISOM_GPIO_WREN_ALL, +}; + +static int rtd1625_gpio_suspend(struct device *dev) +{ + struct rtd1625_gpio *data = dev_get_drvdata(dev); + const struct rtd1625_gpio_info *info = data->info; + + for (unsigned int i = 0; i < info->num_gpios; i++) + data->save_regs[i] = readl_relaxed(data->base + GPIO_CONTROL(i)); + + return 0; +} + +static int rtd1625_gpio_resume(struct device *dev) +{ + struct rtd1625_gpio *data = dev_get_drvdata(dev); + const struct rtd1625_gpio_info *info = data->info; + + for (unsigned int i = 0; i < info->num_gpios; i++) + writel_relaxed(data->save_regs[i] | info->write_en_all, + data->base + GPIO_CONTROL(i)); + + return 0; +} + +static DEFINE_NOIRQ_DEV_PM_OPS(rtd1625_gpio_pm_ops, rtd1625_gpio_suspend, rtd1625_gpio_resume); + +static const struct of_device_id rtd1625_gpio_of_matches[] = { + { .compatible = "realtek,rtd1625-iso-gpio", .data = &rtd1625_iso_gpio_info }, + { .compatible = "realtek,rtd1625-isom-gpio", .data = &rtd1625_isom_gpio_info }, + { } +}; +MODULE_DEVICE_TABLE(of, rtd1625_gpio_of_matches); + +static struct platform_driver rtd1625_gpio_platform_driver = { + .driver = { + .name = "gpio-rtd1625", + .of_match_table = rtd1625_gpio_of_matches, + .pm = pm_sleep_ptr(&rtd1625_gpio_pm_ops), + }, + .probe = rtd1625_gpio_probe, +}; +module_platform_driver(rtd1625_gpio_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Realtek Semiconductor Corporation"); +MODULE_DESCRIPTION("Realtek DHC SoC RTD1625 gpio driver"); diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c index 94ef2efbd14f..e9a992cd4b9c 100644 --- a/drivers/gpio/gpio-sifive.c +++ b/drivers/gpio/gpio-sifive.c @@ -260,6 +260,7 @@ static const struct of_device_id sifive_gpio_match[] = { { .compatible = "sifive,fu540-c000-gpio" }, { }, }; +MODULE_DEVICE_TABLE(of, sifive_gpio_match); static struct platform_driver sifive_gpio_driver = { .probe = sifive_gpio_probe, diff --git a/drivers/gpio/gpio-tb10x.c b/drivers/gpio/gpio-tb10x.c index 705bfd80a8d0..7fb8e6223bd1 100644 --- a/drivers/gpio/gpio-tb10x.c +++ b/drivers/gpio/gpio-tb10x.c @@ -51,7 +51,7 @@ static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs) return ioread32(gpio->base + offs); } -static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) { struct tb10x_gpio *tb10x_gpio = gpiochip_get_data(chip); @@ -167,9 +167,8 @@ static int tb10x_gpio_probe(struct platform_device *pdev) tb10x_gpio->domain = irq_domain_create_linear(dev_fwnode(dev), tb10x_gpio->chip.gc.ngpio, &irq_generic_chip_ops, NULL); - if (!tb10x_gpio->domain) { + if (!tb10x_gpio->domain) return -ENOMEM; - } ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain, tb10x_gpio->chip.gc.ngpio, 1, tb10x_gpio->chip.gc.label, diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 82f27db0b230..9f3b628d5793 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2682,15 +2682,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) file->private_data = cdev; cdev->fp = file; - ret = nonseekable_open(inode, file); - if (ret) - goto out_unregister_device_notifier; - - return ret; + return nonseekable_open(inode, file); -out_unregister_device_notifier: - blocking_notifier_chain_unregister(&gdev->device_notifier, - &cdev->device_unregistered_nb); out_unregister_line_notifier: scoped_guard(write_lock_irqsave, &gdev->line_state_lock) raw_notifier_chain_unregister(&gdev->line_state_notifier, diff --git a/drivers/gpio/gpiolib-kunit.c b/drivers/gpio/gpiolib-kunit.c index 380b68f879e5..c9c5b4374820 100644 --- a/drivers/gpio/gpiolib-kunit.c +++ b/drivers/gpio/gpiolib-kunit.c @@ -18,19 +18,67 @@ #define GPIO_TEST_PROVIDER "gpio-test-provider" #define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer" #define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer" +#define GPIO_CONSUMER_NAME "gpio-swnode-consumer-test-device" + +#define GPIO_TEST_PROVIDER_NGPIO 4 + +/* + * The test provider tracks per-line direction and value so that lines can be + * driven as both inputs and outputs - this is needed to exercise input as well + * as output GPIO hogs. + */ +struct gpio_test_provider_data { + DECLARE_BITMAP(is_output, GPIO_TEST_PROVIDER_NGPIO); + DECLARE_BITMAP(values, GPIO_TEST_PROVIDER_NGPIO); +}; static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset) { - return GPIO_LINE_DIRECTION_OUT; + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + return test_bit(offset, data->is_output) ? + GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int gpio_test_provider_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + clear_bit(offset, data->is_output); + + return 0; +} + +static int gpio_test_provider_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + set_bit(offset, data->is_output); + __assign_bit(offset, data->values, value); + + return 0; +} + +static int gpio_test_provider_get(struct gpio_chip *gc, unsigned int offset) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + return test_bit(offset, data->values); } static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value) { + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + __assign_bit(offset, data->values, value); + return 0; } static int gpio_test_provider_probe(struct platform_device *pdev) { + struct gpio_test_provider_data *data; struct device *dev = &pdev->dev; struct gpio_chip *gc; @@ -38,16 +86,26 @@ static int gpio_test_provider_probe(struct platform_device *pdev) if (!gc) return -ENOMEM; + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Lines start as outputs to preserve the default for lookup tests. */ + bitmap_fill(data->is_output, GPIO_TEST_PROVIDER_NGPIO); + gc->base = -1; - gc->ngpio = 4; - gc->label = "gpio-swnode-consumer-test-device"; + gc->ngpio = GPIO_TEST_PROVIDER_NGPIO; + gc->label = GPIO_CONSUMER_NAME; gc->parent = dev; gc->owner = THIS_MODULE; gc->get_direction = gpio_test_provider_get_direction; + gc->direction_input = gpio_test_provider_direction_input; + gc->direction_output = gpio_test_provider_direction_output; + gc->get = gpio_test_provider_get; gc->set = gpio_test_provider_set; - return devm_gpiochip_add_data(dev, gc, NULL); + return devm_gpiochip_add_data(dev, gc, data); } static struct platform_driver gpio_test_provider_driver = { @@ -63,10 +121,12 @@ static const struct software_node gpio_test_provider_swnode = { struct gpio_swnode_consumer_pdata { bool gpio_ok; + int errno; }; static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = { .gpio_ok = false, + .errno = 0, }; static int gpio_swnode_consumer_probe(struct platform_device *pdev) @@ -76,8 +136,10 @@ static int gpio_swnode_consumer_probe(struct platform_device *pdev) struct gpio_desc *desc; desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); - if (IS_ERR(desc)) + if (IS_ERR(desc)) { + pdata->errno = PTR_ERR(desc); return PTR_ERR(desc); + } pdata->gpio_ok = true; @@ -91,13 +153,8 @@ static struct platform_driver gpio_swnode_consumer_driver = { }, }; -static void gpio_swnode_lookup_by_primary(struct kunit *test) +static int gpio_swnode_register_drivers(struct kunit *test) { - struct gpio_swnode_consumer_pdata *pdata; - struct platform_device_info pdevinfo; - struct property_entry properties[2]; - struct platform_device *pdev; - bool bound = false; int ret; ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); @@ -106,6 +163,17 @@ static void gpio_swnode_lookup_by_primary(struct kunit *test) ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); KUNIT_ASSERT_EQ(test, ret, 0); + return 0; +} + +static void gpio_swnode_lookup_by_primary(struct kunit *test) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *pdev; + bool bound = false; + pdevinfo = (struct platform_device_info){ .name = GPIO_TEST_PROVIDER, .id = PLATFORM_DEVID_NONE, @@ -149,7 +217,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test) struct fwnode_handle *primary; struct platform_device *pdev; bool bound = false; - int ret; /* * Can't live on the stack as it will still get referenced in cleanup @@ -158,12 +225,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test) primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary); - ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); - KUNIT_ASSERT_EQ(test, ret, 0); - - ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); - KUNIT_ASSERT_EQ(test, ret, 0); - fwnode_init(primary, NULL); pdevinfo = (struct platform_device_info){ @@ -211,6 +272,7 @@ static struct kunit_case gpio_swnode_lookup_tests[] = { static struct kunit_suite gpio_swnode_lookup_test_suite = { .name = "gpio-swnode-lookup", .test_cases = gpio_swnode_lookup_tests, + .init = gpio_swnode_register_drivers, }; static BLOCKING_NOTIFIER_HEAD(gpio_unbind_notifier); @@ -348,9 +410,191 @@ static struct kunit_suite gpio_unbind_with_consumers_test_suite = { .test_cases = gpio_unbind_with_consumers_tests, }; +/* + * GPIO line hogs are described by child software nodes of the provider + * carrying the "gpio-hog" property. They are picked up automatically when the + * gpiochip is registered. Each hog below sits on a distinct line of the + * provider. + */ +#define GPIO_HOG_OUTPUT_HIGH_OFFSET 0 +#define GPIO_HOG_OUTPUT_LOW_OFFSET 1 +#define GPIO_HOG_INPUT_OFFSET 2 + +static const u32 gpio_hog_output_high_gpios[] = { + GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_output_high_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_high_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-output-high"), + PROPERTY_ENTRY_BOOL("output-high"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_output_high_swnode = + SOFTWARE_NODE("hog-output-high", gpio_hog_output_high_properties, + &gpio_test_provider_swnode); + +static const u32 gpio_hog_output_low_gpios[] = { + GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_output_low_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_low_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-output-low"), + PROPERTY_ENTRY_BOOL("output-low"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_output_low_swnode = + SOFTWARE_NODE("hog-output-low", gpio_hog_output_low_properties, + &gpio_test_provider_swnode); + +static const u32 gpio_hog_input_gpios[] = { + GPIO_HOG_INPUT_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_input_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_input_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-input"), + PROPERTY_ENTRY_BOOL("input"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_input_swnode = + SOFTWARE_NODE("hog-input", gpio_hog_input_properties, + &gpio_test_provider_swnode); + +static const struct software_node *const gpio_hog_swnodes[] = { + &gpio_test_provider_swnode, + &gpio_hog_output_high_swnode, + &gpio_hog_output_low_swnode, + &gpio_hog_input_swnode, + NULL +}; + +/* + * Bring up the provider with a single hog child registered and verify both + * that the line was configured with the expected direction and that it is now + * exclusively owned (a consumer asking for the same line fails to bind). + * + * The provider node is referenced by the device through its fwnode rather than + * being handed to .swnode, so the device takes no software node reference of + * its own. Both the provider and the hog child are therefore test-managed and + * torn down (child first) once the test case completes. + */ +static void gpio_hog_assert(struct kunit *test, unsigned int offset, + int expected_direction) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + struct gpio_desc *desc; + bool bound = true; + int ret; + + fwnode = software_node_fwnode(&gpio_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + + /* The hog must have configured the line with the expected direction. */ + struct gpio_device *gdev __free(gpio_device_put) = + gpio_device_find_by_label(GPIO_CONSUMER_NAME); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gdev); + + desc = gpio_device_get_desc(gdev, offset); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = gpiod_get_direction(desc); + KUNIT_ASSERT_EQ(test, ret, expected_direction); + + /* A hogged line is owned exclusively, so a consumer must fail to bind. */ + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + offset, GPIO_ACTIVE_HIGH); + properties[1] = (struct property_entry){ }; + + pdevinfo = (struct platform_device_info){ + .name = GPIO_SWNODE_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_swnode_pdata_template, + .size_data = sizeof(gpio_swnode_pdata_template), + .properties = properties, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&pdev->dev); + KUNIT_ASSERT_FALSE(test, pdata->gpio_ok); + KUNIT_ASSERT_EQ(test, pdata->errno, -EBUSY); +} + +static void gpio_hog_output_high(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_LINE_DIRECTION_OUT); +} + +static void gpio_hog_output_low(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_LINE_DIRECTION_OUT); +} + +static void gpio_hog_input(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_INPUT_OFFSET, GPIO_LINE_DIRECTION_IN); +} + +static int gpio_hog_suite_init(struct kunit_suite *suite) +{ + return software_node_register_node_group(gpio_hog_swnodes); +} + +static void gpio_hog_suite_exit(struct kunit_suite *suite) +{ + software_node_unregister_node_group(gpio_hog_swnodes); +} + +static struct kunit_case gpio_swnode_hog_tests[] = { + KUNIT_CASE(gpio_hog_output_high), + KUNIT_CASE(gpio_hog_output_low), + KUNIT_CASE(gpio_hog_input), + { } +}; + +static struct kunit_suite gpio_swnode_hog_test_suite = { + .name = "gpio-swnode-hog", + .test_cases = gpio_swnode_hog_tests, + .suite_init = gpio_hog_suite_init, + .suite_exit = gpio_hog_suite_exit, + .init = gpio_swnode_register_drivers, +}; + kunit_test_suites( &gpio_swnode_lookup_test_suite, &gpio_unbind_with_consumers_test_suite, + &gpio_swnode_hog_test_suite, ); MODULE_DESCRIPTION("Test module for the GPIO subsystem"); |
