Patchwork [v2,3/4] mtd: devices: elm: Low power transition support

login
register
mail settings
Submitter Philip, Avinash
Date Feb. 7, 2013, 12:36 p.m.
Message ID <1360240618-11094-4-git-send-email-avinashphilip@ti.com>
Download mbox | patch
Permalink /patch/218920/
State New
Headers show

Comments

Philip, Avinash - Feb. 7, 2013, 12:36 p.m.
In low power modes of AM335X platforms, peripherals power is cut off.
This patch supports low power sleep transition support for ELM driver.

Signed-off-by: Philip Avinash <avinashphilip@ti.com>
---
 drivers/mtd/devices/elm.c |   40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
Russell King - ARM Linux - Feb. 9, 2013, 10:22 a.m.
On Thu, Feb 07, 2013 at 06:06:57PM +0530, Philip Avinash wrote:
> +static int elm_suspend(struct device *dev)
> +{
> +	struct elm_info *info = dev_get_drvdata(dev);
> +	wait_queue_head_t wq;
> +	DECLARE_WAITQUEUE(wait, current);
> +
> +	init_waitqueue_head(&wq);
> +	while (1) {
> +		/* Make sure that ELM not running */
> +		if (info->idle) {
> +			add_wait_queue(&wq, &wait);
> +			schedule();
> +			remove_wait_queue(&wq, &wait);
> +		} else {
> +			break;
> +		}
> +	}

The above code looks really wrong - it will just spin endlessly with the
waitqueues doing nothing useful.  What are you trying to do here?
Philip, Avinash - Feb. 13, 2013, 11:42 a.m.
On Sat, Feb 09, 2013 at 15:52:44, Russell King - ARM Linux wrote:
> On Thu, Feb 07, 2013 at 06:06:57PM +0530, Philip Avinash wrote:
> > +static int elm_suspend(struct device *dev)
> > +{
> > +	struct elm_info *info = dev_get_drvdata(dev);
> > +	wait_queue_head_t wq;
> > +	DECLARE_WAITQUEUE(wait, current);
> > +
> > +	init_waitqueue_head(&wq);
> > +	while (1) {
> > +		/* Make sure that ELM not running */
> > +		if (info->idle) {
> > +			add_wait_queue(&wq, &wait);
> > +			schedule();
> > +			remove_wait_queue(&wq, &wait);
> > +		} else {
> > +			break;
> > +		}
> > +	}
> 
> The above code looks really wrong - it will just spin endlessly with the
> waitqueues doing nothing useful.  What are you trying to do here?

The intention of waitqeue is to make the suspend process really wait till
the ELM module finishes current activity. Although this type of protection
already achieved in mtd layer using nand_suspend(), this one is particularly
required for ELM module to really make sure that *any pending* corrections to
finish really before gone to suspend.

If suspend activity initiated and NAND driver requested service of ELM
module for error correction, then suspend activity has to wait till
(info->idle == false), so that ELM module finishes activity.

I can think of adding different states to ELM module and take action.
I will add following states.

RUNNING
SUSPEND

Thanks
Avinash

>
Russell King - ARM Linux - Feb. 13, 2013, 12:43 p.m.
On Wed, Feb 13, 2013 at 11:42:01AM +0000, Philip, Avinash wrote:
> On Sat, Feb 09, 2013 at 15:52:44, Russell King - ARM Linux wrote:
> > On Thu, Feb 07, 2013 at 06:06:57PM +0530, Philip Avinash wrote:
> > > +static int elm_suspend(struct device *dev)
> > > +{
> > > +	struct elm_info *info = dev_get_drvdata(dev);
> > > +	wait_queue_head_t wq;
> > > +	DECLARE_WAITQUEUE(wait, current);
> > > +
> > > +	init_waitqueue_head(&wq);
> > > +	while (1) {
> > > +		/* Make sure that ELM not running */
> > > +		if (info->idle) {
> > > +			add_wait_queue(&wq, &wait);
> > > +			schedule();
> > > +			remove_wait_queue(&wq, &wait);
> > > +		} else {
> > > +			break;
> > > +		}
> > > +	}
> > 
> > The above code looks really wrong - it will just spin endlessly with the
> > waitqueues doing nothing useful.  What are you trying to do here?
> 
> The intention of waitqeue is to make the suspend process really wait till
> the ELM module finishes current activity. Although this type of protection
> already achieved in mtd layer using nand_suspend(), this one is particularly
> required for ELM module to really make sure that *any pending* corrections to
> finish really before gone to suspend.

I don't think you understand what's going on with the above, and why the
above is completely ineffective.

1. Your wait queue head is declared on stack, and there's no references
   to it outside of this function.  So _nothing_ can activate the wait
   queue.
2. You're not changing the current thread's status away from TASK_RUNNING,
   so schedule() will either return immediately or it will schedule another
   task if it's time for this one to be preempted.

In other words, the above can be rewritten as:

	while (info->idle)
		schedule();

and it will still have the same effect.

Now, if you want to be kinder to the system, then you should use a
wait queue properly.  Put the waitqueue head in struct elm_info.  Use
wait_event() here.  And call wake_up() where you set info->idle to
false.
Philip, Avinash - Feb. 19, 2013, 12:48 p.m.
On Wed, Feb 13, 2013 at 18:13:03, Russell King - ARM Linux wrote:
> On Wed, Feb 13, 2013 at 11:42:01AM +0000, Philip, Avinash wrote:
> > On Sat, Feb 09, 2013 at 15:52:44, Russell King - ARM Linux wrote:
> > > On Thu, Feb 07, 2013 at 06:06:57PM +0530, Philip Avinash wrote:
> > > > +static int elm_suspend(struct device *dev)
> > > > +{
> > > > +	struct elm_info *info = dev_get_drvdata(dev);
> > > > +	wait_queue_head_t wq;
> > > > +	DECLARE_WAITQUEUE(wait, current);
> > > > +
> > > > +	init_waitqueue_head(&wq);
> > > > +	while (1) {
> > > > +		/* Make sure that ELM not running */
> > > > +		if (info->idle) {
> > > > +			add_wait_queue(&wq, &wait);
> > > > +			schedule();
> > > > +			remove_wait_queue(&wq, &wait);
> > > > +		} else {
> > > > +			break;
> > > > +		}
> > > > +	}
> > > 
> > > The above code looks really wrong - it will just spin endlessly with the
> > > waitqueues doing nothing useful.  What are you trying to do here?
> > 
> > The intention of waitqeue is to make the suspend process really wait till
> > the ELM module finishes current activity. Although this type of protection
> > already achieved in mtd layer using nand_suspend(), this one is particularly
> > required for ELM module to really make sure that *any pending* corrections to
> > finish really before gone to suspend.
> 
> I don't think you understand what's going on with the above, and why the
> above is completely ineffective.
> 
> 1. Your wait queue head is declared on stack, and there's no references
>    to it outside of this function.  So _nothing_ can activate the wait
>    queue.
> 2. You're not changing the current thread's status away from TASK_RUNNING,
>    so schedule() will either return immediately or it will schedule another
>    task if it's time for this one to be preempted.
> 
> In other words, the above can be rewritten as:
> 
> 	while (info->idle)
> 		schedule();
> 
> and it will still have the same effect.
> 
> Now, if you want to be kinder to the system, then you should use a
> wait queue properly.  Put the waitqueue head in struct elm_info.  Use
> wait_event() here.  And call wake_up() where you set info->idle to
> false.

I understood the issue. Thanks for the detailed explanation.

It seems the entire mechanism of ELM module state check is not required.
The ELM suspend procedure initiated only after the current MTD transaction
finishes and is ensured in MTD class driver.

So I can simply disable ELM module in suspend without any check.

Thanks
Avinash

>

Patch

diff --git a/drivers/mtd/devices/elm.c b/drivers/mtd/devices/elm.c
index f034239..a9ac8f2 100644
--- a/drivers/mtd/devices/elm.c
+++ b/drivers/mtd/devices/elm.c
@@ -20,6 +20,7 @@ 
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/of.h>
+#include <linux/sched.h>
 #include <linux/pm_runtime.h>
 #include <linux/platform_data/elm.h>
 
@@ -62,6 +63,7 @@  struct elm_info {
 	struct completion elm_completion;
 	struct list_head list;
 	enum bch_ecc bch_type;
+	bool idle;
 };
 
 static LIST_HEAD(elm_devices);
@@ -86,9 +88,11 @@  void elm_config(struct device *dev, enum bch_ecc bch_type)
 	u32 reg_val;
 	struct elm_info *info = dev_get_drvdata(dev);
 
+	info->idle = true;
 	reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16);
 	elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val);
 	info->bch_type = bch_type;
+	info->idle = false;
 }
 EXPORT_SYMBOL(elm_config);
 
@@ -280,6 +284,7 @@  void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
 	struct elm_info *info = dev_get_drvdata(dev);
 	u32 reg_val;
 
+	info->idle = true;
 	/* Enable page mode interrupt */
 	reg_val = elm_read_reg(info, ELM_IRQSTATUS);
 	elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);
@@ -298,6 +303,7 @@  void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
 	reg_val = elm_read_reg(info, ELM_IRQENABLE);
 	elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);
 	elm_error_correction(info, err_vec);
+	info->idle = false;
 }
 EXPORT_SYMBOL(elm_decode_bch_error_page);
 
@@ -368,6 +374,7 @@  static int elm_probe(struct platform_device *pdev)
 	INIT_LIST_HEAD(&info->list);
 	list_add(&info->list, &elm_devices);
 	platform_set_drvdata(pdev, info);
+	info->idle = false;
 	return ret;
 }
 
@@ -379,6 +386,38 @@  static int elm_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static int elm_suspend(struct device *dev)
+{
+	struct elm_info *info = dev_get_drvdata(dev);
+	wait_queue_head_t wq;
+	DECLARE_WAITQUEUE(wait, current);
+
+	init_waitqueue_head(&wq);
+	while (1) {
+		/* Make sure that ELM not running */
+		if (info->idle) {
+			add_wait_queue(&wq, &wait);
+			schedule();
+			remove_wait_queue(&wq, &wait);
+		} else {
+			break;
+		}
+	}
+	pm_runtime_put_sync(dev);
+	return 0;
+}
+
+static int elm_resume(struct device *dev)
+{
+	struct elm_info *info = dev_get_drvdata(dev);
+
+	pm_runtime_get_sync(dev);
+	elm_config(dev, info->bch_type);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);
+
 #ifdef CONFIG_OF
 static const struct of_device_id elm_of_match[] = {
 	{ .compatible = "ti,am3352-elm" },
@@ -392,6 +431,7 @@  static struct platform_driver elm_driver = {
 		.name	= "elm",
 		.owner	= THIS_MODULE,
 		.of_match_table = of_match_ptr(elm_of_match),
+		.pm	= &elm_pm_ops,
 	},
 	.probe	= elm_probe,
 	.remove	= elm_remove,