1/* $NetBSD: obio.c,v 1.53 2022/12/28 07:34:42 macallan Exp $ */ 2 3/*- 4 * Copyright (C) 1998 Internet Research Institute, Inc. 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 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by 18 * Internet Research Institute, Inc. 19 * 4. The name of the author may not be used to endorse or promote products 20 * derived from this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34#include <sys/cdefs.h> 35__KERNEL_RCSID(0, "$NetBSD: obio.c,v 1.53 2022/12/28 07:34:42 macallan Exp $"); 36 37#include <sys/param.h> 38#include <sys/systm.h> 39#include <sys/kernel.h> 40#include <sys/device.h> 41#include <sys/sysctl.h> 42 43#include <dev/pci/pcivar.h> 44#include <dev/pci/pcidevs.h> 45 46#include <dev/ofw/openfirm.h> 47 48#include <machine/autoconf.h> 49 50#include <macppc/dev/obiovar.h> 51 52#include <powerpc/cpu.h> 53#include <sys/cpufreq.h> 54 55#include "opt_obio.h" 56 57#ifdef OBIO_DEBUG 58# define DPRINTF printf 59#else 60# define DPRINTF while (0) printf 61#endif 62 63static void obio_attach(device_t, device_t, void *); 64static int obio_match(device_t, cfdata_t, void *); 65static int obio_print(void *, const char *); 66 67struct obio_softc { 68 device_t sc_dev; 69 bus_space_tag_t sc_tag; 70 bus_space_handle_t sc_bh; 71 int sc_node; 72#ifdef OBIO_SPEED_CONTROL 73 int sc_voltage; 74 int sc_busspeed; 75 int sc_spd_hi, sc_spd_lo; 76 struct cpufreq sc_cf; 77#endif 78}; 79 80static struct obio_softc *obio0 = NULL; 81 82#ifdef OBIO_SPEED_CONTROL 83static void obio_setup_gpios(struct obio_softc *, int); 84static void obio_set_cpu_speed(struct obio_softc *, int); 85static int obio_get_cpu_speed(struct obio_softc *); 86static int sysctl_cpuspeed_temp(SYSCTLFN_ARGS); 87static int sysctl_cpuspeed_cur(SYSCTLFN_ARGS); 88static int sysctl_cpuspeed_available(SYSCTLFN_ARGS); 89static void obio_get_freq(void *, void *); 90static void obio_set_freq(void *, void *); 91static const char *keylargo[] = {"Keylargo", 92 "AAPL,Keylargo", 93 NULL}; 94 95#endif 96 97CFATTACH_DECL_NEW(obio, sizeof(struct obio_softc), 98 obio_match, obio_attach, NULL, NULL); 99 100int 101obio_match(device_t parent, cfdata_t cf, void *aux) 102{ 103 struct pci_attach_args *pa = aux; 104 105 if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_APPLE) 106 switch (PCI_PRODUCT(pa->pa_id)) { 107 case PCI_PRODUCT_APPLE_GC: 108 case PCI_PRODUCT_APPLE_OHARE: 109 case PCI_PRODUCT_APPLE_HEATHROW: 110 case PCI_PRODUCT_APPLE_PADDINGTON: 111 case PCI_PRODUCT_APPLE_KEYLARGO: 112 case PCI_PRODUCT_APPLE_PANGEA_MACIO: 113 case PCI_PRODUCT_APPLE_INTREPID: 114 case PCI_PRODUCT_APPLE_K2: 115 case PCI_PRODUCT_APPLE_SHASTA: 116 return 1; 117 } 118 119 return 0; 120} 121 122/* 123 * Attach all the sub-devices we can find 124 */ 125void 126obio_attach(device_t parent, device_t self, void *aux) 127{ 128 struct obio_softc *sc = device_private(self); 129 struct pci_attach_args *pa = aux; 130 struct confargs ca; 131 bus_space_handle_t bsh; 132 int node, child, namelen, error; 133 u_int reg[20]; 134 int intr[6], parent_intr = 0, parent_nintr = 0; 135 int map_size = 0x1000; 136 char name[32]; 137 char compat[32]; 138 139 sc->sc_dev = self; 140#ifdef OBIO_SPEED_CONTROL 141 sc->sc_voltage = -1; 142 sc->sc_busspeed = -1; 143 sc->sc_spd_lo = 600; 144 sc->sc_spd_hi = 800; 145#endif 146 147 switch (PCI_PRODUCT(pa->pa_id)) { 148 149 case PCI_PRODUCT_APPLE_GC: 150 case PCI_PRODUCT_APPLE_OHARE: 151 case PCI_PRODUCT_APPLE_HEATHROW: 152 case PCI_PRODUCT_APPLE_PADDINGTON: 153 case PCI_PRODUCT_APPLE_KEYLARGO: 154 case PCI_PRODUCT_APPLE_PANGEA_MACIO: 155 case PCI_PRODUCT_APPLE_INTREPID: 156 node = pcidev_to_ofdev(pa->pa_pc, pa->pa_tag); 157 if (node == -1) 158 node = OF_finddevice("mac-io"); 159 if (node == -1) 160 node = OF_finddevice("/pci/mac-io"); 161 break; 162 case PCI_PRODUCT_APPLE_K2: 163 case PCI_PRODUCT_APPLE_SHASTA: 164 node = OF_finddevice("mac-io"); 165 map_size = 0x10000; 166 break; 167 168 default: 169 node = -1; 170 break; 171 } 172 if (node == -1) 173 panic("macio not found or unknown"); 174 175 sc->sc_node = node; 176 177#if defined (PMAC_G5) 178 if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 20) 179 { 180 return; 181 } 182#else 183 if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 12) 184 return; 185#endif /* PMAC_G5 */ 186 187 /* 188 * XXX 189 * This relies on the primary obio always attaching first which is 190 * true on the PowerBook 3400c and similar machines but may or may 191 * not work on others. We can't rely on the node name since Apple 192 * didn't follow anything remotely resembling a consistent naming 193 * scheme. 194 */ 195 if (obio0 == NULL) 196 obio0 = sc; 197 198 ca.ca_baseaddr = reg[2]; 199 ca.ca_tag = pa->pa_memt; 200 sc->sc_tag = pa->pa_memt; 201 error = bus_space_map (pa->pa_memt, ca.ca_baseaddr, map_size, 0, &bsh); 202 if (error) 203 panic(": failed to map mac-io %#x", ca.ca_baseaddr); 204 sc->sc_bh = bsh; 205 206 printf(": addr 0x%x\n", ca.ca_baseaddr); 207 208 /* Enable internal modem (KeyLargo) */ 209 if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_KEYLARGO) { 210 aprint_normal("%s: enabling KeyLargo internal modem\n", 211 device_xname(self)); 212 bus_space_write_4(ca.ca_tag, bsh, 0x40, 213 bus_space_read_4(ca.ca_tag, bsh, 0x40) & ~(1<<25)); 214 } 215 216 /* Enable internal modem (Pangea) */ 217 if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PANGEA_MACIO) { 218 /* set reset */ 219 bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x04); 220 /* power modem on */ 221 bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x02, 0x04); 222 /* unset reset */ 223 bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x05); 224 } 225 226 /* Gatwick and Paddington use same product ID */ 227 namelen = OF_getprop(node, "compatible", compat, sizeof(compat)); 228 229 if (strcmp(compat, "gatwick") == 0) { 230 parent_nintr = OF_getprop(node, "AAPL,interrupts", intr, 231 sizeof(intr)); 232 parent_intr = intr[0]; 233 } else { 234 /* Enable CD and microphone sound input. */ 235 if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PADDINGTON) 236 bus_space_write_1(ca.ca_tag, bsh, 0x37, 0x03); 237 } 238 239 devhandle_t selfh = device_handle(self); 240 for (child = OF_child(node); child; child = OF_peer(child)) { 241 namelen = OF_getprop(child, "name", name, sizeof(name)); 242 if (namelen < 0) 243 continue; 244 if (namelen >= sizeof(name)) 245 continue; 246 247#ifdef OBIO_SPEED_CONTROL 248 if (strcmp(name, "gpio") == 0) { 249 250 obio_setup_gpios(sc, child); 251 continue; 252 } 253#endif 254 255 name[namelen] = 0; 256 ca.ca_name = name; 257 ca.ca_node = child; 258 ca.ca_tag = pa->pa_memt; 259 260 ca.ca_nreg = OF_getprop(child, "reg", reg, sizeof(reg)); 261 262 if (strcmp(compat, "gatwick") != 0) { 263 ca.ca_nintr = OF_getprop(child, "AAPL,interrupts", intr, 264 sizeof(intr)); 265 if (ca.ca_nintr == -1) 266 ca.ca_nintr = OF_getprop(child, "interrupts", intr, 267 sizeof(intr)); 268 } else { 269 intr[0] = parent_intr; 270 ca.ca_nintr = parent_nintr; 271 } 272 ca.ca_reg = reg; 273 ca.ca_intr = intr; 274 275 config_found(self, &ca, obio_print, 276 CFARGS(.devhandle = devhandle_from_of(selfh, child))); 277 } 278} 279 280static const char * const skiplist[] = { 281 "interrupt-controller", 282 "chrp,open-pic", 283 "open-pic", 284 "mpic", 285 "gpio", 286 "escc-legacy", 287 "timer", 288 "i2c", 289 "power-mgt", 290 "escc", 291 "battery", 292 "backlight" 293 294}; 295 296#define N_LIST (sizeof(skiplist) / sizeof(skiplist[0])) 297 298int 299obio_print(void *aux, const char *obio) 300{ 301 struct confargs *ca = aux; 302 int i; 303 304 for (i = 0; i < N_LIST; i++) 305 if (strcmp(ca->ca_name, skiplist[i]) == 0) 306 return QUIET; 307 308 if (obio) 309 aprint_normal("%s at %s", ca->ca_name, obio); 310 311 if (ca->ca_nreg > 0) 312 aprint_normal(" offset 0x%x", ca->ca_reg[0]); 313 314 return UNCONF; 315} 316 317void obio_write_4(int offset, uint32_t value) 318{ 319 if (obio0 == NULL) 320 return; 321 bus_space_write_4(obio0->sc_tag, obio0->sc_bh, offset, value); 322} 323 324void obio_write_1(int offset, uint8_t value) 325{ 326 if (obio0 == NULL) 327 return; 328 bus_space_write_1(obio0->sc_tag, obio0->sc_bh, offset, value); 329} 330 331uint32_t obio_read_4(int offset) 332{ 333 if (obio0 == NULL) 334 return 0xffffffff; 335 return bus_space_read_4(obio0->sc_tag, obio0->sc_bh, offset); 336} 337 338uint8_t obio_read_1(int offset) 339{ 340 if (obio0 == NULL) 341 return 0xff; 342 return bus_space_read_1(obio0->sc_tag, obio0->sc_bh, offset); 343} 344 345int 346obio_space_map(bus_addr_t addr, bus_size_t size, bus_space_handle_t *bh) 347{ 348 if (obio0 == NULL) 349 return 0xff; 350 return bus_space_subregion(obio0->sc_tag, obio0->sc_bh, 351 addr & 0xfffff, size, bh); 352} 353 354#ifdef OBIO_SPEED_CONTROL 355 356static void 357obio_setup_cpufreq(device_t dev) 358{ 359 struct obio_softc *sc = device_private(dev); 360 int ret; 361 362 ret = cpufreq_register(&sc->sc_cf); 363 if (ret != 0) 364 aprint_error_dev(sc->sc_dev, "cpufreq_register() failed, error %d\n", ret); 365} 366 367static void 368obio_setup_gpios(struct obio_softc *sc, int node) 369{ 370 uint32_t gpio_base, reg[6]; 371 const struct sysctlnode *sysctl_node, *me, *freq; 372 struct cpufreq *cf = &sc->sc_cf; 373 char name[32]; 374 int child, use_dfs, cpunode, hiclock; 375 376 if (! of_compatible(sc->sc_node, keylargo)) 377 return; 378 379 if (OF_getprop(node, "reg", reg, sizeof(reg)) < 4) 380 return; 381 382 gpio_base = reg[0]; 383 DPRINTF("gpio_base: %02x\n", gpio_base); 384 385 /* now look for voltage and bus speed gpios */ 386 use_dfs = 0; 387 for (child = OF_child(node); child; child = OF_peer(child)) { 388 389 if (OF_getprop(child, "name", name, sizeof(name)) < 1) 390 continue; 391 392 if (OF_getprop(child, "reg", reg, sizeof(reg)) < 4) 393 continue; 394 395 /* 396 * These register offsets either have to be added to the obio 397 * base address or to the gpio base address. This differs 398 * even in the same OF-tree! So we guess the offset is 399 * based on obio when it is larger than the gpio_base. 400 */ 401 if (reg[0] >= gpio_base) 402 reg[0] -= gpio_base; 403 404 if (strcmp(name, "frequency-gpio") == 0) { 405 DPRINTF("found frequency_gpio at %02x\n", reg[0]); 406 sc->sc_busspeed = gpio_base + reg[0]; 407 } 408 if (strcmp(name, "voltage-gpio") == 0) { 409 DPRINTF("found voltage_gpio at %02x\n", reg[0]); 410 sc->sc_voltage = gpio_base + reg[0]; 411 } 412 if (strcmp(name, "cpu-vcore-select") == 0) { 413 DPRINTF("found cpu-vcore-select at %02x\n", reg[0]); 414 sc->sc_voltage = gpio_base + reg[0]; 415 /* frequency gpio is not needed, we use cpu's DFS */ 416 use_dfs = 1; 417 } 418 } 419 420 if ((sc->sc_voltage < 0) || (sc->sc_busspeed < 0 && !use_dfs)) 421 return; 422 423 printf("%s: enabling Intrepid CPU speed control\n", 424 device_xname(sc->sc_dev)); 425 426 sc->sc_spd_lo = curcpu()->ci_khz / 1000; 427 hiclock = 0; 428 cpunode = OF_finddevice("/cpus/@0"); 429 OF_getprop(cpunode, "clock-frequency", &hiclock, 4); 430 if (hiclock != 0) 431 sc->sc_spd_hi = (hiclock + 500000) / 1000000; 432 printf("hiclock: %d\n", sc->sc_spd_hi); 433 if (use_dfs) sc->sc_spd_lo = sc->sc_spd_hi / 2; 434 435 sysctl_node = NULL; 436 437 if (sysctl_createv(NULL, 0, NULL, 438 &me, 439 CTLFLAG_READWRITE, CTLTYPE_NODE, "cpu", NULL, NULL, 440 0, NULL, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL) != 0) 441 printf("couldn't create 'cpu' node\n"); 442 443 if (sysctl_createv(NULL, 0, NULL, 444 &freq, 445 CTLFLAG_READWRITE, CTLTYPE_NODE, "frequency", NULL, NULL, 446 0, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL) != 0) 447 printf("couldn't create 'frequency' node\n"); 448 449 if (sysctl_createv(NULL, 0, NULL, 450 &sysctl_node, 451 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 452 CTLTYPE_INT, "target", "CPU speed", sysctl_cpuspeed_temp, 453 0, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 454 CTL_CREATE, CTL_EOL) == 0) { 455 } else 456 printf("couldn't create 'target' node\n"); 457 458 if (sysctl_createv(NULL, 0, NULL, 459 &sysctl_node, 460 CTLFLAG_READWRITE, 461 CTLTYPE_INT, "current", NULL, sysctl_cpuspeed_cur, 462 1, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 463 CTL_CREATE, CTL_EOL) == 0) { 464 } else 465 printf("couldn't create 'current' node\n"); 466 467 if (sysctl_createv(NULL, 0, NULL, 468 &sysctl_node, 469 CTLFLAG_READWRITE, 470 CTLTYPE_STRING, "available", NULL, sysctl_cpuspeed_available, 471 2, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 472 CTL_CREATE, CTL_EOL) == 0) { 473 } else 474 printf("couldn't create 'available' node\n"); 475 printf("speed: %d\n", curcpu()->ci_khz); 476 477 /* support cpufreq */ 478 snprintf(cf->cf_name, CPUFREQ_NAME_MAX, "Intrepid"); 479 cf->cf_state[0].cfs_freq = sc->sc_spd_hi; 480 cf->cf_state[1].cfs_freq = sc->sc_spd_lo; 481 cf->cf_state_count = 2; 482 cf->cf_mp = FALSE; 483 cf->cf_cookie = sc; 484 cf->cf_get_freq = obio_get_freq; 485 cf->cf_set_freq = obio_set_freq; 486 /* 487 * XXX 488 * cpufreq_register() calls xc_broadcast() which relies on kthreads 489 * running so we need to postpone it 490 */ 491 config_interrupts(sc->sc_dev, obio_setup_cpufreq); 492} 493 494static void 495obio_set_cpu_speed(struct obio_softc *sc, int fast) 496{ 497 498 if (sc->sc_voltage < 0) 499 return; 500 501 if (sc->sc_busspeed >= 0) { 502 /* set voltage and speed via gpio */ 503 if (fast) { 504 bus_space_write_1(sc->sc_tag, sc->sc_bh, 505 sc->sc_voltage, 5); 506 bus_space_write_1(sc->sc_tag, sc->sc_bh, 507 sc->sc_busspeed, 5); 508 } else { 509 bus_space_write_1(sc->sc_tag, sc->sc_bh, 510 sc->sc_busspeed, 4); 511 bus_space_write_1(sc->sc_tag, sc->sc_bh, 512 sc->sc_voltage, 4); 513 } 514 } 515 else { 516 /* set voltage via gpio and speed via the 7447A's DFS bit */ 517 if (fast) { 518 bus_space_write_1(sc->sc_tag, sc->sc_bh, 519 sc->sc_voltage, 5); 520 DELAY(1000); 521 } 522 523 /* set DFS for all cpus */ 524 cpu_set_dfs(fast ? 1 : 2); 525 DELAY(100); 526 527 if (!fast) { 528 bus_space_write_1(sc->sc_tag, sc->sc_bh, 529 sc->sc_voltage, 4); 530 DELAY(1000); 531 } 532 } 533} 534 535static int 536obio_get_cpu_speed(struct obio_softc *sc) 537{ 538 539 if (sc->sc_voltage < 0) 540 return 0; 541 542 if (sc->sc_busspeed >= 0) { 543 if (bus_space_read_1(sc->sc_tag, sc->sc_bh, sc->sc_busspeed) 544 & 1) 545 return 1; 546 } 547 else 548 return cpu_get_dfs() == 1; 549 550 return 0; 551} 552 553static void 554obio_get_freq(void *cookie, void *spd) 555{ 556 struct obio_softc *sc = cookie; 557 uint32_t *freq; 558 559 freq = spd; 560 if (obio_get_cpu_speed(sc) == 0) { 561 *freq = sc->sc_spd_lo; 562 } else 563 *freq = sc->sc_spd_hi; 564} 565 566static void 567obio_set_freq(void *cookie, void *spd) 568{ 569 struct obio_softc *sc = cookie; 570 uint32_t *freq; 571 572 freq = spd; 573 if (*freq == sc->sc_spd_lo) { 574 obio_set_cpu_speed(sc, 0); 575 } else if (*freq == sc->sc_spd_hi) { 576 obio_set_cpu_speed(sc, 1); 577 } else 578 aprint_error_dev(sc->sc_dev, "%s(%d) bogus CPU speed\n", __func__, *freq); 579} 580 581static int 582sysctl_cpuspeed_temp(SYSCTLFN_ARGS) 583{ 584 struct sysctlnode node = *rnode; 585 struct obio_softc *sc = node.sysctl_data; 586 int speed, mhz; 587 588 speed = obio_get_cpu_speed(sc); 589 switch (speed) { 590 case 0: 591 mhz = sc->sc_spd_lo; 592 break; 593 case 1: 594 mhz = sc->sc_spd_hi; 595 break; 596 default: 597 speed = -1; 598 } 599 node.sysctl_data = &mhz; 600 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 601 int new_reg; 602 603 new_reg = *(int *)node.sysctl_data; 604 if (new_reg == sc->sc_spd_lo) { 605 obio_set_cpu_speed(sc, 0); 606 } else if (new_reg == sc->sc_spd_hi) { 607 obio_set_cpu_speed(sc, 1); 608 } else { 609 printf("%s: new_reg %d\n", __func__, new_reg); 610 return EINVAL; 611 } 612 return 0; 613 } 614 return EINVAL; 615} 616 617static int 618sysctl_cpuspeed_cur(SYSCTLFN_ARGS) 619{ 620 struct sysctlnode node = *rnode; 621 struct obio_softc *sc = node.sysctl_data; 622 int speed, mhz; 623 624 speed = obio_get_cpu_speed(sc); 625 switch (speed) { 626 case 0: 627 mhz = sc->sc_spd_lo; 628 break; 629 case 1: 630 mhz = sc->sc_spd_hi; 631 break; 632 default: 633 speed = -1; 634 } 635 node.sysctl_data = &mhz; 636 return sysctl_lookup(SYSCTLFN_CALL(&node)); 637} 638 639static int 640sysctl_cpuspeed_available(SYSCTLFN_ARGS) 641{ 642 struct sysctlnode node = *rnode; 643 struct obio_softc *sc = node.sysctl_data; 644 char buf[128]; 645 646 snprintf(buf, 128, "%d %d", sc->sc_spd_lo, sc->sc_spd_hi); 647 node.sysctl_data = buf; 648 return(sysctl_lookup(SYSCTLFN_CALL(&node))); 649} 650 651SYSCTL_SETUP(sysctl_ams_setup, "sysctl obio subtree setup") 652{ 653 654 sysctl_createv(NULL, 0, NULL, NULL, 655 CTLFLAG_PERMANENT, 656 CTLTYPE_NODE, "machdep", NULL, 657 NULL, 0, NULL, 0, 658 CTL_MACHDEP, CTL_EOL); 659} 660 661#endif /* OBIO_SPEEDCONTROL */ 662