diff options
author | Tilman Schmidt <tilman@imap.cc> | 2008-02-06 04:38:26 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-06 13:41:11 -0500 |
commit | 024fd299ba6e933055fccf1bb1cc2e7bdc58bde6 (patch) | |
tree | 1cb59fb034b9e5a22621d6387b9c8ec317e7be76 /drivers/isdn/gigaset | |
parent | c652cbd8ee114307baab072e4e560dce5c5fb12a (diff) |
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>
Diffstat (limited to 'drivers/isdn/gigaset')
-rw-r--r-- | drivers/isdn/gigaset/bas-gigaset.c | 166 |
1 files changed, 164 insertions, 2 deletions
diff --git a/drivers/isdn/gigaset/bas-gigaset.c b/drivers/isdn/gigaset/bas-gigaset.c index d60a6510e92b..1c401b3f88e1 100644 --- a/drivers/isdn/gigaset/bas-gigaset.c +++ b/drivers/isdn/gigaset/bas-gigaset.c | |||
@@ -73,6 +73,14 @@ static int gigaset_probe(struct usb_interface *interface, | |||
73 | /* Function will be called if the device is unplugged */ | 73 | /* Function will be called if the device is unplugged */ |
74 | static void gigaset_disconnect(struct usb_interface *interface); | 74 | static void gigaset_disconnect(struct usb_interface *interface); |
75 | 75 | ||
76 | /* functions called before/after suspend */ | ||
77 | static int gigaset_suspend(struct usb_interface *intf, pm_message_t message); | ||
78 | static int gigaset_resume(struct usb_interface *intf); | ||
79 | |||
80 | /* functions called before/after device reset */ | ||
81 | static int gigaset_pre_reset(struct usb_interface *intf); | ||
82 | static int gigaset_post_reset(struct usb_interface *intf); | ||
83 | |||
76 | static int atread_submit(struct cardstate *, int); | 84 | static int atread_submit(struct cardstate *, int); |
77 | static void stopurbs(struct bas_bc_state *); | 85 | static void stopurbs(struct bas_bc_state *); |
78 | static int req_submit(struct bc_state *, int, int, int); | 86 | static int req_submit(struct bc_state *, int, int, int); |
@@ -107,6 +115,7 @@ struct bas_cardstate { | |||
107 | spinlock_t lock; /* locks all following */ | 115 | spinlock_t lock; /* locks all following */ |
108 | atomic_t basstate; /* bitmap (BS_*) */ | 116 | atomic_t basstate; /* bitmap (BS_*) */ |
109 | int pending; /* uncompleted base request */ | 117 | int pending; /* uncompleted base request */ |
118 | wait_queue_head_t waitqueue; | ||
110 | int rcvbuf_size; /* size of AT receive buffer */ | 119 | int rcvbuf_size; /* size of AT receive buffer */ |
111 | /* 0: no receive in progress */ | 120 | /* 0: no receive in progress */ |
112 | int retry_cmd_in; /* receive req retry count */ | 121 | int retry_cmd_in; /* receive req retry count */ |
@@ -121,6 +130,7 @@ struct bas_cardstate { | |||
121 | #define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ | 130 | #define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ |
122 | #define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ | 131 | #define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ |
123 | #define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ | 132 | #define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ |
133 | #define BS_SUSPEND 0x100 /* USB port suspended */ | ||
124 | 134 | ||
125 | 135 | ||
126 | static struct gigaset_driver *driver = NULL; | 136 | static struct gigaset_driver *driver = NULL; |
@@ -132,6 +142,11 @@ static struct usb_driver gigaset_usb_driver = { | |||
132 | .probe = gigaset_probe, | 142 | .probe = gigaset_probe, |
133 | .disconnect = gigaset_disconnect, | 143 | .disconnect = gigaset_disconnect, |
134 | .id_table = gigaset_table, | 144 | .id_table = gigaset_table, |
145 | .suspend = gigaset_suspend, | ||
146 | .resume = gigaset_resume, | ||
147 | .reset_resume = gigaset_post_reset, | ||
148 | .pre_reset = gigaset_pre_reset, | ||
149 | .post_reset = gigaset_post_reset, | ||
135 | }; | 150 | }; |
136 | 151 | ||
137 | /* get message text for usb_submit_urb return code | 152 | /* get message text for usb_submit_urb return code |
@@ -465,6 +480,7 @@ static void read_ctrl_callback(struct urb *urb) | |||
465 | int rc; | 480 | int rc; |
466 | 481 | ||
467 | update_basstate(ucs, 0, BS_ATRDPEND); | 482 | update_basstate(ucs, 0, BS_ATRDPEND); |
483 | wake_up(&ucs->waitqueue); | ||
468 | 484 | ||
469 | if (!ucs->rcvbuf_size) { | 485 | if (!ucs->rcvbuf_size) { |
470 | dev_warn(cs->dev, "%s: no receive in progress\n", __func__); | 486 | dev_warn(cs->dev, "%s: no receive in progress\n", __func__); |
@@ -551,17 +567,28 @@ static void read_ctrl_callback(struct urb *urb) | |||
551 | static int atread_submit(struct cardstate *cs, int timeout) | 567 | static int atread_submit(struct cardstate *cs, int timeout) |
552 | { | 568 | { |
553 | struct bas_cardstate *ucs = cs->hw.bas; | 569 | struct bas_cardstate *ucs = cs->hw.bas; |
570 | int basstate; | ||
554 | int ret; | 571 | int ret; |
555 | 572 | ||
556 | gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", | 573 | gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", |
557 | ucs->rcvbuf_size); | 574 | ucs->rcvbuf_size); |
558 | 575 | ||
559 | if (update_basstate(ucs, BS_ATRDPEND, 0) & BS_ATRDPEND) { | 576 | basstate = update_basstate(ucs, BS_ATRDPEND, 0); |
577 | if (basstate & BS_ATRDPEND) { | ||
560 | dev_err(cs->dev, | 578 | dev_err(cs->dev, |
561 | "could not submit HD_READ_ATMESSAGE: URB busy\n"); | 579 | "could not submit HD_READ_ATMESSAGE: URB busy\n"); |
562 | return -EBUSY; | 580 | return -EBUSY; |
563 | } | 581 | } |
564 | 582 | ||
583 | if (basstate & BS_SUSPEND) { | ||
584 | dev_notice(cs->dev, | ||
585 | "HD_READ_ATMESSAGE not submitted, " | ||
586 | "suspend in progress\n"); | ||
587 | update_basstate(ucs, 0, BS_ATRDPEND); | ||
588 | /* treat like disconnect */ | ||
589 | return -ENODEV; | ||
590 | } | ||
591 | |||
565 | ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ; | 592 | ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ; |
566 | ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE; | 593 | ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE; |
567 | ucs->dr_cmd_in.wValue = 0; | 594 | ucs->dr_cmd_in.wValue = 0; |
@@ -747,6 +774,7 @@ static void read_int_callback(struct urb *urb) | |||
747 | } | 774 | } |
748 | 775 | ||
749 | check_pending(ucs); | 776 | check_pending(ucs); |
777 | wake_up(&ucs->waitqueue); | ||
750 | 778 | ||
751 | resubmit: | 779 | resubmit: |
752 | rc = usb_submit_urb(urb, GFP_ATOMIC); | 780 | rc = usb_submit_urb(urb, GFP_ATOMIC); |
@@ -1416,6 +1444,8 @@ static void req_timeout(unsigned long data) | |||
1416 | dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n", | 1444 | dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n", |
1417 | pending); | 1445 | pending); |
1418 | } | 1446 | } |
1447 | |||
1448 | wake_up(&ucs->waitqueue); | ||
1419 | } | 1449 | } |
1420 | 1450 | ||
1421 | /* write_ctrl_callback | 1451 | /* write_ctrl_callback |
@@ -1456,7 +1486,9 @@ static void write_ctrl_callback(struct urb *urb) | |||
1456 | break; | 1486 | break; |
1457 | 1487 | ||
1458 | default: /* any failure */ | 1488 | default: /* any failure */ |
1459 | if (++ucs->retry_ctrl > BAS_RETRY) { | 1489 | /* don't retry if suspend requested */ |
1490 | if (++ucs->retry_ctrl > BAS_RETRY || | ||
1491 | (atomic_read(&ucs->basstate) & BS_SUSPEND)) { | ||
1460 | dev_err(&ucs->interface->dev, | 1492 | dev_err(&ucs->interface->dev, |
1461 | "control request 0x%02x failed: %s\n", | 1493 | "control request 0x%02x failed: %s\n", |
1462 | ucs->dr_ctrl.bRequest, | 1494 | ucs->dr_ctrl.bRequest, |
@@ -1485,6 +1517,7 @@ static void write_ctrl_callback(struct urb *urb) | |||
1485 | del_timer(&ucs->timer_ctrl); | 1517 | del_timer(&ucs->timer_ctrl); |
1486 | ucs->pending = 0; | 1518 | ucs->pending = 0; |
1487 | spin_unlock_irqrestore(&ucs->lock, flags); | 1519 | spin_unlock_irqrestore(&ucs->lock, flags); |
1520 | wake_up(&ucs->waitqueue); | ||
1488 | } | 1521 | } |
1489 | 1522 | ||
1490 | /* req_submit | 1523 | /* req_submit |
@@ -1570,6 +1603,14 @@ static int gigaset_init_bchannel(struct bc_state *bcs) | |||
1570 | return -ENODEV; | 1603 | return -ENODEV; |
1571 | } | 1604 | } |
1572 | 1605 | ||
1606 | if (atomic_read(&cs->hw.bas->basstate) & BS_SUSPEND) { | ||
1607 | dev_notice(cs->dev, | ||
1608 | "not starting isochronous I/O, " | ||
1609 | "suspend in progress\n"); | ||
1610 | spin_unlock_irqrestore(&cs->lock, flags); | ||
1611 | return -EHOSTUNREACH; | ||
1612 | } | ||
1613 | |||
1573 | if ((ret = starturbs(bcs)) < 0) { | 1614 | if ((ret = starturbs(bcs)) < 0) { |
1574 | dev_err(cs->dev, | 1615 | dev_err(cs->dev, |
1575 | "could not start isochronous I/O for channel B%d: %s\n", | 1616 | "could not start isochronous I/O for channel B%d: %s\n", |
@@ -1682,6 +1723,7 @@ static void write_command_callback(struct urb *urb) | |||
1682 | unsigned long flags; | 1723 | unsigned long flags; |
1683 | 1724 | ||
1684 | update_basstate(ucs, 0, BS_ATWRPEND); | 1725 | update_basstate(ucs, 0, BS_ATWRPEND); |
1726 | wake_up(&ucs->waitqueue); | ||
1685 | 1727 | ||
1686 | /* check status */ | 1728 | /* check status */ |
1687 | switch (status) { | 1729 | switch (status) { |
@@ -1705,6 +1747,13 @@ static void write_command_callback(struct urb *urb) | |||
1705 | ucs->retry_cmd_out); | 1747 | ucs->retry_cmd_out); |
1706 | break; | 1748 | break; |
1707 | } | 1749 | } |
1750 | if (atomic_read(&ucs->basstate) & BS_SUSPEND) { | ||
1751 | dev_warn(cs->dev, | ||
1752 | "command write: %s, " | ||
1753 | "won't retry - suspend requested\n", | ||
1754 | get_usb_statmsg(status)); | ||
1755 | break; | ||
1756 | } | ||
1708 | if (cs->cmdbuf == NULL) { | 1757 | if (cs->cmdbuf == NULL) { |
1709 | dev_warn(cs->dev, | 1758 | dev_warn(cs->dev, |
1710 | "command write: %s, " | 1759 | "command write: %s, " |
@@ -1813,6 +1862,12 @@ static int start_cbsend(struct cardstate *cs) | |||
1813 | int rc; | 1862 | int rc; |
1814 | int retval = 0; | 1863 | int retval = 0; |
1815 | 1864 | ||
1865 | /* check if suspend requested */ | ||
1866 | if (atomic_read(&ucs->basstate) & BS_SUSPEND) { | ||
1867 | gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "suspending"); | ||
1868 | return -EHOSTUNREACH; | ||
1869 | } | ||
1870 | |||
1816 | /* check if AT channel is open */ | 1871 | /* check if AT channel is open */ |
1817 | if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) { | 1872 | if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) { |
1818 | gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "AT channel not open"); | 1873 | gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "AT channel not open"); |
@@ -2099,6 +2154,7 @@ static int gigaset_initcshw(struct cardstate *cs) | |||
2099 | init_timer(&ucs->timer_ctrl); | 2154 | init_timer(&ucs->timer_ctrl); |
2100 | init_timer(&ucs->timer_atrdy); | 2155 | init_timer(&ucs->timer_atrdy); |
2101 | init_timer(&ucs->timer_cmd_in); | 2156 | init_timer(&ucs->timer_cmd_in); |
2157 | init_waitqueue_head(&ucs->waitqueue); | ||
2102 | 2158 | ||
2103 | return 1; | 2159 | return 1; |
2104 | } | 2160 | } |
@@ -2311,6 +2367,112 @@ static void gigaset_disconnect(struct usb_interface *interface) | |||
2311 | gigaset_unassign(cs); | 2367 | gigaset_unassign(cs); |
2312 | } | 2368 | } |
2313 | 2369 | ||
2370 | /* gigaset_suspend | ||
2371 | * This function is called before the USB connection is suspended. | ||
2372 | */ | ||
2373 | static int gigaset_suspend(struct usb_interface *intf, pm_message_t message) | ||
2374 | { | ||
2375 | struct cardstate *cs = usb_get_intfdata(intf); | ||
2376 | struct bas_cardstate *ucs = cs->hw.bas; | ||
2377 | int basstate; | ||
2378 | int rc; | ||
2379 | |||
2380 | /* set suspend flag; this stops AT command/response traffic */ | ||
2381 | if (update_basstate(ucs, BS_SUSPEND, 0) & BS_SUSPEND) { | ||
2382 | gig_dbg(DEBUG_SUSPEND, "already suspended"); | ||
2383 | return 0; | ||
2384 | } | ||
2385 | |||
2386 | /* wait a bit for blocking conditions to go away */ | ||
2387 | rc = wait_event_timeout(ucs->waitqueue, | ||
2388 | !(atomic_read(&ucs->basstate) & | ||
2389 | (BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)), | ||
2390 | BAS_TIMEOUT*HZ/10); | ||
2391 | gig_dbg(DEBUG_SUSPEND, "wait_event_timeout() -> %d", rc); | ||
2392 | |||
2393 | /* check for conditions preventing suspend */ | ||
2394 | basstate = atomic_read(&ucs->basstate); | ||
2395 | if (basstate & (BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)) { | ||
2396 | dev_warn(cs->dev, "cannot suspend:\n"); | ||
2397 | if (basstate & BS_B1OPEN) | ||
2398 | dev_warn(cs->dev, " B channel 1 open\n"); | ||
2399 | if (basstate & BS_B2OPEN) | ||
2400 | dev_warn(cs->dev, " B channel 2 open\n"); | ||
2401 | if (basstate & BS_ATRDPEND) | ||
2402 | dev_warn(cs->dev, " receiving AT reply\n"); | ||
2403 | if (basstate & BS_ATWRPEND) | ||
2404 | dev_warn(cs->dev, " sending AT command\n"); | ||
2405 | update_basstate(ucs, 0, BS_SUSPEND); | ||
2406 | return -EBUSY; | ||
2407 | } | ||
2408 | |||
2409 | /* close AT channel if open */ | ||
2410 | if (basstate & BS_ATOPEN) { | ||
2411 | gig_dbg(DEBUG_SUSPEND, "closing AT channel"); | ||
2412 | rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, 0); | ||
2413 | if (rc) { | ||
2414 | update_basstate(ucs, 0, BS_SUSPEND); | ||
2415 | return rc; | ||
2416 | } | ||
2417 | wait_event_timeout(ucs->waitqueue, !ucs->pending, | ||
2418 | BAS_TIMEOUT*HZ/10); | ||
2419 | /* in case of timeout, proceed anyway */ | ||
2420 | } | ||
2421 | |||
2422 | /* kill all URBs and timers that might still be pending */ | ||
2423 | usb_kill_urb(ucs->urb_ctrl); | ||
2424 | usb_kill_urb(ucs->urb_int_in); | ||
2425 | del_timer_sync(&ucs->timer_ctrl); | ||
2426 | |||
2427 | gig_dbg(DEBUG_SUSPEND, "suspend complete"); | ||
2428 | return 0; | ||
2429 | } | ||
2430 | |||
2431 | /* gigaset_resume | ||
2432 | * This function is called after the USB connection has been resumed. | ||
2433 | */ | ||
2434 | static int gigaset_resume(struct usb_interface *intf) | ||
2435 | { | ||
2436 | struct cardstate *cs = usb_get_intfdata(intf); | ||
2437 | struct bas_cardstate *ucs = cs->hw.bas; | ||
2438 | int rc; | ||
2439 | |||
2440 | /* resubmit interrupt URB for spontaneous messages from base */ | ||
2441 | rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL); | ||
2442 | if (rc) { | ||
2443 | dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", | ||
2444 | get_usb_rcmsg(rc)); | ||
2445 | return rc; | ||
2446 | } | ||
2447 | |||
2448 | /* clear suspend flag to reallow activity */ | ||
2449 | update_basstate(ucs, 0, BS_SUSPEND); | ||
2450 | |||
2451 | gig_dbg(DEBUG_SUSPEND, "resume complete"); | ||
2452 | return 0; | ||
2453 | } | ||
2454 | |||
2455 | /* gigaset_pre_reset | ||
2456 | * This function is called before the USB connection is reset. | ||
2457 | */ | ||
2458 | static int gigaset_pre_reset(struct usb_interface *intf) | ||
2459 | { | ||
2460 | /* handle just like suspend */ | ||
2461 | return gigaset_suspend(intf, PMSG_ON); | ||
2462 | } | ||
2463 | |||
2464 | /* gigaset_post_reset | ||
2465 | * This function is called after the USB connection has been reset. | ||
2466 | */ | ||
2467 | static int gigaset_post_reset(struct usb_interface *intf) | ||
2468 | { | ||
2469 | /* FIXME: send HD_DEVICE_INIT_ACK? */ | ||
2470 | |||
2471 | /* resume operations */ | ||
2472 | return gigaset_resume(intf); | ||
2473 | } | ||
2474 | |||
2475 | |||
2314 | static const struct gigaset_ops gigops = { | 2476 | static const struct gigaset_ops gigops = { |
2315 | gigaset_write_cmd, | 2477 | gigaset_write_cmd, |
2316 | gigaset_write_room, | 2478 | gigaset_write_room, |