linux/drivers/pnp/driver.c
Rene Herman 5d38998ed1 PNP: do not test PNP_DRIVER_RES_DO_NOT_CHANGE on suspend/resume
The PNP_DRIVER_RES_DO_NOT_CHANGE flag is meant to signify that the PNP core
should not change resources for the device -- not that it shouldn't
disable/enable the device on suspend/resume.

ALSA ISAPnP drivers set PNP_DRIVER_RES_DO_NOT_CHANAGE (0x0001) through
setting PNP_DRIVER_RES_DISABLE (0x0003).  The latter including the former
may in itself be considered rather unexpected but doesn't change that
suspend/resume wouldn't seem to have any business testing the flag.

As reported by Ondrej Zary for snd-cs4236, ALSA driven ISAPnP cards don't
survive swsusp hibernation with the resume skipping setting the resources
due to testing the flag -- the same test in the suspend path isn't enough
to keep hibernation from disabling the card it seems.

These tests were added (in 2005) by Piere Ossman in commit
68094e3251, "alsa: Improved PnP suspend
support" who doesn't remember why.  This deletes them.

Signed-off-by: Rene Herman <rene.herman@gmail.com>
Tested-by: Ondrej Zary <linux@rainbow-software.org>
Cc: Bjorn Helgaas <bjorn.helgaas@hp.com>
Cc: Pierre Ossman <drzeus@drzeus.cx>
Cc: Adam Belay <ambx1@neo.rr.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2008-02-06 10:41:20 -08:00

250 lines
5 KiB
C

/*
* driver.c - device id matching, driver model, etc.
*
* Copyright 2002 Adam Belay <ambx1@neo.rr.com>
*/
#include <linux/string.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/pnp.h>
#include "base.h"
static int compare_func(const char *ida, const char *idb)
{
int i;
/* we only need to compare the last 4 chars */
for (i = 3; i < 7; i++) {
if (ida[i] != 'X' &&
idb[i] != 'X' && toupper(ida[i]) != toupper(idb[i]))
return 0;
}
return 1;
}
int compare_pnp_id(struct pnp_id *pos, const char *id)
{
if (!pos || !id || (strlen(id) != 7))
return 0;
if (memcmp(id, "ANYDEVS", 7) == 0)
return 1;
while (pos) {
if (memcmp(pos->id, id, 3) == 0)
if (compare_func(pos->id, id) == 1)
return 1;
pos = pos->next;
}
return 0;
}
static const struct pnp_device_id *match_device(struct pnp_driver *drv,
struct pnp_dev *dev)
{
const struct pnp_device_id *drv_id = drv->id_table;
if (!drv_id)
return NULL;
while (*drv_id->id) {
if (compare_pnp_id(dev->id, drv_id->id))
return drv_id;
drv_id++;
}
return NULL;
}
int pnp_device_attach(struct pnp_dev *pnp_dev)
{
spin_lock(&pnp_lock);
if (pnp_dev->status != PNP_READY) {
spin_unlock(&pnp_lock);
return -EBUSY;
}
pnp_dev->status = PNP_ATTACHED;
spin_unlock(&pnp_lock);
return 0;
}
void pnp_device_detach(struct pnp_dev *pnp_dev)
{
spin_lock(&pnp_lock);
if (pnp_dev->status == PNP_ATTACHED)
pnp_dev->status = PNP_READY;
spin_unlock(&pnp_lock);
pnp_disable_dev(pnp_dev);
}
static int pnp_device_probe(struct device *dev)
{
int error;
struct pnp_driver *pnp_drv;
struct pnp_dev *pnp_dev;
const struct pnp_device_id *dev_id = NULL;
pnp_dev = to_pnp_dev(dev);
pnp_drv = to_pnp_driver(dev->driver);
error = pnp_device_attach(pnp_dev);
if (error < 0)
return error;
if (pnp_dev->active == 0) {
if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) {
error = pnp_activate_dev(pnp_dev);
if (error < 0)
return error;
}
} else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE)
== PNP_DRIVER_RES_DISABLE) {
error = pnp_disable_dev(pnp_dev);
if (error < 0)
return error;
}
error = 0;
if (pnp_drv->probe) {
dev_id = match_device(pnp_drv, pnp_dev);
if (dev_id != NULL)
error = pnp_drv->probe(pnp_dev, dev_id);
}
if (error >= 0) {
pnp_dev->driver = pnp_drv;
error = 0;
} else
goto fail;
dev_dbg(dev, "driver attached\n");
return error;
fail:
pnp_device_detach(pnp_dev);
return error;
}
static int pnp_device_remove(struct device *dev)
{
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
struct pnp_driver *drv = pnp_dev->driver;
if (drv) {
if (drv->remove)
drv->remove(pnp_dev);
pnp_dev->driver = NULL;
}
pnp_device_detach(pnp_dev);
return 0;
}
static int pnp_bus_match(struct device *dev, struct device_driver *drv)
{
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
struct pnp_driver *pnp_drv = to_pnp_driver(drv);
if (match_device(pnp_drv, pnp_dev) == NULL)
return 0;
return 1;
}
static int pnp_bus_suspend(struct device *dev, pm_message_t state)
{
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
struct pnp_driver *pnp_drv = pnp_dev->driver;
int error;
if (!pnp_drv)
return 0;
if (pnp_drv->suspend) {
error = pnp_drv->suspend(pnp_dev, state);
if (error)
return error;
}
if (pnp_can_disable(pnp_dev)) {
error = pnp_stop_dev(pnp_dev);
if (error)
return error;
}
if (pnp_dev->protocol && pnp_dev->protocol->suspend)
pnp_dev->protocol->suspend(pnp_dev, state);
return 0;
}
static int pnp_bus_resume(struct device *dev)
{
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
struct pnp_driver *pnp_drv = pnp_dev->driver;
int error;
if (!pnp_drv)
return 0;
if (pnp_dev->protocol && pnp_dev->protocol->resume)
pnp_dev->protocol->resume(pnp_dev);
if (pnp_can_write(pnp_dev)) {
error = pnp_start_dev(pnp_dev);
if (error)
return error;
}
if (pnp_drv->resume) {
error = pnp_drv->resume(pnp_dev);
if (error)
return error;
}
return 0;
}
struct bus_type pnp_bus_type = {
.name = "pnp",
.match = pnp_bus_match,
.probe = pnp_device_probe,
.remove = pnp_device_remove,
.suspend = pnp_bus_suspend,
.resume = pnp_bus_resume,
};
int pnp_register_driver(struct pnp_driver *drv)
{
pnp_dbg("the driver '%s' has been registered", drv->name);
drv->driver.name = drv->name;
drv->driver.bus = &pnp_bus_type;
return driver_register(&drv->driver);
}
void pnp_unregister_driver(struct pnp_driver *drv)
{
driver_unregister(&drv->driver);
pnp_dbg("the driver '%s' has been unregistered", drv->name);
}
/**
* pnp_add_id - adds an EISA id to the specified device
* @id: pointer to a pnp_id structure
* @dev: pointer to the desired device
*/
int pnp_add_id(struct pnp_id *id, struct pnp_dev *dev)
{
struct pnp_id *ptr;
id->next = NULL;
ptr = dev->id;
while (ptr && ptr->next)
ptr = ptr->next;
if (ptr)
ptr->next = id;
else
dev->id = id;
return 0;
}
EXPORT_SYMBOL(pnp_register_driver);
EXPORT_SYMBOL(pnp_unregister_driver);
EXPORT_SYMBOL(pnp_device_attach);
EXPORT_SYMBOL(pnp_device_detach);