bas_gigaset: suspend support

Add basic suspend/resume support to the bas_gigaset ISDN driver for the
Siemens Gigaset SX255 series of ISDN DECT bases.

Only the USB aspects are handled so far; the ISDN subsystem is not notified in
any way, for lack of information about how to do that.  The driver will refuse
to suspend if a connection is active.

Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Cc: Greg KH <gregkh@suse.de>
Cc: Hansjoerg Lipp <hjlipp@web.de>
Cc: Karsten Keil <kkeil@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Tilman Schmidt 2008-02-06 01:38:26 -08:00 committed by Linus Torvalds
parent c652cbd8ee
commit 024fd299ba

View File

@ -73,6 +73,14 @@ static int gigaset_probe(struct usb_interface *interface,
/* Function will be called if the device is unplugged */ /* Function will be called if the device is unplugged */
static void gigaset_disconnect(struct usb_interface *interface); static void gigaset_disconnect(struct usb_interface *interface);
/* functions called before/after suspend */
static int gigaset_suspend(struct usb_interface *intf, pm_message_t message);
static int gigaset_resume(struct usb_interface *intf);
/* functions called before/after device reset */
static int gigaset_pre_reset(struct usb_interface *intf);
static int gigaset_post_reset(struct usb_interface *intf);
static int atread_submit(struct cardstate *, int); static int atread_submit(struct cardstate *, int);
static void stopurbs(struct bas_bc_state *); static void stopurbs(struct bas_bc_state *);
static int req_submit(struct bc_state *, int, int, int); static int req_submit(struct bc_state *, int, int, int);
@ -107,6 +115,7 @@ struct bas_cardstate {
spinlock_t lock; /* locks all following */ spinlock_t lock; /* locks all following */
atomic_t basstate; /* bitmap (BS_*) */ atomic_t basstate; /* bitmap (BS_*) */
int pending; /* uncompleted base request */ int pending; /* uncompleted base request */
wait_queue_head_t waitqueue;
int rcvbuf_size; /* size of AT receive buffer */ int rcvbuf_size; /* size of AT receive buffer */
/* 0: no receive in progress */ /* 0: no receive in progress */
int retry_cmd_in; /* receive req retry count */ int retry_cmd_in; /* receive req retry count */
@ -121,6 +130,7 @@ struct bas_cardstate {
#define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ #define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */
#define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ #define BS_ATRDPEND 0x040 /* urb_cmd_in in use */
#define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ #define BS_ATWRPEND 0x080 /* urb_cmd_out in use */
#define BS_SUSPEND 0x100 /* USB port suspended */
static struct gigaset_driver *driver = NULL; static struct gigaset_driver *driver = NULL;
@ -132,6 +142,11 @@ static struct usb_driver gigaset_usb_driver = {
.probe = gigaset_probe, .probe = gigaset_probe,
.disconnect = gigaset_disconnect, .disconnect = gigaset_disconnect,
.id_table = gigaset_table, .id_table = gigaset_table,
.suspend = gigaset_suspend,
.resume = gigaset_resume,
.reset_resume = gigaset_post_reset,
.pre_reset = gigaset_pre_reset,
.post_reset = gigaset_post_reset,
}; };
/* get message text for usb_submit_urb return code /* get message text for usb_submit_urb return code
@ -465,6 +480,7 @@ static void read_ctrl_callback(struct urb *urb)
int rc; int rc;
update_basstate(ucs, 0, BS_ATRDPEND); update_basstate(ucs, 0, BS_ATRDPEND);
wake_up(&ucs->waitqueue);
if (!ucs->rcvbuf_size) { if (!ucs->rcvbuf_size) {
dev_warn(cs->dev, "%s: no receive in progress\n", __func__); dev_warn(cs->dev, "%s: no receive in progress\n", __func__);
@ -551,17 +567,28 @@ static void read_ctrl_callback(struct urb *urb)
static int atread_submit(struct cardstate *cs, int timeout) static int atread_submit(struct cardstate *cs, int timeout)
{ {
struct bas_cardstate *ucs = cs->hw.bas; struct bas_cardstate *ucs = cs->hw.bas;
int basstate;
int ret; int ret;
gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)",
ucs->rcvbuf_size); ucs->rcvbuf_size);
if (update_basstate(ucs, BS_ATRDPEND, 0) & BS_ATRDPEND) { basstate = update_basstate(ucs, BS_ATRDPEND, 0);
if (basstate & BS_ATRDPEND) {
dev_err(cs->dev, dev_err(cs->dev,
"could not submit HD_READ_ATMESSAGE: URB busy\n"); "could not submit HD_READ_ATMESSAGE: URB busy\n");
return -EBUSY; return -EBUSY;
} }
if (basstate & BS_SUSPEND) {
dev_notice(cs->dev,
"HD_READ_ATMESSAGE not submitted, "
"suspend in progress\n");
update_basstate(ucs, 0, BS_ATRDPEND);
/* treat like disconnect */
return -ENODEV;
}
ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ; ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ;
ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE; ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE;
ucs->dr_cmd_in.wValue = 0; ucs->dr_cmd_in.wValue = 0;
@ -747,6 +774,7 @@ static void read_int_callback(struct urb *urb)
} }
check_pending(ucs); check_pending(ucs);
wake_up(&ucs->waitqueue);
resubmit: resubmit:
rc = usb_submit_urb(urb, GFP_ATOMIC); rc = usb_submit_urb(urb, GFP_ATOMIC);
@ -1416,6 +1444,8 @@ static void req_timeout(unsigned long data)
dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n", dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n",
pending); pending);
} }
wake_up(&ucs->waitqueue);
} }
/* write_ctrl_callback /* write_ctrl_callback
@ -1456,7 +1486,9 @@ static void write_ctrl_callback(struct urb *urb)
break; break;
default: /* any failure */ default: /* any failure */
if (++ucs->retry_ctrl > BAS_RETRY) { /* don't retry if suspend requested */
if (++ucs->retry_ctrl > BAS_RETRY ||
(atomic_read(&ucs->basstate) & BS_SUSPEND)) {
dev_err(&ucs->interface->dev, dev_err(&ucs->interface->dev,
"control request 0x%02x failed: %s\n", "control request 0x%02x failed: %s\n",
ucs->dr_ctrl.bRequest, ucs->dr_ctrl.bRequest,
@ -1485,6 +1517,7 @@ static void write_ctrl_callback(struct urb *urb)
del_timer(&ucs->timer_ctrl); del_timer(&ucs->timer_ctrl);
ucs->pending = 0; ucs->pending = 0;
spin_unlock_irqrestore(&ucs->lock, flags); spin_unlock_irqrestore(&ucs->lock, flags);
wake_up(&ucs->waitqueue);
} }
/* req_submit /* req_submit
@ -1570,6 +1603,14 @@ static int gigaset_init_bchannel(struct bc_state *bcs)
return -ENODEV; return -ENODEV;
} }
if (atomic_read(&cs->hw.bas->basstate) & BS_SUSPEND) {
dev_notice(cs->dev,
"not starting isochronous I/O, "
"suspend in progress\n");
spin_unlock_irqrestore(&cs->lock, flags);
return -EHOSTUNREACH;
}
if ((ret = starturbs(bcs)) < 0) { if ((ret = starturbs(bcs)) < 0) {
dev_err(cs->dev, dev_err(cs->dev,
"could not start isochronous I/O for channel B%d: %s\n", "could not start isochronous I/O for channel B%d: %s\n",
@ -1682,6 +1723,7 @@ static void write_command_callback(struct urb *urb)
unsigned long flags; unsigned long flags;
update_basstate(ucs, 0, BS_ATWRPEND); update_basstate(ucs, 0, BS_ATWRPEND);
wake_up(&ucs->waitqueue);
/* check status */ /* check status */
switch (status) { switch (status) {
@ -1705,6 +1747,13 @@ static void write_command_callback(struct urb *urb)
ucs->retry_cmd_out); ucs->retry_cmd_out);
break; break;
} }
if (atomic_read(&ucs->basstate) & BS_SUSPEND) {
dev_warn(cs->dev,
"command write: %s, "
"won't retry - suspend requested\n",
get_usb_statmsg(status));
break;
}
if (cs->cmdbuf == NULL) { if (cs->cmdbuf == NULL) {
dev_warn(cs->dev, dev_warn(cs->dev,
"command write: %s, " "command write: %s, "
@ -1813,6 +1862,12 @@ static int start_cbsend(struct cardstate *cs)
int rc; int rc;
int retval = 0; int retval = 0;
/* check if suspend requested */
if (atomic_read(&ucs->basstate) & BS_SUSPEND) {
gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "suspending");
return -EHOSTUNREACH;
}
/* check if AT channel is open */ /* check if AT channel is open */
if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) { if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) {
gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "AT channel not open"); gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "AT channel not open");
@ -2099,6 +2154,7 @@ static int gigaset_initcshw(struct cardstate *cs)
init_timer(&ucs->timer_ctrl); init_timer(&ucs->timer_ctrl);
init_timer(&ucs->timer_atrdy); init_timer(&ucs->timer_atrdy);
init_timer(&ucs->timer_cmd_in); init_timer(&ucs->timer_cmd_in);
init_waitqueue_head(&ucs->waitqueue);
return 1; return 1;
} }
@ -2311,6 +2367,112 @@ static void gigaset_disconnect(struct usb_interface *interface)
gigaset_unassign(cs); gigaset_unassign(cs);
} }
/* gigaset_suspend
* This function is called before the USB connection is suspended.
*/
static int gigaset_suspend(struct usb_interface *intf, pm_message_t message)
{
struct cardstate *cs = usb_get_intfdata(intf);
struct bas_cardstate *ucs = cs->hw.bas;
int basstate;
int rc;
/* set suspend flag; this stops AT command/response traffic */
if (update_basstate(ucs, BS_SUSPEND, 0) & BS_SUSPEND) {
gig_dbg(DEBUG_SUSPEND, "already suspended");
return 0;
}
/* wait a bit for blocking conditions to go away */
rc = wait_event_timeout(ucs->waitqueue,
!(atomic_read(&ucs->basstate) &
(BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)),
BAS_TIMEOUT*HZ/10);
gig_dbg(DEBUG_SUSPEND, "wait_event_timeout() -> %d", rc);
/* check for conditions preventing suspend */
basstate = atomic_read(&ucs->basstate);
if (basstate & (BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)) {
dev_warn(cs->dev, "cannot suspend:\n");
if (basstate & BS_B1OPEN)
dev_warn(cs->dev, " B channel 1 open\n");
if (basstate & BS_B2OPEN)
dev_warn(cs->dev, " B channel 2 open\n");
if (basstate & BS_ATRDPEND)
dev_warn(cs->dev, " receiving AT reply\n");
if (basstate & BS_ATWRPEND)
dev_warn(cs->dev, " sending AT command\n");
update_basstate(ucs, 0, BS_SUSPEND);
return -EBUSY;
}
/* close AT channel if open */
if (basstate & BS_ATOPEN) {
gig_dbg(DEBUG_SUSPEND, "closing AT channel");
rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, 0);
if (rc) {
update_basstate(ucs, 0, BS_SUSPEND);
return rc;
}
wait_event_timeout(ucs->waitqueue, !ucs->pending,
BAS_TIMEOUT*HZ/10);
/* in case of timeout, proceed anyway */
}
/* kill all URBs and timers that might still be pending */
usb_kill_urb(ucs->urb_ctrl);
usb_kill_urb(ucs->urb_int_in);
del_timer_sync(&ucs->timer_ctrl);
gig_dbg(DEBUG_SUSPEND, "suspend complete");
return 0;
}
/* gigaset_resume
* This function is called after the USB connection has been resumed.
*/
static int gigaset_resume(struct usb_interface *intf)
{
struct cardstate *cs = usb_get_intfdata(intf);
struct bas_cardstate *ucs = cs->hw.bas;
int rc;
/* resubmit interrupt URB for spontaneous messages from base */
rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL);
if (rc) {
dev_err(cs->dev, "could not resubmit interrupt URB: %s\n",
get_usb_rcmsg(rc));
return rc;
}
/* clear suspend flag to reallow activity */
update_basstate(ucs, 0, BS_SUSPEND);
gig_dbg(DEBUG_SUSPEND, "resume complete");
return 0;
}
/* gigaset_pre_reset
* This function is called before the USB connection is reset.
*/
static int gigaset_pre_reset(struct usb_interface *intf)
{
/* handle just like suspend */
return gigaset_suspend(intf, PMSG_ON);
}
/* gigaset_post_reset
* This function is called after the USB connection has been reset.
*/
static int gigaset_post_reset(struct usb_interface *intf)
{
/* FIXME: send HD_DEVICE_INIT_ACK? */
/* resume operations */
return gigaset_resume(intf);
}
static const struct gigaset_ops gigops = { static const struct gigaset_ops gigops = {
gigaset_write_cmd, gigaset_write_cmd,
gigaset_write_room, gigaset_write_room,