1 /* $NetBSD: tspld.c,v 1.21 2011/07/01 19:11:34 dyoung Exp $ */ 2 3 /*- 4 * Copyright (c) 2004 Jesse Off 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 * 28 */ 29 30 #include <sys/cdefs.h> 31 __KERNEL_RCSID(0, "$NetBSD: tspld.c,v 1.21 2011/07/01 19:11:34 dyoung Exp $"); 32 33 #include <sys/param.h> 34 #include <sys/callout.h> 35 #include <sys/kernel.h> 36 #include <sys/sysctl.h> 37 #include <sys/systm.h> 38 #include <sys/device.h> 39 #include <sys/wdog.h> 40 41 #include <sys/bus.h> 42 #include <machine/cpu.h> 43 #include <machine/autoconf.h> 44 #include "isa.h" 45 #if NISA > 0 46 #include <dev/isa/isavar.h> 47 #include <machine/isa_machdep.h> 48 #endif 49 50 #include <evbarm/tsarm/tsarmreg.h> 51 #include <evbarm/tsarm/tspldvar.h> 52 #include <arm/ep93xx/ep93xxvar.h> 53 #include <arm/ep93xx/ep93xxreg.h> 54 #include <arm/ep93xx/epgpioreg.h> 55 #include <arm/arm32/machdep.h> 56 #include <arm/cpufunc.h> 57 #include <dev/sysmon/sysmonvar.h> 58 59 int tspldmatch (struct device *, struct cfdata *, void *); 60 void tspldattach (struct device *, struct device *, void *); 61 static int tspld_wdog_setmode (struct sysmon_wdog *); 62 static int tspld_wdog_tickle (struct sysmon_wdog *); 63 int tspld_search (struct device *, struct cfdata *, const int *, void *); 64 int tspld_print (void *, const char *); 65 void boardtemp_poll (void *); 66 67 struct tspld_softc { 68 struct device sc_dev; 69 bus_space_tag_t sc_iot; 70 bus_space_handle_t sc_wdogfeed_ioh; 71 bus_space_handle_t sc_wdogctrl_ioh; 72 struct sysmon_wdog sc_wdog; 73 bus_space_handle_t sc_ssph; 74 bus_space_handle_t sc_gpioh; 75 unsigned const char * sc_com2mode; 76 unsigned const char * sc_model; 77 unsigned char sc_pldrev[4]; 78 uint32_t sc_rs485; 79 uint32_t sc_adc; 80 uint32_t sc_jp[6]; 81 uint32_t sc_blaster_present; 82 uint32_t sc_blaster_boot; 83 uint32_t boardtemp; 84 uint32_t boardtemp_5s; 85 uint32_t boardtemp_30s; 86 struct callout boardtemp_callout; 87 }; 88 89 CFATTACH_DECL(tspld, sizeof(struct tspld_softc), 90 tspldmatch, tspldattach, NULL, NULL); 91 92 void tspld_callback(struct device *); 93 94 #define GPIO_GET(x) bus_space_read_4(sc->sc_iot, sc->sc_gpioh, \ 95 (EP93XX_GPIO_ ## x)) 96 97 #define GPIO_SET(x, y) bus_space_write_4(sc->sc_iot, sc->sc_gpioh, \ 98 (EP93XX_GPIO_ ## x), (y)) 99 100 #define GPIO_SETBITS(x, y) bus_space_write_4(sc->sc_iot, sc->sc_gpioh, \ 101 (EP93XX_GPIO_ ## x), GPIO_GET(x) | (y)) 102 103 #define GPIO_CLEARBITS(x, y) bus_space_write_4(sc->sc_iot, sc->sc_gpioh, \ 104 (EP93XX_GPIO_ ## x), GPIO_GET(x) & (~(y))) 105 106 #define SSP_GET(x) bus_space_read_4(sc->sc_iot, sc->sc_ssph, \ 107 (EP93XX_SSP_ ## x)) 108 109 #define SSP_SET(x, y) bus_space_write_4(sc->sc_iot, sc->sc_ssph, \ 110 (EP93XX_SSP_ ## x), (y)) 111 112 #define SSP_SETBITS(x, y) bus_space_write_4(sc->sc_iot, sc->sc_ssph, \ 113 (EP93XX_SSP_ ## x), SSP_GET(x) | (y)) 114 115 #define SSP_CLEARBITS(x, y) bus_space_write_4(sc->sc_iot, sc->sc_ssph, \ 116 (EP93XX_SSP_ ## x), SSP_GET(x) & (~(y))) 117 118 int 119 tspldmatch(struct device *parent, struct cfdata *match, void *aux) 120 { 121 122 return 1; 123 } 124 125 void 126 boardtemp_poll(void *arg) 127 { 128 struct tspld_softc *sc = arg; 129 u_int16_t val; 130 131 /* Disable chip select */ 132 GPIO_SET(PFDDR, 0x0); 133 134 val = SSP_GET(SSPDR) & 0xffff; 135 sc->boardtemp = ((int16_t)val >> 3) * 62500; 136 sc->boardtemp_5s = sc->boardtemp_5s / 20 * 19 + sc->boardtemp / 20; 137 sc->boardtemp_30s = sc->boardtemp_30s / 120 * 119 + sc->boardtemp / 120; 138 139 callout_schedule(&sc->boardtemp_callout, hz / 4); 140 141 /* Enable chip select */ 142 GPIO_SET(PFDDR, 0x4); 143 144 /* Send read command */ 145 SSP_SET(SSPDR, 0x8000); 146 } 147 148 void 149 tspldattach(struct device *parent, struct device *self, void *aux) 150 { 151 int i, rev, features, jp, model; 152 struct tspld_softc *sc = (struct tspld_softc *)self; 153 bus_space_handle_t ioh; 154 const struct sysctlnode *node; 155 156 if (sysctl_createv(NULL, 0, NULL, NULL, 157 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", 158 NULL, NULL, 0, NULL, 0, 159 CTL_HW, CTL_EOL) != 0) { 160 printf("%s: could not create sysctl\n", 161 sc->sc_dev.dv_xname); 162 return; 163 } 164 if (sysctl_createv(NULL, 0, NULL, &node, 165 0, CTLTYPE_NODE, sc->sc_dev.dv_xname, 166 NULL, 167 NULL, 0, NULL, 0, 168 CTL_HW, CTL_CREATE, CTL_EOL) != 0) { 169 printf("%s: could not create sysctl\n", 170 sc->sc_dev.dv_xname); 171 return; 172 } 173 174 sc->sc_iot = &ep93xx_bs_tag; 175 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_MODEL, 2, 0, 176 &ioh); 177 model = bus_space_read_2(sc->sc_iot, ioh, 0) & 0x7; 178 sc->sc_model = (model ? "TS-7250" : "TS-7200"); 179 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 180 0, CTLTYPE_STRING, "boardmodel", 181 SYSCTL_DESCR("Technologic Systems board model"), 182 NULL, 0, __UNCONST(sc->sc_model), 0, 183 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 184 != 0) { 185 printf("%s: could not create sysctl\n", 186 sc->sc_dev.dv_xname); 187 return; 188 } 189 bus_space_unmap(sc->sc_iot, ioh, 2); 190 191 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_PLDREV, 2, 0, 192 &ioh); 193 rev = bus_space_read_2(sc->sc_iot, ioh, 0) & 0x7; 194 rev = 'A' + rev - 1; 195 sc->sc_pldrev[0] = rev; 196 sc->sc_pldrev[1] = 0; 197 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 198 0, CTLTYPE_STRING, "pldrev", 199 SYSCTL_DESCR("CPLD revision"), 200 NULL, 0, sc->sc_pldrev, 0, 201 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 202 != 0) { 203 printf("%s: could not create sysctl\n", 204 sc->sc_dev.dv_xname); 205 return; 206 } 207 bus_space_unmap(sc->sc_iot, ioh, 2); 208 209 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_FEATURES, 2, 0, 210 &ioh); 211 features = bus_space_read_2(sc->sc_iot, ioh, 0) & 0x7; 212 bus_space_unmap(sc->sc_iot, ioh, 2); 213 214 bus_space_map(sc->sc_iot, TS7XXX_IO8_HWBASE + TS7XXX_STATUS1, 1, 0, 215 &ioh); 216 i = bus_space_read_1(sc->sc_iot, ioh, 0) & 0x1f; 217 jp = (~((i & 0x18) >> 1) & 0xc) | (i & 0x3); 218 bus_space_unmap(sc->sc_iot, ioh, 1); 219 220 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 221 0, CTLTYPE_INT, "blaster_present", 222 SYSCTL_DESCR("Whether or not a TS-9420/TS-9202 blaster board is connected"), 223 NULL, 0, &sc->sc_blaster_present, 0, 224 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 225 != 0) { 226 printf("%s: could not create sysctl\n", 227 sc->sc_dev.dv_xname); 228 return; 229 } 230 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 231 0, CTLTYPE_INT, "blaster_boot", 232 SYSCTL_DESCR("Whether or not a blast board was used to boot"), 233 NULL, 0, &sc->sc_blaster_boot, 0, 234 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 235 != 0) { 236 printf("%s: could not create sysctl\n", 237 sc->sc_dev.dv_xname); 238 return; 239 } 240 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_STATUS2, 2, 0, 241 &ioh); 242 i = bus_space_read_2(sc->sc_iot, ioh, 0) & 0x6; 243 sc->sc_blaster_boot = sc->sc_blaster_present = 0; 244 if (i & 0x2) 245 sc->sc_blaster_boot = 1; 246 if (i & 0x4) 247 sc->sc_blaster_present = 1; 248 jp |= (i << 4); 249 bus_space_unmap(sc->sc_iot, ioh, 1); 250 251 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 252 0, CTLTYPE_INT, "rs485_avail", 253 SYSCTL_DESCR("RS485 level driver for COM2 available"), 254 NULL, 0, &sc->sc_rs485, 0, 255 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 256 != 0) { 257 printf("%s: could not create sysctl\n", 258 sc->sc_dev.dv_xname); 259 return; 260 } 261 sc->sc_com2mode = "rs232"; 262 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 263 0, CTLTYPE_STRING, "com2_mode", 264 SYSCTL_DESCR("line driver type for COM2"), 265 NULL, 0, __UNCONST(sc->sc_com2mode), 0, 266 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 267 != 0) { 268 printf("%s: could not create sysctl\n", 269 sc->sc_dev.dv_xname); 270 return; 271 } 272 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 273 0, CTLTYPE_INT, "max197adc_avail", 274 SYSCTL_DESCR("Maxim 197 Analog to Digital Converter available"), 275 NULL, 0, &sc->sc_adc, 0, 276 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 277 != 0) { 278 printf("%s: could not create sysctl\n", 279 sc->sc_dev.dv_xname); 280 return; 281 } 282 printf(": Technologic Systems %s rev %c, features 0x%x", 283 sc->sc_model, rev, features); 284 sc->sc_adc = sc->sc_rs485 = 0; 285 if (features == 0x1) { 286 printf("<MAX197-ADC>"); 287 sc->sc_adc = 1; 288 } else if (features == 0x2) { 289 printf("<RS485>"); 290 sc->sc_rs485 = 1; 291 } else if (features == 0x3) { 292 printf("<MAX197-ADC,RS485>"); 293 sc->sc_adc = sc->sc_rs485 = 1; 294 } 295 printf("\n"); 296 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 297 0, CTLTYPE_INT, "jp1", 298 SYSCTL_DESCR("onboard jumper setting"), 299 NULL, 0, &sc->sc_jp[0], 0, 300 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 301 != 0) { 302 printf("%s: could not create sysctl\n", 303 sc->sc_dev.dv_xname); 304 return; 305 } 306 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 307 0, CTLTYPE_INT, "jp2", 308 SYSCTL_DESCR("onboard jumper setting"), 309 NULL, 0, &sc->sc_jp[1], 0, 310 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 311 != 0) { 312 printf("%s: could not create sysctl\n", 313 sc->sc_dev.dv_xname); 314 return; 315 } 316 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 317 0, CTLTYPE_INT, "jp3", 318 SYSCTL_DESCR("onboard jumper setting"), 319 NULL, 0, &sc->sc_jp[2], 0, 320 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 321 != 0) { 322 printf("%s: could not create sysctl\n", 323 sc->sc_dev.dv_xname); 324 return; 325 } 326 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 327 0, CTLTYPE_INT, "jp4", 328 SYSCTL_DESCR("onboard jumper setting"), 329 NULL, 0, &sc->sc_jp[3], 0, 330 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 331 != 0) { 332 printf("%s: could not create sysctl\n", 333 sc->sc_dev.dv_xname); 334 return; 335 } 336 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 337 0, CTLTYPE_INT, "jp5", 338 SYSCTL_DESCR("onboard jumper setting"), 339 NULL, 0, &sc->sc_jp[4], 0, 340 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 341 != 0) { 342 printf("%s: could not create sysctl\n", 343 sc->sc_dev.dv_xname); 344 return; 345 } 346 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 347 0, CTLTYPE_INT, "jp6", 348 SYSCTL_DESCR("onboard jumper setting"), 349 NULL, 0, &sc->sc_jp[5], 0, 350 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 351 != 0) { 352 printf("%s: could not create sysctl\n", 353 sc->sc_dev.dv_xname); 354 return; 355 } 356 printf("%s: jumpers 0x%x", sc->sc_dev.dv_xname, jp); 357 if (jp) { 358 printf("<"); 359 for(i = 0; i < 5; i++) { 360 if (jp & (1 << i)) { 361 sc->sc_jp[i + 1] = 1; 362 printf("JP%d", i + 2); 363 jp &= ~(1 << i); 364 if (jp) printf(","); 365 } else { 366 sc->sc_jp[i + 2] = 0; 367 } 368 } 369 printf(">"); 370 } 371 printf("\n"); 372 373 374 bus_space_map(sc->sc_iot, EP93XX_APB_HWBASE + EP93XX_APB_SSP, 375 EP93XX_APB_SSP_SIZE, 0, &sc->sc_ssph); 376 bus_space_map(sc->sc_iot, EP93XX_APB_HWBASE + EP93XX_APB_GPIO, 377 EP93XX_APB_GPIO_SIZE, 0, &sc->sc_gpioh); 378 SSP_SETBITS(SSPCR1, 0x10); 379 SSP_SET(SSPCR0, 0xf); 380 SSP_SET(SSPCPSR, 0xfe); 381 SSP_CLEARBITS(SSPCR1, 0x10); 382 SSP_SETBITS(SSPCR1, 0x10); 383 GPIO_SET(PFDR, 0x0); 384 callout_init(&sc->boardtemp_callout, 0); 385 callout_setfunc(&sc->boardtemp_callout, boardtemp_poll, sc); 386 boardtemp_poll(sc); 387 delay(1000); 388 boardtemp_poll(sc); 389 sc->boardtemp_5s = sc->boardtemp_30s = sc->boardtemp; 390 #define DEGF(c) ((c) * 9 / 5 + 32000000) 391 printf("%s: board temperature %d.%02d degC (%d.%02d degF)\n", 392 sc->sc_dev.dv_xname, 393 sc->boardtemp / 1000000, sc->boardtemp / 10000 % 100, 394 DEGF(sc->boardtemp) / 1000000, DEGF(sc->boardtemp) / 10000 % 100); 395 #undef DEGF 396 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 397 0, CTLTYPE_INT, "board_temp", 398 SYSCTL_DESCR("board temperature in micro degrees Celsius"), 399 NULL, 0, &sc->boardtemp, 0, 400 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 401 != 0) { 402 printf("%s: could not create sysctl\n", 403 sc->sc_dev.dv_xname); 404 return; 405 } 406 407 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 408 0, CTLTYPE_INT, "board_temp_5s", 409 SYSCTL_DESCR("5 second average board temperature in micro degrees Celsius"), 410 NULL, 0, &sc->boardtemp_5s, 0, 411 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 412 != 0) { 413 printf("%s: could not create sysctl\n", 414 sc->sc_dev.dv_xname); 415 return; 416 } 417 418 if ((i = sysctl_createv(NULL, 0, NULL, NULL, 419 0, CTLTYPE_INT, "board_temp_30s", 420 SYSCTL_DESCR("30 second average board temperature in micro degrees Celsius"), 421 NULL, 0, &sc->boardtemp_30s, 0, 422 CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL)) 423 != 0) { 424 printf("%s: could not create sysctl\n", 425 sc->sc_dev.dv_xname); 426 return; 427 } 428 429 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_WDOGCTRL, 2, 0, 430 &sc->sc_wdogctrl_ioh); 431 bus_space_map(sc->sc_iot, TS7XXX_IO16_HWBASE + TS7XXX_WDOGFEED, 2, 0, 432 &sc->sc_wdogfeed_ioh); 433 434 sc->sc_wdog.smw_name = sc->sc_dev.dv_xname; 435 sc->sc_wdog.smw_cookie = sc; 436 sc->sc_wdog.smw_setmode = tspld_wdog_setmode; 437 sc->sc_wdog.smw_tickle = tspld_wdog_tickle; 438 sc->sc_wdog.smw_period = 8; 439 sysmon_wdog_register(&sc->sc_wdog); 440 tspld_wdog_setmode(&sc->sc_wdog); 441 442 /* Set the on board peripherals bus callback */ 443 config_defer(self, tspld_callback); 444 } 445 446 int 447 tspld_search(struct device *parent, struct cfdata *cf, const int *ldesc, void *aux) 448 { 449 struct tspld_softc *sc = (struct tspld_softc *)parent; 450 struct tspld_attach_args sa; 451 452 sa.ta_iot = sc->sc_iot; 453 454 if (config_match(parent, cf, &sa) > 0) 455 config_attach(parent, cf, &sa, tspld_print); 456 457 return (0); 458 } 459 460 int 461 tspld_print(void *aux, const char *name) 462 { 463 464 return (UNCONF); 465 } 466 467 void 468 tspld_callback(struct device *self) 469 { 470 #if NISA > 0 471 extern void isa_bs_mallocok(void); 472 struct isabus_attach_args iba; 473 474 /* 475 * Attach the ISA bus behind this bridge. 476 */ 477 memset(&iba, 0, sizeof(iba)); 478 iba.iba_iot = &isa_io_bs_tag; 479 iba.iba_memt = &isa_mem_bs_tag; 480 isa_bs_mallocok(); 481 config_found_ia(self, "isabus", &iba, isabusprint); 482 #endif 483 /* 484 * Attach each devices 485 */ 486 config_search_ia(tspld_search, self, "tspldbus", NULL); 487 488 } 489 490 static int 491 tspld_wdog_tickle(struct sysmon_wdog *smw) 492 { 493 struct tspld_softc *sc = (struct tspld_softc *)smw->smw_cookie; 494 495 bus_space_write_2(sc->sc_iot, sc->sc_wdogfeed_ioh, 0, 0x5); 496 return 0; 497 } 498 499 static int 500 tspld_wdog_setmode(struct sysmon_wdog *smw) 501 { 502 int i, ret = 0; 503 struct tspld_softc *sc = (struct tspld_softc *)smw->smw_cookie; 504 505 i = disable_interrupts(I32_bit|F32_bit); 506 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 507 bus_space_write_2(sc->sc_iot, sc->sc_wdogfeed_ioh, 0, 0x5); 508 bus_space_write_2(sc->sc_iot, sc->sc_wdogctrl_ioh, 0, 0); 509 } else { 510 if (smw->smw_period == WDOG_PERIOD_DEFAULT) { 511 smw->smw_period = 8; 512 } 513 514 bus_space_write_2(sc->sc_iot, sc->sc_wdogfeed_ioh, 0, 0x5); 515 switch (smw->smw_period) { 516 case 1: 517 bus_space_write_2(sc->sc_iot, sc->sc_wdogctrl_ioh, 0, 518 0x3); 519 break; 520 case 2: 521 bus_space_write_2(sc->sc_iot, sc->sc_wdogctrl_ioh, 0, 522 0x5); 523 break; 524 case 4: 525 bus_space_write_2(sc->sc_iot, sc->sc_wdogctrl_ioh, 0, 526 0x6); 527 break; 528 case 8: 529 bus_space_write_2(sc->sc_iot, sc->sc_wdogctrl_ioh, 0, 530 0x7); 531 break; 532 default: 533 ret = EINVAL; 534 } 535 } 536 restore_interrupts(i); 537 return ret; 538 } 539