ehci-hub.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /*
  2. * Copyright (C) 2001-2004 by David Brownell
  3. *
  4. * This program is free software; you can redistribute it and/or modify it
  5. * under the terms of the GNU General Public License as published by the
  6. * Free Software Foundation; either version 2 of the License, or (at your
  7. * option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software Foundation,
  16. * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17. */
  18. /* this file is part of ehci-hcd.c */
  19. /*-------------------------------------------------------------------------*/
  20. /*
  21. * EHCI Root Hub ... the nonsharable stuff
  22. *
  23. * Registers don't need cpu_to_le32, that happens transparently
  24. */
  25. /*-------------------------------------------------------------------------*/
  26. #ifdef CONFIG_PM
  27. static int ehci_bus_suspend (struct usb_hcd *hcd)
  28. {
  29. struct ehci_hcd *ehci = hcd_to_ehci (hcd);
  30. int port;
  31. if (time_before (jiffies, ehci->next_statechange))
  32. msleep(5);
  33. port = HCS_N_PORTS (ehci->hcs_params);
  34. spin_lock_irq (&ehci->lock);
  35. /* stop schedules, clean any completed work */
  36. if (HC_IS_RUNNING(hcd->state)) {
  37. ehci_quiesce (ehci);
  38. hcd->state = HC_STATE_QUIESCING;
  39. }
  40. ehci->command = readl (&ehci->regs->command);
  41. if (ehci->reclaim)
  42. ehci->reclaim_ready = 1;
  43. ehci_work(ehci);
  44. /* suspend any active/unsuspended ports, maybe allow wakeup */
  45. while (port--) {
  46. u32 __iomem *reg = &ehci->regs->port_status [port];
  47. u32 t1 = readl (reg) & ~PORT_RWC_BITS;
  48. u32 t2 = t1;
  49. if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
  50. t2 |= PORT_SUSPEND;
  51. if (device_may_wakeup(&hcd->self.root_hub->dev))
  52. t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
  53. else
  54. t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
  55. if (t1 != t2) {
  56. ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
  57. port + 1, t1, t2);
  58. writel (t2, reg);
  59. }
  60. }
  61. /* turn off now-idle HC */
  62. del_timer_sync (&ehci->watchdog);
  63. ehci_halt (ehci);
  64. hcd->state = HC_STATE_SUSPENDED;
  65. ehci->next_statechange = jiffies + msecs_to_jiffies(10);
  66. spin_unlock_irq (&ehci->lock);
  67. return 0;
  68. }
  69. /* caller has locked the root hub, and should reset/reinit on error */
  70. static int ehci_bus_resume (struct usb_hcd *hcd)
  71. {
  72. struct ehci_hcd *ehci = hcd_to_ehci (hcd);
  73. u32 temp;
  74. int i;
  75. int intr_enable;
  76. if (time_before (jiffies, ehci->next_statechange))
  77. msleep(5);
  78. spin_lock_irq (&ehci->lock);
  79. /* Ideally and we've got a real resume here, and no port's power
  80. * was lost. (For PCI, that means Vaux was maintained.) But we
  81. * could instead be restoring a swsusp snapshot -- so that BIOS was
  82. * the last user of the controller, not reset/pm hardware keeping
  83. * state we gave to it.
  84. */
  85. /* re-init operational registers in case we lost power */
  86. if (readl (&ehci->regs->intr_enable) == 0) {
  87. /* at least some APM implementations will try to deliver
  88. * IRQs right away, so delay them until we're ready.
  89. */
  90. intr_enable = 1;
  91. writel (0, &ehci->regs->segment);
  92. writel (ehci->periodic_dma, &ehci->regs->frame_list);
  93. writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
  94. } else
  95. intr_enable = 0;
  96. ehci_dbg(ehci, "resume root hub%s\n",
  97. intr_enable ? " after power loss" : "");
  98. /* restore CMD_RUN, framelist size, and irq threshold */
  99. writel (ehci->command, &ehci->regs->command);
  100. /* take ports out of suspend */
  101. i = HCS_N_PORTS (ehci->hcs_params);
  102. while (i--) {
  103. temp = readl (&ehci->regs->port_status [i]);
  104. temp &= ~(PORT_RWC_BITS
  105. | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E);
  106. if (temp & PORT_SUSPEND) {
  107. ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
  108. temp |= PORT_RESUME;
  109. }
  110. writel (temp, &ehci->regs->port_status [i]);
  111. }
  112. i = HCS_N_PORTS (ehci->hcs_params);
  113. mdelay (20);
  114. while (i--) {
  115. temp = readl (&ehci->regs->port_status [i]);
  116. if ((temp & PORT_SUSPEND) == 0)
  117. continue;
  118. temp &= ~(PORT_RWC_BITS | PORT_RESUME);
  119. writel (temp, &ehci->regs->port_status [i]);
  120. ehci_vdbg (ehci, "resumed port %d\n", i + 1);
  121. }
  122. (void) readl (&ehci->regs->command);
  123. /* maybe re-activate the schedule(s) */
  124. temp = 0;
  125. if (ehci->async->qh_next.qh)
  126. temp |= CMD_ASE;
  127. if (ehci->periodic_sched)
  128. temp |= CMD_PSE;
  129. if (temp) {
  130. ehci->command |= temp;
  131. writel (ehci->command, &ehci->regs->command);
  132. }
  133. ehci->next_statechange = jiffies + msecs_to_jiffies(5);
  134. hcd->state = HC_STATE_RUNNING;
  135. /* Now we can safely re-enable irqs */
  136. if (intr_enable)
  137. writel (INTR_MASK, &ehci->regs->intr_enable);
  138. spin_unlock_irq (&ehci->lock);
  139. return 0;
  140. }
  141. #else
  142. #define ehci_bus_suspend NULL
  143. #define ehci_bus_resume NULL
  144. #endif /* CONFIG_PM */
  145. /*-------------------------------------------------------------------------*/
  146. static int check_reset_complete (
  147. struct ehci_hcd *ehci,
  148. int index,
  149. int port_status
  150. ) {
  151. if (!(port_status & PORT_CONNECT)) {
  152. ehci->reset_done [index] = 0;
  153. return port_status;
  154. }
  155. /* if reset finished and it's still not enabled -- handoff */
  156. if (!(port_status & PORT_PE)) {
  157. /* with integrated TT, there's nobody to hand it to! */
  158. if (ehci_is_TDI(ehci)) {
  159. ehci_dbg (ehci,
  160. "Failed to enable port %d on root hub TT\n",
  161. index+1);
  162. return port_status;
  163. }
  164. ehci_dbg (ehci, "port %d full speed --> companion\n",
  165. index + 1);
  166. // what happens if HCS_N_CC(params) == 0 ?
  167. port_status |= PORT_OWNER;
  168. port_status &= ~PORT_RWC_BITS;
  169. writel (port_status, &ehci->regs->port_status [index]);
  170. } else
  171. ehci_dbg (ehci, "port %d high speed\n", index + 1);
  172. return port_status;
  173. }
  174. /*-------------------------------------------------------------------------*/
  175. /* build "status change" packet (one or two bytes) from HC registers */
  176. static int
  177. ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
  178. {
  179. struct ehci_hcd *ehci = hcd_to_ehci (hcd);
  180. u32 temp, status = 0;
  181. u32 mask;
  182. int ports, i, retval = 1;
  183. unsigned long flags;
  184. /* if !USB_SUSPEND, root hub timers won't get shut down ... */
  185. if (!HC_IS_RUNNING(hcd->state))
  186. return 0;
  187. /* init status to no-changes */
  188. buf [0] = 0;
  189. ports = HCS_N_PORTS (ehci->hcs_params);
  190. if (ports > 7) {
  191. buf [1] = 0;
  192. retval++;
  193. }
  194. /* Some boards (mostly VIA?) report bogus overcurrent indications,
  195. * causing massive log spam unless we completely ignore them. It
  196. * may be relevant that VIA VT8235 controlers, where PORT_POWER is
  197. * always set, seem to clear PORT_OCC and PORT_CSC when writing to
  198. * PORT_POWER; that's surprising, but maybe within-spec.
  199. */
  200. if (!ignore_oc)
  201. mask = PORT_CSC | PORT_PEC | PORT_OCC;
  202. else
  203. mask = PORT_CSC | PORT_PEC;
  204. // PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND
  205. /* no hub change reports (bit 0) for now (power, ...) */
  206. /* port N changes (bit N)? */
  207. spin_lock_irqsave (&ehci->lock, flags);
  208. for (i = 0; i < ports; i++) {
  209. temp = readl (&ehci->regs->port_status [i]);
  210. if (temp & PORT_OWNER) {
  211. /* don't report this in GetPortStatus */
  212. if (temp & PORT_CSC) {
  213. temp &= ~PORT_RWC_BITS;
  214. temp |= PORT_CSC;
  215. writel (temp, &ehci->regs->port_status [i]);
  216. }
  217. continue;
  218. }
  219. if (!(temp & PORT_CONNECT))
  220. ehci->reset_done [i] = 0;
  221. if ((temp & mask) != 0
  222. || ((temp & PORT_RESUME) != 0
  223. && time_after (jiffies,
  224. ehci->reset_done [i]))) {
  225. if (i < 7)
  226. buf [0] |= 1 << (i + 1);
  227. else
  228. buf [1] |= 1 << (i - 7);
  229. status = STS_PCD;
  230. }
  231. }
  232. /* FIXME autosuspend idle root hubs */
  233. spin_unlock_irqrestore (&ehci->lock, flags);
  234. return status ? retval : 0;
  235. }
  236. /*-------------------------------------------------------------------------*/
  237. static void
  238. ehci_hub_descriptor (
  239. struct ehci_hcd *ehci,
  240. struct usb_hub_descriptor *desc
  241. ) {
  242. int ports = HCS_N_PORTS (ehci->hcs_params);
  243. u16 temp;
  244. desc->bDescriptorType = 0x29;
  245. desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */
  246. desc->bHubContrCurrent = 0;
  247. desc->bNbrPorts = ports;
  248. temp = 1 + (ports / 8);
  249. desc->bDescLength = 7 + 2 * temp;
  250. /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
  251. memset (&desc->bitmap [0], 0, temp);
  252. memset (&desc->bitmap [temp], 0xff, temp);
  253. temp = 0x0008; /* per-port overcurrent reporting */
  254. if (HCS_PPC (ehci->hcs_params))
  255. temp |= 0x0001; /* per-port power control */
  256. else
  257. temp |= 0x0002; /* no power switching */
  258. #if 0
  259. // re-enable when we support USB_PORT_FEAT_INDICATOR below.
  260. if (HCS_INDICATOR (ehci->hcs_params))
  261. temp |= 0x0080; /* per-port indicators (LEDs) */
  262. #endif
  263. desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
  264. }
  265. /*-------------------------------------------------------------------------*/
  266. #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
  267. static int ehci_hub_control (
  268. struct usb_hcd *hcd,
  269. u16 typeReq,
  270. u16 wValue,
  271. u16 wIndex,
  272. char *buf,
  273. u16 wLength
  274. ) {
  275. struct ehci_hcd *ehci = hcd_to_ehci (hcd);
  276. int ports = HCS_N_PORTS (ehci->hcs_params);
  277. u32 temp, status;
  278. unsigned long flags;
  279. int retval = 0;
  280. unsigned selector;
  281. /*
  282. * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
  283. * HCS_INDICATOR may say we can change LEDs to off/amber/green.
  284. * (track current state ourselves) ... blink for diagnostics,
  285. * power, "this is the one", etc. EHCI spec supports this.
  286. */
  287. spin_lock_irqsave (&ehci->lock, flags);
  288. switch (typeReq) {
  289. case ClearHubFeature:
  290. switch (wValue) {
  291. case C_HUB_LOCAL_POWER:
  292. case C_HUB_OVER_CURRENT:
  293. /* no hub-wide feature/status flags */
  294. break;
  295. default:
  296. goto error;
  297. }
  298. break;
  299. case ClearPortFeature:
  300. if (!wIndex || wIndex > ports)
  301. goto error;
  302. wIndex--;
  303. temp = readl (&ehci->regs->port_status [wIndex]);
  304. if (temp & PORT_OWNER)
  305. break;
  306. switch (wValue) {
  307. case USB_PORT_FEAT_ENABLE:
  308. writel (temp & ~PORT_PE,
  309. &ehci->regs->port_status [wIndex]);
  310. break;
  311. case USB_PORT_FEAT_C_ENABLE:
  312. writel((temp & ~PORT_RWC_BITS) | PORT_PEC,
  313. &ehci->regs->port_status [wIndex]);
  314. break;
  315. case USB_PORT_FEAT_SUSPEND:
  316. if (temp & PORT_RESET)
  317. goto error;
  318. if (ehci->no_selective_suspend)
  319. break;
  320. if (temp & PORT_SUSPEND) {
  321. if ((temp & PORT_PE) == 0)
  322. goto error;
  323. /* resume signaling for 20 msec */
  324. temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
  325. writel (temp | PORT_RESUME,
  326. &ehci->regs->port_status [wIndex]);
  327. ehci->reset_done [wIndex] = jiffies
  328. + msecs_to_jiffies (20);
  329. }
  330. break;
  331. case USB_PORT_FEAT_C_SUSPEND:
  332. /* we auto-clear this feature */
  333. break;
  334. case USB_PORT_FEAT_POWER:
  335. if (HCS_PPC (ehci->hcs_params))
  336. writel (temp & ~(PORT_RWC_BITS | PORT_POWER),
  337. &ehci->regs->port_status [wIndex]);
  338. break;
  339. case USB_PORT_FEAT_C_CONNECTION:
  340. writel((temp & ~PORT_RWC_BITS) | PORT_CSC,
  341. &ehci->regs->port_status [wIndex]);
  342. break;
  343. case USB_PORT_FEAT_C_OVER_CURRENT:
  344. writel((temp & ~PORT_RWC_BITS) | PORT_OCC,
  345. &ehci->regs->port_status [wIndex]);
  346. break;
  347. case USB_PORT_FEAT_C_RESET:
  348. /* GetPortStatus clears reset */
  349. break;
  350. default:
  351. goto error;
  352. }
  353. readl (&ehci->regs->command); /* unblock posted write */
  354. break;
  355. case GetHubDescriptor:
  356. ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
  357. buf);
  358. break;
  359. case GetHubStatus:
  360. /* no hub-wide feature/status flags */
  361. memset (buf, 0, 4);
  362. //cpu_to_le32s ((u32 *) buf);
  363. break;
  364. case GetPortStatus:
  365. if (!wIndex || wIndex > ports)
  366. goto error;
  367. wIndex--;
  368. status = 0;
  369. temp = readl (&ehci->regs->port_status [wIndex]);
  370. // wPortChange bits
  371. if (temp & PORT_CSC)
  372. status |= 1 << USB_PORT_FEAT_C_CONNECTION;
  373. if (temp & PORT_PEC)
  374. status |= 1 << USB_PORT_FEAT_C_ENABLE;
  375. if ((temp & PORT_OCC) && !ignore_oc)
  376. status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
  377. /* whoever resumes must GetPortStatus to complete it!! */
  378. if ((temp & PORT_RESUME)
  379. && time_after (jiffies,
  380. ehci->reset_done [wIndex])) {
  381. status |= 1 << USB_PORT_FEAT_C_SUSPEND;
  382. ehci->reset_done [wIndex] = 0;
  383. /* stop resume signaling */
  384. temp = readl (&ehci->regs->port_status [wIndex]);
  385. writel (temp & ~(PORT_RWC_BITS | PORT_RESUME),
  386. &ehci->regs->port_status [wIndex]);
  387. retval = handshake (
  388. &ehci->regs->port_status [wIndex],
  389. PORT_RESUME, 0, 2000 /* 2msec */);
  390. if (retval != 0) {
  391. ehci_err (ehci, "port %d resume error %d\n",
  392. wIndex + 1, retval);
  393. goto error;
  394. }
  395. temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
  396. }
  397. /* whoever resets must GetPortStatus to complete it!! */
  398. if ((temp & PORT_RESET)
  399. && time_after (jiffies,
  400. ehci->reset_done [wIndex])) {
  401. status |= 1 << USB_PORT_FEAT_C_RESET;
  402. ehci->reset_done [wIndex] = 0;
  403. /* force reset to complete */
  404. writel (temp & ~(PORT_RWC_BITS | PORT_RESET),
  405. &ehci->regs->port_status [wIndex]);
  406. /* REVISIT: some hardware needs 550+ usec to clear
  407. * this bit; seems too long to spin routinely...
  408. */
  409. retval = handshake (
  410. &ehci->regs->port_status [wIndex],
  411. PORT_RESET, 0, 750);
  412. if (retval != 0) {
  413. ehci_err (ehci, "port %d reset error %d\n",
  414. wIndex + 1, retval);
  415. goto error;
  416. }
  417. /* see what we found out */
  418. temp = check_reset_complete (ehci, wIndex,
  419. readl (&ehci->regs->port_status [wIndex]));
  420. }
  421. // don't show wPortStatus if it's owned by a companion hc
  422. if (!(temp & PORT_OWNER)) {
  423. if (temp & PORT_CONNECT) {
  424. status |= 1 << USB_PORT_FEAT_CONNECTION;
  425. // status may be from integrated TT
  426. status |= ehci_port_speed(ehci, temp);
  427. }
  428. if (temp & PORT_PE)
  429. status |= 1 << USB_PORT_FEAT_ENABLE;
  430. if (temp & (PORT_SUSPEND|PORT_RESUME))
  431. status |= 1 << USB_PORT_FEAT_SUSPEND;
  432. if (temp & PORT_OC)
  433. status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
  434. if (temp & PORT_RESET)
  435. status |= 1 << USB_PORT_FEAT_RESET;
  436. if (temp & PORT_POWER)
  437. status |= 1 << USB_PORT_FEAT_POWER;
  438. }
  439. #ifndef EHCI_VERBOSE_DEBUG
  440. if (status & ~0xffff) /* only if wPortChange is interesting */
  441. #endif
  442. dbg_port (ehci, "GetStatus", wIndex + 1, temp);
  443. // we "know" this alignment is good, caller used kmalloc()...
  444. *((__le32 *) buf) = cpu_to_le32 (status);
  445. break;
  446. case SetHubFeature:
  447. switch (wValue) {
  448. case C_HUB_LOCAL_POWER:
  449. case C_HUB_OVER_CURRENT:
  450. /* no hub-wide feature/status flags */
  451. break;
  452. default:
  453. goto error;
  454. }
  455. break;
  456. case SetPortFeature:
  457. selector = wIndex >> 8;
  458. wIndex &= 0xff;
  459. if (!wIndex || wIndex > ports)
  460. goto error;
  461. wIndex--;
  462. temp = readl (&ehci->regs->port_status [wIndex]);
  463. if (temp & PORT_OWNER)
  464. break;
  465. temp &= ~PORT_RWC_BITS;
  466. switch (wValue) {
  467. case USB_PORT_FEAT_SUSPEND:
  468. if (ehci->no_selective_suspend)
  469. break;
  470. if ((temp & PORT_PE) == 0
  471. || (temp & PORT_RESET) != 0)
  472. goto error;
  473. if (device_may_wakeup(&hcd->self.root_hub->dev))
  474. temp |= PORT_WAKE_BITS;
  475. writel (temp | PORT_SUSPEND,
  476. &ehci->regs->port_status [wIndex]);
  477. break;
  478. case USB_PORT_FEAT_POWER:
  479. if (HCS_PPC (ehci->hcs_params))
  480. writel (temp | PORT_POWER,
  481. &ehci->regs->port_status [wIndex]);
  482. break;
  483. case USB_PORT_FEAT_RESET:
  484. if (temp & PORT_RESUME)
  485. goto error;
  486. /* line status bits may report this as low speed,
  487. * which can be fine if this root hub has a
  488. * transaction translator built in.
  489. */
  490. if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
  491. && !ehci_is_TDI(ehci)
  492. && PORT_USB11 (temp)) {
  493. ehci_dbg (ehci,
  494. "port %d low speed --> companion\n",
  495. wIndex + 1);
  496. temp |= PORT_OWNER;
  497. } else {
  498. ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
  499. temp |= PORT_RESET;
  500. temp &= ~PORT_PE;
  501. /*
  502. * caller must wait, then call GetPortStatus
  503. * usb 2.0 spec says 50 ms resets on root
  504. */
  505. ehci->reset_done [wIndex] = jiffies
  506. + msecs_to_jiffies (50);
  507. }
  508. writel (temp, &ehci->regs->port_status [wIndex]);
  509. break;
  510. /* For downstream facing ports (these): one hub port is put
  511. * into test mode according to USB2 11.24.2.13, then the hub
  512. * must be reset (which for root hub now means rmmod+modprobe,
  513. * or else system reboot). See EHCI 2.3.9 and 4.14 for info
  514. * about the EHCI-specific stuff.
  515. */
  516. case USB_PORT_FEAT_TEST:
  517. if (!selector || selector > 5)
  518. goto error;
  519. ehci_quiesce(ehci);
  520. ehci_halt(ehci);
  521. temp |= selector << 16;
  522. writel (temp, &ehci->regs->port_status [wIndex]);
  523. break;
  524. default:
  525. goto error;
  526. }
  527. readl (&ehci->regs->command); /* unblock posted writes */
  528. break;
  529. default:
  530. error:
  531. /* "stall" on error */
  532. retval = -EPIPE;
  533. }
  534. spin_unlock_irqrestore (&ehci->lock, flags);
  535. return retval;
  536. }