diff mbox series

[V4,4/5] gpio: gpio-xilinx: Add support for suspend and resume

Message ID 1609936000-28378-5-git-send-email-srinivas.neeli@xilinx.com
State New
Headers show
Series gpio-xilinx: Update on xilinx gpio driver | expand

Commit Message

Srinivas Neeli Jan. 6, 2021, 12:26 p.m. UTC
Add support for suspend and resume, pm runtime suspend and resume.
Added free and request calls.

Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
---
Changes in V4:
-Adjust code to remove conflicts.
Changes in V3:
-Created new patch for suspend and resume.
---
 drivers/gpio/gpio-xilinx.c | 94 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 90 insertions(+), 4 deletions(-)

Comments

Linus Walleij Jan. 7, 2021, 9:46 a.m. UTC | #1
On Wed, Jan 6, 2021 at 1:27 PM Srinivas Neeli <srinivas.neeli@xilinx.com> wrote:

> Add support for suspend and resume, pm runtime suspend and resume.
> Added free and request calls.
>
> Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
(...)

> +static int xgpio_request(struct gpio_chip *chip, unsigned int offset)
> +{
> +       int ret;
> +
> +       ret = pm_runtime_get_sync(chip->parent);
> +       /*
> +        * If the device is already active pm_runtime_get() will return 1 on
> +        * success, but gpio_request still needs to return 0.
> +        */
> +       return ret < 0 ? ret : 0;
> +}

That's clever. I think more GPIO drivers should be doing it like this,
today I think most just ignore the return code.

> +static int __maybe_unused xgpio_suspend(struct device *dev)
> +static int __maybe_unused xgpio_resume(struct device *dev)

Those look good.


>  /**
>   * xgpio_remove - Remove method for the GPIO device.
>   * @pdev: pointer to the platform device
> @@ -289,7 +323,10 @@ static int xgpio_remove(struct platform_device *pdev)
>  {
>         struct xgpio_instance *gpio = platform_get_drvdata(pdev);
>
> -       clk_disable_unprepare(gpio->clk);
> +       if (!pm_runtime_suspended(&pdev->dev))
> +               clk_disable_unprepare(gpio->clk);
> +
> +       pm_runtime_disable(&pdev->dev);

This looks complex and racy. What if the device is resumed after you
executed the
first part of the statement.

The normal sequence is:

pm_runtime_get_sync(dev);
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);

This will make sure the clock is enabled and pm runtime is disabled.
After this you can unconditionally call clk_disable_unprepare(gpio->clk);

It is what you are doing on the errorpath of probe().

Yours,
Linus Walleij
Srinivas Neeli Jan. 8, 2021, 11:41 a.m. UTC | #2
Hi Linus,

> -----Original Message-----
> From: Linus Walleij <linus.walleij@linaro.org>
> Sent: Thursday, January 7, 2021 3:17 PM
> To: Srinivas Neeli <sneeli@xilinx.com>
> Cc: Bartosz Golaszewski <bgolaszewski@baylibre.com>; Michal Simek
> <michals@xilinx.com>; Shubhrajyoti Datta <shubhraj@xilinx.com>; Srinivas
> Goud <sgoud@xilinx.com>; Robert Hancock <hancock@sedsystems.ca>;
> William Breathitt Gray <vilhelm.gray@gmail.com>; Syed Nayyar Waris
> <syednwaris@gmail.com>; open list:GPIO SUBSYSTEM <linux-
> gpio@vger.kernel.org>; Linux ARM <linux-arm-kernel@lists.infradead.org>;
> linux-kernel@vger.kernel.org; git <git@xilinx.com>
> Subject: Re: [PATCH V4 4/5] gpio: gpio-xilinx: Add support for suspend and
> resume
> 
> On Wed, Jan 6, 2021 at 1:27 PM Srinivas Neeli <srinivas.neeli@xilinx.com>
> wrote:
> 
> > Add support for suspend and resume, pm runtime suspend and resume.
> > Added free and request calls.
> >
> > Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
> (...)
> 
> > +static int xgpio_request(struct gpio_chip *chip, unsigned int offset)
> > +{
> > +       int ret;
> > +
> > +       ret = pm_runtime_get_sync(chip->parent);
> > +       /*
> > +        * If the device is already active pm_runtime_get() will return 1 on
> > +        * success, but gpio_request still needs to return 0.
> > +        */
> > +       return ret < 0 ? ret : 0;
> > +}
> 
> That's clever. I think more GPIO drivers should be doing it like this, today I
> think most just ignore the return code.
> 
> > +static int __maybe_unused xgpio_suspend(struct device *dev) static
> > +int __maybe_unused xgpio_resume(struct device *dev)
> 
> Those look good.
> 
> 
> >  /**
> >   * xgpio_remove - Remove method for the GPIO device.
> >   * @pdev: pointer to the platform device @@ -289,7 +323,10 @@ static
> > int xgpio_remove(struct platform_device *pdev)  {
> >         struct xgpio_instance *gpio = platform_get_drvdata(pdev);
> >
> > -       clk_disable_unprepare(gpio->clk);
> > +       if (!pm_runtime_suspended(&pdev->dev))
> > +               clk_disable_unprepare(gpio->clk);
> > +
> > +       pm_runtime_disable(&pdev->dev);
> 
> This looks complex and racy. What if the device is resumed after you
> executed the first part of the statement.

Could you please explain more on this.
What is the need to call pm_runtime_get_sync(); in remove API ?

> 
> The normal sequence is:
> 
> pm_runtime_get_sync(dev);
> pm_runtime_put_noidle(dev);
> pm_runtime_disable(dev);
> 
> This will make sure the clock is enabled and pm runtime is disabled.
> After this you can unconditionally call clk_disable_unprepare(gpio->clk);
> 
> It is what you are doing on the errorpath of probe().
> 
> Yours,
> Linus Walleij
Linus Walleij Jan. 9, 2021, 12:25 a.m. UTC | #3
On Fri, Jan 8, 2021 at 12:41 PM Srinivas Neeli <sneeli@xilinx.com> wrote:
> > On Wed, Jan 6, 2021 at 1:27 PM Srinivas Neeli <srinivas.neeli@xilinx.com>
> > wrote:

> > >  /**
> > >   * xgpio_remove - Remove method for the GPIO device.
> > >   * @pdev: pointer to the platform device @@ -289,7 +323,10 @@ static
> > > int xgpio_remove(struct platform_device *pdev)  {
> > >         struct xgpio_instance *gpio = platform_get_drvdata(pdev);
> > >
> > > -       clk_disable_unprepare(gpio->clk);
> > > +       if (!pm_runtime_suspended(&pdev->dev))
> > > +               clk_disable_unprepare(gpio->clk);
> > > +
> > > +       pm_runtime_disable(&pdev->dev);
> >
> > This looks complex and racy. What if the device is resumed after you
> > executed the first part of the statement.
>
> Could you please explain more on this.
> What is the need to call pm_runtime_get_sync(); in remove API ?

I explain that on the lines right below your comment ;D

> > The normal sequence is:
> >
> > pm_runtime_get_sync(dev);
> > pm_runtime_put_noidle(dev);
> > pm_runtime_disable(dev);
> >
> > This will make sure the clock is enabled and pm runtime is disabled.
> > After this you can unconditionally call clk_disable_unprepare(gpio->clk);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Yours,
Linus Walleij
diff mbox series

Patch

diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c
index 437c50e72aa1..e47ae08167f8 100644
--- a/drivers/gpio/gpio-xilinx.c
+++ b/drivers/gpio/gpio-xilinx.c
@@ -17,6 +17,7 @@ 
 #include <linux/of_device.h>
 #include <linux/of_gpio.h>
 #include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
 /* Register Offset Definitions */
@@ -277,6 +278,39 @@  static void xgpio_save_regs(struct xgpio_instance *chip)
 		       chip->gpio_dir[1]);
 }
 
+static int xgpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+	int ret;
+
+	ret = pm_runtime_get_sync(chip->parent);
+	/*
+	 * If the device is already active pm_runtime_get() will return 1 on
+	 * success, but gpio_request still needs to return 0.
+	 */
+	return ret < 0 ? ret : 0;
+}
+
+static void xgpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+	pm_runtime_put(chip->parent);
+}
+
+static int __maybe_unused xgpio_suspend(struct device *dev)
+{
+	struct xgpio_instance *gpio = dev_get_drvdata(dev);
+	struct irq_data *data = irq_get_irq_data(gpio->irq);
+
+	if (!data) {
+		dev_err(dev, "irq_get_irq_data() failed\n");
+		return -EINVAL;
+	}
+
+	if (!irqd_is_wakeup_set(data))
+		return pm_runtime_force_suspend(dev);
+
+	return 0;
+}
+
 /**
  * xgpio_remove - Remove method for the GPIO device.
  * @pdev: pointer to the platform device
@@ -289,7 +323,10 @@  static int xgpio_remove(struct platform_device *pdev)
 {
 	struct xgpio_instance *gpio = platform_get_drvdata(pdev);
 
-	clk_disable_unprepare(gpio->clk);
+	if (!pm_runtime_suspended(&pdev->dev))
+		clk_disable_unprepare(gpio->clk);
+
+	pm_runtime_disable(&pdev->dev);
 
 	return 0;
 }
@@ -304,6 +341,46 @@  static void xgpio_irq_ack(struct irq_data *irq_data)
 {
 }
 
+static int __maybe_unused xgpio_resume(struct device *dev)
+{
+	struct xgpio_instance *gpio = dev_get_drvdata(dev);
+	struct irq_data *data = irq_get_irq_data(gpio->irq);
+
+	if (!data) {
+		dev_err(dev, "irq_get_irq_data() failed\n");
+		return -EINVAL;
+	}
+
+	if (!irqd_is_wakeup_set(data))
+		return pm_runtime_force_resume(dev);
+
+	return 0;
+}
+
+static int __maybe_unused xgpio_runtime_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct xgpio_instance *gpio = platform_get_drvdata(pdev);
+
+	clk_disable(gpio->clk);
+
+	return 0;
+}
+
+static int __maybe_unused xgpio_runtime_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct xgpio_instance *gpio = platform_get_drvdata(pdev);
+
+	return clk_enable(gpio->clk);
+}
+
+static const struct dev_pm_ops xgpio_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(xgpio_suspend, xgpio_resume)
+	SET_RUNTIME_PM_OPS(xgpio_runtime_suspend,
+			   xgpio_runtime_resume, NULL)
+};
+
 /**
  * xgpio_irq_mask - Write the specified signal of the GPIO device.
  * @irq_data: per IRQ and chip data passed down to chip functions
@@ -548,6 +625,8 @@  static int xgpio_probe(struct platform_device *pdev)
 	chip->gc.of_gpio_n_cells = cells;
 	chip->gc.get = xgpio_get;
 	chip->gc.set = xgpio_set;
+	chip->gc.request = xgpio_request;
+	chip->gc.free = xgpio_free;
 	chip->gc.set_multiple = xgpio_set_multiple;
 
 	chip->gc.label = dev_name(&pdev->dev);
@@ -567,6 +646,9 @@  static int xgpio_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "Failed to prepare clk\n");
 		return status;
 	}
+	pm_runtime_get_noresume(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
 
 	xgpio_save_regs(chip);
 
@@ -591,7 +673,7 @@  static int xgpio_probe(struct platform_device *pdev)
 				     GFP_KERNEL);
 	if (!girq->parents) {
 		status = -ENOMEM;
-		goto err_unprepare_clk;
+		goto err_pm_put;
 	}
 	girq->parents[0] = chip->irq;
 	girq->default_type = IRQ_TYPE_NONE;
@@ -601,12 +683,15 @@  static int xgpio_probe(struct platform_device *pdev)
 	status = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
 	if (status) {
 		dev_err(&pdev->dev, "failed to add GPIO chip\n");
-		goto err_unprepare_clk;
+		goto err_pm_put;
 	}
 
+	pm_runtime_put(&pdev->dev);
 	return 0;
 
-err_unprepare_clk:
+err_pm_put:
+	pm_runtime_disable(&pdev->dev);
+	pm_runtime_put_noidle(&pdev->dev);
 	clk_disable_unprepare(chip->clk);
 	return status;
 }
@@ -624,6 +709,7 @@  static struct platform_driver xgpio_plat_driver = {
 	.driver		= {
 			.name = "gpio-xilinx",
 			.of_match_table	= xgpio_of_match,
+			.pm = &xgpio_dev_pm_ops,
 	},
 };