runtime PM ops and PCMCIA bus
Dominik Brodowski
linux at dominikbrodowski.net
Sun Mar 21 18:45:46 EDT 2010
Hey!
On the PCMCIA bus, implementing runtime PM "should" be simple.
However, there's a major caveat: the bus must be able to
force devices to suspend, or force them to resume during
runtime. Let's discuss this in detail:
(1) Converting the PCMCIA bus to dev_pm_ops seems easy:
pcmcia_dev_suspend() needs to get rid of its second argument,
then it's just a
SET_SYSTEM_SLEEP_PM_OPS(pcmcia_dev_suspend, pcmcia_dev_resume)
(2) Whenever two functions local to drivers/pcmcia/ds.c --
runtime_suspend() or runtime_resume() -- are called,
we need to force a synchronous call to pcmcia_dev_suspend(),
or to pcmcia_dev_resume(). Currently, we do this by taking
device_lock() and calling the function manually.
(3) However, it'd be much nicer to be able to use the new runtime
PM interface. As the PCMCIA bus can always suspend,
pcmcia_dev_idle() might only contain a call to
pm_schedule_suspend(dev, 0) - or pm_runtime_suspend() ?
To enable it, we should add
pm_runtime_set_active(&p_dev->dev);
pm_runtime_enable(&p_dev->dev);
in pcmcia_device_add().
(4) What is then to be done to modify runtime_suspend() / runtime_resume() ?
Is it:
+ static int runtime_suspend(struct device *dev)
+ {
+ pm_runtime_put_noidle(dev);
+ return pm_runtime_suspend(dev);
+ }
+
+ static int runtime_resume(struct device *dev)
+ {
+ return pm_runtime_get_sync(dev);
+ }
or more something like
+ static int runtime_suspend(struct device *dev)
+ {
+ return pm_runtime_put_sync(dev);
+ }
+
+ static int runtime_resume(struct device *dev)
+ {
+ return pm_runtime_get_sync(dev);
+ }
and modifying pcmcia_dev_idle() to call pm_runtime_suspend() instead of
pm_schedule_suspend()?
(5) Another problem I'm seeing with the put/get approach: there are three
different callpaths where runtime_suspend() / runtime_resume() might
be called.[*] However, each one decreases the counter, which might get < 0.
What to do? Using pm_runtime_suspended seems racy...
(6) As pcmcia_dev_resume() is used -- as per UNIVERSAL_DEV_PM_OPS -- what is
needed there to set the runtime PM state correctly if we do not wake up
the device during system sleep suspend?
Pseudo patch -- untested and probably broken [see at least (5) above]
attached. Any feedback is most welcome.
Best,
Dominik
[*] 1) by echoing the state into a device sysfs file.
2) by echoing the state into a socket sysfs file.
3) during "resets" of the socket -- we need to suspend devices
first, then physically reset the socket, then resume the devices
on the card.
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
index caca50e..551fcdb 100644
--- a/drivers/pcmcia/Kconfig
+++ b/drivers/pcmcia/Kconfig
@@ -20,6 +20,7 @@ if PCCARD
config PCMCIA
tristate "16-bit PCMCIA support"
select CRC32
+ select PM_RUNTIME
default y
---help---
This option enables support for 16-bit PCMCIA cards. Most older
diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c
index f4df4d0..ec9f56e 100644
--- a/drivers/pcmcia/ds.c
+++ b/drivers/pcmcia/ds.c
@@ -24,6 +24,7 @@
#include <linux/firmware.h>
#include <linux/kref.h>
#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
@@ -560,6 +561,9 @@ struct pcmcia_device *pcmcia_device_add(struct pcmcia_socket *s, unsigned int fu
if (device_register(&p_dev->dev))
goto err_unreg;
+ pm_runtime_set_active(&p_dev->dev);
+ pm_runtime_enable(&p_dev->dev);
+
return p_dev;
err_unreg:
@@ -952,28 +956,24 @@ static int pcmcia_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
#endif
/************************ runtime PM support ***************************/
-
-static int pcmcia_dev_suspend(struct device *dev, pm_message_t state);
-static int pcmcia_dev_resume(struct device *dev);
-
static int runtime_suspend(struct device *dev)
{
- int rc;
+ int ret;
- device_lock(dev);
- rc = pcmcia_dev_suspend(dev, PMSG_SUSPEND);
- device_unlock(dev);
- return rc;
+ dev_dbg(dev, "runtime_suspend\n");
+ ret = pm_runtime_put_sync(dev);
+ dev_dbg(dev, "runtime_suspend %d\n", ret);
+ return (ret >=0 ? 0 : ret);
}
static int runtime_resume(struct device *dev)
{
- int rc;
+ int ret;
- device_lock(dev);
- rc = pcmcia_dev_resume(dev);
- device_unlock(dev);
- return rc;
+ dev_dbg(dev, "runtime_resume\n");
+ ret = pm_runtime_get_sync(dev);
+ dev_dbg(dev, "runtime_resume %d\n", ret);
+ return (ret >=0 ? 0 : ret);
}
/************************ per-device sysfs output ***************************/
@@ -1085,7 +1085,7 @@ static struct device_attribute pcmcia_dev_attrs[] = {
/* PM support, also needed for reset */
-static int pcmcia_dev_suspend(struct device *dev, pm_message_t state)
+static int pcmcia_dev_suspend(struct device *dev)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
struct pcmcia_driver *p_drv = NULL;
@@ -1406,6 +1406,19 @@ static struct class_interface pcmcia_bus_interface __refdata = {
};
+static int pcmcia_dev_idle(struct device *dev)
+{
+ int ret = pm_runtime_suspend(dev);
+
+ /* we can always suspend */
+ dev_dbg(dev, "idle callback -- pm_runtime_suspend returned %d\n", ret);
+
+ return ret;
+}
+
+UNIVERSAL_DEV_PM_OPS(pcmcia_bus_pm_ops,
+ pcmcia_dev_suspend, pcmcia_dev_resume, pcmcia_dev_idle);
+
struct bus_type pcmcia_bus_type = {
.name = "pcmcia",
.uevent = pcmcia_bus_uevent,
@@ -1413,8 +1426,7 @@ struct bus_type pcmcia_bus_type = {
.dev_attrs = pcmcia_dev_attrs,
.probe = pcmcia_device_probe,
.remove = pcmcia_device_remove,
- .suspend = pcmcia_dev_suspend,
- .resume = pcmcia_dev_resume,
+ .pm = &pcmcia_bus_pm_ops,
};
More information about the linux-pcmcia
mailing list