Skip to contentSkip to conversation

gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume

PATCH Superseded Rosen Penev 3 participants 5 messages Jul 7 – Aug 4 GPIO

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume

The driver uses the legacy .suspend/.resume callbacks, but sets IRQCHIP_MASK_ON_SUSPEND on the irq_chip. During resume, the PM core runs dpm_resume_noirq() first, which calls irq_pm_resume() to unmask interrupts, and only then runs dpm_resume() which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL, GPIO_IO_CONF, mask registers).

This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.

Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via dev_pm_ops. The noirq phase runs before resume_device_irqs() on resume and after suspend_device_irqs() on suspend, ensuring GPIO registers are restored before interrupts are unmasked.

Assisted-by opencode:big-pickle
---
 drivers/gpio/gpio-mvebu.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)
drivers/gpio/gpio-mvebu.c +9 -6
index a556fdb267a9..1df763e60726 100644
@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = {
 	},
 };
 
-static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+static int mvebu_gpio_suspend(struct device *dev)
 {
-	struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+	struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
 	int i;
 
 	regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
@@ -1031,9 +1031,9 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
 	return 0;
 }
 
-static int mvebu_gpio_resume(struct platform_device *pdev)
+static int mvebu_gpio_resume(struct device *dev)
 {
-	struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+	struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
 	int i;
 
 	regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
@@ -1338,13 +1338,16 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 	return 0;
 }
 
+static const struct dev_pm_ops mvebu_gpio_pm_ops = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mvebu_gpio_suspend, mvebu_gpio_resume)
+};
+
 static struct platform_driver mvebu_gpio_driver = {
 	.driver		= {
 		.name		= "mvebu-gpio",
 		.of_match_table = mvebu_gpio_of_match,
+		.pm		= pm_sleep_ptr(&mvebu_gpio_pm_ops),
 	},
 	.probe		= mvebu_gpio_probe,
-	.suspend        = mvebu_gpio_suspend,
-	.resume         = mvebu_gpio_resume,
 };
 builtin_platform_driver(mvebu_gpio_driver);
Signature · 1 line
2.55.0
#1 lore raw
Quoted 27 lines · Rosen Penev

The driver uses the legacy .suspend/.resume callbacks, but sets IRQCHIP_MASK_ON_SUSPEND on the irq_chip. During resume, the PM core runs dpm_resume_noirq() first, which calls irq_pm_resume() to unmask interrupts, and only then runs dpm_resume() which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL, GPIO_IO_CONF, mask registers).

This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.

Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via dev_pm_ops. The noirq phase runs before resume_device_irqs() on resume and after suspend_device_irqs() on suspend, ensuring GPIO registers are restored before interrupts are unmasked.

Assisted-by opencode:big-pickle
---
 drivers/gpio/gpio-mvebu.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)
drivers/gpio/gpio-mvebu.c +0 -0
index a556fdb267a9..1df763e60726 100644

drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982

Quoted 5 lines
drivers/gpio/gpio-mvebu.c +1 -1
@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = {
 	},
 };
 
-static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+static int mvebu_gpio_suspend(struct device *dev)

Needs __maybe_unused for SET_NOIRQ_SYSTEM_SLEEP_PM_OPS().

Bart

#2 lore raw
Quoted 32 lines · Bartosz Golaszewski
Quoted 30 lines · Rosen Penev

The driver uses the legacy .suspend/.resume callbacks, but sets IRQCHIP_MASK_ON_SUSPEND on the irq_chip. During resume, the PM core runs dpm_resume_noirq() first, which calls irq_pm_resume() to unmask interrupts, and only then runs dpm_resume() which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL, GPIO_IO_CONF, mask registers).

This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.

Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via dev_pm_ops. The noirq phase runs before resume_device_irqs() on resume and after suspend_device_irqs() on suspend, ensuring GPIO registers are restored before interrupts are unmasked.

Assisted-by opencode:big-pickle
---
 drivers/gpio/gpio-mvebu.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)
drivers/gpio/gpio-mvebu.c +0 -0
index a556fdb267a9..1df763e60726 100644
@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = {
      },
 };
 
Quoted 4 lines

drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982

Quoted 2 lines
drivers/gpio/gpio-mvebu.c +1 -1
-static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+static int mvebu_gpio_suspend(struct device *dev)

Needs __maybe_unused for SET_NOIRQ_SYSTEM_SLEEP_PM_OPS().

I see __maybe_unused and #ifdef for code like this. Which is prefered?

Quoted 2 lines

Bart

#3 lore raw
Quoted 33 lines · Rosen Penev
Quoted 32 lines · Bartosz Golaszewski
Quoted 30 lines · Rosen Penev

The driver uses the legacy .suspend/.resume callbacks, but sets IRQCHIP_MASK_ON_SUSPEND on the irq_chip. During resume, the PM core runs dpm_resume_noirq() first, which calls irq_pm_resume() to unmask interrupts, and only then runs dpm_resume() which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL, GPIO_IO_CONF, mask registers).

This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.

Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via dev_pm_ops. The noirq phase runs before resume_device_irqs() on resume and after suspend_device_irqs() on suspend, ensuring GPIO registers are restored before interrupts are unmasked.

Assisted-by opencode:big-pickle
---
 drivers/gpio/gpio-mvebu.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)
drivers/gpio/gpio-mvebu.c +0 -0
index a556fdb267a9..1df763e60726 100644
@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = {
      },
 };
 
Quoted 5 lines
Quoted 4 lines

drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982

Quoted 2 lines
drivers/gpio/gpio-mvebu.c +1 -1
-static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+static int mvebu_gpio_suspend(struct device *dev)

Needs __maybe_unused for SET_NOIRQ_SYSTEM_SLEEP_PM_OPS().

I see __maybe_unused and #ifdef for code like this. Which is prefered?

__maybe_unused is cleaner. Please add some newlines between quoted parts of your emails and your responses for better readability. I'm finding myself looking for where your response starts.

Bart

#4 lore raw
In reply to Rosen Penev
kernel test robot noticed the following build warnings:

Hi Rosen,

kernel test robot noticed the following build warnings:

[auto build test WARNING on brgl/gpio/for-next]
[also build test WARNING on linusw-pinctrl/devel linusw-pinctrl/for-next linus/master v7.2-rc6 next-20260803] [If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Rosen-Penev/gpio-mvebu-convert-to-noirq-suspend-resume-to-prevent-interrupt-storm-on-resume/20260804-155102
base:   https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio/for-next
patch link:    https://lore.kernel.org/r/20260707233511.1272686-1-rosenp%40gmail.com
patch subject: [PATCH] gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume
config: alpha-allmodconfig (https://download.01.org/0day-ci/archive/20260804/[email protected]/config)
compiler: alpha-linux-gcc (GCC) 16.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260804/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> drivers/gpio/gpio-mvebu.c:1034:12: warning: 'mvebu_gpio_resume' defined but not used [-Wunused-function]
    1034 | static int mvebu_gpio_resume(struct device *dev)
         |            ^~~~~~~~~~~~~~~~~
>> drivers/gpio/gpio-mvebu.c:982:12: warning: 'mvebu_gpio_suspend' defined but not used [-Wunused-function]

982 | static int mvebu_gpio_suspend(struct device *dev)
| ^~~~~~~~~~~~~~~~~~

vim +/mvebu_gpio_resume +1034 drivers/gpio/gpio-mvebu.c

981

 > 982	static int mvebu_gpio_suspend(struct device *dev)
   983	{
   984		struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
   985		int i;
   986	
   987		regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
   988			    &mvchip->out_reg);
   989		regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
   990			    &mvchip->io_conf_reg);
   991		regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
   992			    &mvchip->blink_en_reg);
   993		regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
   994			    &mvchip->in_pol_reg);
   995	
   996		switch (mvchip->soc_variant) {
   997		case MVEBU_GPIO_SOC_VARIANT_ORION:
   998		case MVEBU_GPIO_SOC_VARIANT_A8K:
   999			regmap_read(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
  1000				    &mvchip->edge_mask_regs[0]);
  1001			regmap_read(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
  1002				    &mvchip->level_mask_regs[0]);
  1003			break;
  1004		case MVEBU_GPIO_SOC_VARIANT_MV78200:
  1005			for (i = 0; i < 2; i++) {
  1006				regmap_read(mvchip->regs,
  1007					    GPIO_EDGE_MASK_MV78200_OFF(i),
  1008					    &mvchip->edge_mask_regs[i]);
  1009				regmap_read(mvchip->regs,
  1010					    GPIO_LEVEL_MASK_MV78200_OFF(i),
  1011					    &mvchip->level_mask_regs[i]);
  1012			}
  1013			break;
  1014		case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
  1015			for (i = 0; i < 4; i++) {
  1016				regmap_read(mvchip->regs,
  1017					    GPIO_EDGE_MASK_ARMADAXP_OFF(i),
  1018					    &mvchip->edge_mask_regs[i]);
  1019				regmap_read(mvchip->regs,
  1020					    GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
  1021					    &mvchip->level_mask_regs[i]);
  1022			}
  1023			break;
  1024		default:
  1025			BUG();
  1026		}
  1027	
  1028		if (IS_REACHABLE(CONFIG_PWM) && mvchip->mvpwm)
  1029			mvebu_pwm_suspend(mvchip);
  1030	
  1031		return 0;
  1032	}
  1033	
> 1034	static int mvebu_gpio_resume(struct device *dev)

1035 {
1036 struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
1037 int i;
1038
1039 regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
1040 mvchip->out_reg);
1041 regmap_write(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
1042 mvchip->io_conf_reg);
1043 regmap_write(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
1044 mvchip->blink_en_reg);
1045 regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
1046 mvchip->in_pol_reg);
1047
1048 switch (mvchip->soc_variant) {
1049 case MVEBU_GPIO_SOC_VARIANT_ORION:
1050 case MVEBU_GPIO_SOC_VARIANT_A8K:
1051 regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
1052 mvchip->edge_mask_regs[0]);
1053 regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
1054 mvchip->level_mask_regs[0]);
1055 break;
1056 case MVEBU_GPIO_SOC_VARIANT_MV78200:
1057 for (i = 0; i < 2; i++) {
1058 regmap_write(mvchip->regs,
1059 GPIO_EDGE_MASK_MV78200_OFF(i),
1060 mvchip->edge_mask_regs[i]);
1061 regmap_write(mvchip->regs,
1062 GPIO_LEVEL_MASK_MV78200_OFF(i),
1063 mvchip->level_mask_regs[i]);
1064 }
1065 break;
1066 case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
1067 for (i = 0; i < 4; i++) {
1068 regmap_write(mvchip->regs,
1069 GPIO_EDGE_MASK_ARMADAXP_OFF(i),
1070 mvchip->edge_mask_regs[i]);
1071 regmap_write(mvchip->regs,
1072 GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
1073 mvchip->level_mask_regs[i]);
1074 }
1075 break;
1076 default:
1077 BUG();
1078 }
1079
1080 if (IS_REACHABLE(CONFIG_PWM) && mvchip->mvpwm)
1081 mvebu_pwm_resume(mvchip);
1082
1083 return 0;
1084 }
1085

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki