1 /* $NetBSD: clock.c,v 1.51 2009/12/12 13:10:36 phx Exp $ */ 2 3 /* 4 * Copyright (c) 1982, 1990 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * the Systems Programming Group of the University of Utah Computer 9 * Science Department. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * from: Utah $Hdr: clock.c 1.18 91/01/21$ 36 * 37 * @(#)clock.c 7.6 (Berkeley) 5/7/91 38 */ 39 /* 40 * Copyright (c) 1988 University of Utah. 41 * 42 * This code is derived from software contributed to Berkeley by 43 * the Systems Programming Group of the University of Utah Computer 44 * Science Department. 45 * 46 * Redistribution and use in source and binary forms, with or without 47 * modification, are permitted provided that the following conditions 48 * are met: 49 * 1. Redistributions of source code must retain the above copyright 50 * notice, this list of conditions and the following disclaimer. 51 * 2. Redistributions in binary form must reproduce the above copyright 52 * notice, this list of conditions and the following disclaimer in the 53 * documentation and/or other materials provided with the distribution. 54 * 3. All advertising materials mentioning features or use of this software 55 * must display the following acknowledgement: 56 * This product includes software developed by the University of 57 * California, Berkeley and its contributors. 58 * 4. Neither the name of the University nor the names of its contributors 59 * may be used to endorse or promote products derived from this software 60 * without specific prior written permission. 61 * 62 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 63 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 64 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 65 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 66 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 67 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 68 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 69 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 70 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 71 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 72 * SUCH DAMAGE. 73 * 74 * from: Utah $Hdr: clock.c 1.18 91/01/21$ 75 * 76 * @(#)clock.c 7.6 (Berkeley) 5/7/91 77 */ 78 79 #include <sys/cdefs.h> 80 __KERNEL_RCSID(0, "$NetBSD: clock.c,v 1.51 2009/12/12 13:10:36 phx Exp $"); 81 82 #include <sys/param.h> 83 #include <sys/kernel.h> 84 #include <sys/device.h> 85 #include <sys/systm.h> 86 #include <sys/timetc.h> 87 #include <machine/psl.h> 88 #include <machine/cpu.h> 89 #include <amiga/amiga/device.h> 90 #include <amiga/amiga/custom.h> 91 #include <amiga/amiga/cia.h> 92 #ifdef DRACO 93 #include <amiga/amiga/drcustom.h> 94 #include <m68k/include/asm_single.h> 95 #endif 96 #include <amiga/dev/rtc.h> 97 #include <amiga/dev/zbusvar.h> 98 99 #if defined(PROF) && defined(PROFTIMER) 100 #include <sys/PROF.h> 101 #endif 102 103 /* 104 * Machine-dependent clock routines. 105 * 106 * Startrtclock restarts the real-time clock, which provides 107 * hardclock interrupts to kern_clock.c. 108 * 109 * Inittodr initializes the time of day hardware which provides 110 * date functions. 111 * 112 * Resettodr restores the time of day hardware after a time change. 113 * 114 * A note on the real-time clock: 115 * We actually load the clock with amiga_clk_interval-1 instead of amiga_clk_interval. 116 * This is because the counter decrements to zero after N+1 enabled clock 117 * periods where N is the value loaded into the counter. 118 */ 119 120 int clockmatch(struct device *, struct cfdata *, void *); 121 void clockattach(struct device *, struct device *, void *); 122 void cpu_initclocks(void); 123 static void calibrate_delay(struct device *); 124 125 /* the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz. 126 We're using a 100 Hz clock. */ 127 int amiga_clk_interval; 128 int eclockfreq; 129 struct CIA *clockcia; 130 131 static u_int clk_getcounter(struct timecounter *); 132 133 static struct timecounter clk_timecounter = { 134 clk_getcounter, /* get_timecount */ 135 0, /* no poll_pps */ 136 ~0u, /* counter_mask */ 137 0, /* frequency */ 138 "clock", /* name, overriden later */ 139 100, /* quality */ 140 NULL, /* prev */ 141 NULL, /* next */ 142 }; 143 144 CFATTACH_DECL(clock, sizeof(struct device), 145 clockmatch, clockattach, NULL, NULL); 146 147 int 148 clockmatch(struct device *pdp, struct cfdata *cfp, void *auxp) 149 { 150 if (matchname("clock", auxp)) 151 return(1); 152 return(0); 153 } 154 155 /* 156 * Start the real-time clock. 157 */ 158 void 159 clockattach(struct device *pdp, struct device *dp, void *auxp) 160 { 161 const char *clockchip; 162 unsigned short interval; 163 int chipfreq; 164 #ifdef DRACO 165 u_char dracorev; 166 #endif 167 168 if (eclockfreq == 0) 169 eclockfreq = 715909; /* guess NTSC */ 170 171 chipfreq = eclockfreq; 172 173 #ifdef DRACO 174 dracorev = is_draco(); 175 if (dracorev >= 4) { 176 chipfreq = eclockfreq / 7; 177 clockchip = "QuickLogic"; 178 } else if (dracorev) { 179 clockcia = (struct CIA *)CIAAbase; 180 clockchip = "CIA A"; 181 } else 182 #endif 183 { 184 clockcia = (struct CIA *)CIABbase; 185 clockchip = "CIA B"; 186 } 187 188 amiga_clk_interval = chipfreq / hz; 189 190 if (dp != NULL) { /* real autoconfig? */ 191 printf(": %s system hz %d hardware hz %d\n", clockchip, hz, 192 chipfreq); 193 194 clk_timecounter.tc_name = clockchip; 195 clk_timecounter.tc_frequency = chipfreq; 196 tc_init(&clk_timecounter); 197 } 198 199 #ifdef DRACO 200 if (dracorev >= 4) { 201 /* 202 * can't preload anything beforehand, timer is free_running; 203 * but need this for delay calibration. 204 */ 205 206 draco_ioct->io_timerlo = amiga_clk_interval & 0xff; 207 draco_ioct->io_timerhi = amiga_clk_interval >> 8; 208 209 calibrate_delay(dp); 210 211 return; 212 } 213 #endif 214 /* 215 * stop timer A 216 */ 217 clockcia->cra = clockcia->cra & 0xc0; 218 clockcia->icr = 1 << 0; /* disable timer A interrupt */ 219 interval = clockcia->icr; /* and make sure it's clear */ 220 221 /* 222 * load interval into registers. 223 * the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz 224 */ 225 interval = amiga_clk_interval - 1; 226 227 /* 228 * order of setting is important ! 229 */ 230 clockcia->talo = interval & 0xff; 231 clockcia->tahi = interval >> 8; 232 /* 233 * start timer A in continuous mode 234 */ 235 clockcia->cra = (clockcia->cra & 0xc0) | 1; 236 237 calibrate_delay(dp); 238 } 239 240 void 241 cpu_initclocks(void) 242 { 243 #ifdef DRACO 244 unsigned char dracorev; 245 dracorev = is_draco(); 246 if (dracorev >= 4) { 247 draco_ioct->io_timerlo = amiga_clk_interval & 0xFF; 248 draco_ioct->io_timerhi = amiga_clk_interval >> 8; 249 draco_ioct->io_timerrst = 0; /* any value resets */ 250 single_inst_bset_b(draco_ioct->io_status2, DRSTAT2_TMRINTENA); 251 252 return; 253 } 254 #endif 255 /* 256 * enable interrupts for timer A 257 */ 258 clockcia->icr = (1<<7) | (1<<0); 259 260 /* 261 * start timer A in continuous shot mode 262 */ 263 clockcia->cra = (clockcia->cra & 0xc0) | 1; 264 265 /* 266 * and globally enable interrupts for ciab 267 */ 268 #ifdef DRACO 269 if (dracorev) /* we use cia a on DraCo */ 270 single_inst_bset_b(*draco_intena, DRIRQ_INT2); 271 else 272 #endif 273 custom.intena = INTF_SETCLR | INTF_EXTER; 274 275 } 276 277 void 278 setstatclockrate(int hertz) 279 { 280 } 281 282 /* 283 * Returns ticks since last recorded clock "tick" 284 * (i.e. clock interrupt). 285 */ 286 static u_int 287 clk_gettick(void) 288 { 289 u_int interval; 290 u_char hi, hi2, lo; 291 292 #ifdef DRACO 293 if (is_draco() >= 4) { 294 hi2 = draco_ioct->io_chiprev; /* latch timer */ 295 hi = draco_ioct->io_timerhi; 296 lo = draco_ioct->io_timerlo; 297 interval = ((hi<<8) | lo); 298 if (interval > amiga_clk_interval) /* timer underflow */ 299 interval = 65536 + amiga_clk_interval - interval; 300 else 301 interval = amiga_clk_interval - interval; 302 303 } else 304 #endif 305 { 306 hi = clockcia->tahi; 307 lo = clockcia->talo; 308 hi2 = clockcia->tahi; 309 if (hi != hi2) { 310 lo = clockcia->talo; 311 hi = hi2; 312 } 313 314 interval = (amiga_clk_interval - 1) - ((hi<<8) | lo); 315 316 /* 317 * should read ICR and if there's an int pending, adjust 318 * interval. However, since reading ICR clears the interrupt, 319 * we'd lose a hardclock int, and this is not tolerable. 320 */ 321 } 322 323 return interval; 324 } 325 326 static u_int 327 clk_getcounter(struct timecounter *tc) 328 { 329 static int prev_hardclock; 330 static u_int prev_counter; 331 int cur_hardclock; 332 u_int counter; 333 334 do { 335 cur_hardclock = hardclock_ticks; 336 counter = clk_gettick(); 337 } while (cur_hardclock != hardclock_ticks); 338 339 /* 340 * Handle the situation of a wrapped interval counter, while 341 * the hardclock() interrupt was not yet executed to update 342 * hardclock_ticks. 343 */ 344 if (cur_hardclock < prev_hardclock) 345 cur_hardclock = prev_hardclock; 346 if (counter < prev_counter && cur_hardclock == prev_hardclock) 347 cur_hardclock++; 348 349 prev_hardclock = cur_hardclock; 350 prev_counter = counter; 351 352 return cur_hardclock * amiga_clk_interval + counter; 353 } 354 355 /* 356 * Calibrate delay loop. 357 * We use two iterations because we don't have enough bits to do a factor of 358 * 8 with better than 1%. 359 * 360 * XXX Note that we MUST stay below 1 tick if using clk_gettick(), even for 361 * underestimated values of delaydivisor. 362 * 363 * XXX the "ns" below is only correct for a shift of 10 bits, and even then 364 * off by 2.4% 365 */ 366 static void 367 calibrate_delay(struct device *dp) 368 { 369 unsigned long t1, t2; 370 extern u_int32_t delaydivisor; 371 /* XXX this should be defined elsewhere */ 372 373 if (dp) 374 printf("Calibrating delay loop... "); 375 376 do { 377 t1 = clk_gettick(); 378 delay(1024); 379 t2 = clk_gettick(); 380 } while (t2 <= t1); 381 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 382 delaydivisor = (delaydivisor * t2 + 1023) >> 10; 383 #ifdef DEBUG 384 if (dp) 385 printf("\ndiff %ld us, new divisor %u/1024 us\n", t2, 386 delaydivisor); 387 do { 388 t1 = clk_gettick(); 389 delay(1024); 390 t2 = clk_gettick(); 391 } while (t2 <= t1); 392 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 393 delaydivisor = (delaydivisor * t2 + 1023) >> 10; 394 if (dp) 395 printf("diff %ld us, new divisor %u/1024 us\n", t2, 396 delaydivisor); 397 #endif 398 do { 399 t1 = clk_gettick(); 400 delay(1024); 401 t2 = clk_gettick(); 402 } while (t2 <= t1); 403 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 404 delaydivisor = (delaydivisor * t2 + 1023) >> 10; 405 #ifdef DEBUG 406 if (dp) 407 printf("diff %ld us, new divisor ", t2); 408 #endif 409 if (dp) 410 printf("%u/1024 us\n", delaydivisor); 411 } 412 413 #if notyet 414 415 /* implement this later. I'd suggest using both timers in CIA-A, they're 416 not yet used. */ 417 418 #include "clock.h" 419 #if NCLOCK > 0 420 /* 421 * /dev/clock: mappable high resolution timer. 422 * 423 * This code implements a 32-bit recycling counter (with a 4 usec period) 424 * using timers 2 & 3 on the 6840 clock chip. The counter can be mapped 425 * RO into a user's address space to achieve low overhead (no system calls), 426 * high-precision timing. 427 * 428 * Note that timer 3 is also used for the high precision profiling timer 429 * (PROFTIMER code above). Care should be taken when both uses are 430 * configured as only a token effort is made to avoid conflicting use. 431 */ 432 #include <sys/proc.h> 433 #include <sys/resourcevar.h> 434 #include <sys/ioctl.h> 435 #include <sys/malloc.h> 436 #include <uvm/uvm_extern.h> 437 #include <amiga/amiga/clockioctl.h> 438 #include <sys/specdev.h> 439 #include <sys/vnode.h> 440 #include <sys/mman.h> 441 442 int clockon = 0; /* non-zero if high-res timer enabled */ 443 #ifdef PROFTIMER 444 int profprocs = 0; /* # of procs using profiling timer */ 445 #endif 446 #ifdef DEBUG 447 int clockdebug = 0; 448 #endif 449 450 /*ARGSUSED*/ 451 int 452 clockopen(dev_t dev, int flags) 453 { 454 #ifdef PROFTIMER 455 #ifdef PROF 456 /* 457 * Kernel profiling enabled, give up. 458 */ 459 if (profiling) 460 return(EBUSY); 461 #endif 462 /* 463 * If any user processes are profiling, give up. 464 */ 465 if (profprocs) 466 return(EBUSY); 467 #endif 468 if (!clockon) { 469 startclock(); 470 clockon++; 471 } 472 return(0); 473 } 474 475 /*ARGSUSED*/ 476 int 477 clockclose(dev_t dev, int flags) 478 { 479 (void) clockunmmap(dev, (void *)0, curproc); /* XXX */ 480 stopclock(); 481 clockon = 0; 482 return(0); 483 } 484 485 /*ARGSUSED*/ 486 int 487 clockioctl(dev_t dev, u_long cmd, void *data, int flag, struct proc *p) 488 { 489 int error = 0; 490 491 switch (cmd) { 492 493 case CLOCKMAP: 494 error = clockmmap(dev, (void **)data, p); 495 break; 496 497 case CLOCKUNMAP: 498 error = clockunmmap(dev, *(void **)data, p); 499 break; 500 501 case CLOCKGETRES: 502 *(int *)data = CLK_RESOLUTION; 503 break; 504 505 default: 506 error = EINVAL; 507 break; 508 } 509 return(error); 510 } 511 512 /*ARGSUSED*/ 513 void 514 clockmap(dev_t dev, int off, int prot) 515 { 516 return((off + (INTIOBASE+CLKBASE+CLKSR-1)) >> PGSHIFT); 517 } 518 519 int 520 clockmmap(dev_t dev, void **addrp, struct proc *p) 521 { 522 int error; 523 struct vnode vn; 524 struct specinfo si; 525 int flags; 526 527 flags = MAP_FILE|MAP_SHARED; 528 if (*addrp) 529 flags |= MAP_FIXED; 530 else 531 *addrp = (void *)0x1000000; /* XXX */ 532 vn.v_type = VCHR; /* XXX */ 533 vn.v_specinfo = &si; /* XXX */ 534 vn.v_rdev = dev; /* XXX */ 535 error = vm_mmap(&p->p_vmspace->vm_map, (vm_offset_t *)addrp, 536 PAGE_SIZE, VM_PROT_ALL, flags, (void *)&vn, 0); 537 return(error); 538 } 539 540 int 541 clockunmmap(dev_t dev, void *addr, struct proc *p) 542 { 543 int rv; 544 545 if (addr == 0) 546 return(EINVAL); /* XXX: how do we deal with this? */ 547 uvm_deallocate(p->p_vmspace->vm_map, (vm_offset_t)addr, PAGE_SIZE); 548 return 0; 549 } 550 551 void 552 startclock(void) 553 { 554 register struct clkreg *clk = (struct clkreg *)clkstd[0]; 555 556 clk->clk_msb2 = -1; clk->clk_lsb2 = -1; 557 clk->clk_msb3 = -1; clk->clk_lsb3 = -1; 558 559 clk->clk_cr2 = CLK_CR3; 560 clk->clk_cr3 = CLK_OENAB|CLK_8BIT; 561 clk->clk_cr2 = CLK_CR1; 562 clk->clk_cr1 = CLK_IENAB; 563 } 564 565 void 566 stopclock(void) 567 { 568 register struct clkreg *clk = (struct clkreg *)clkstd[0]; 569 570 clk->clk_cr2 = CLK_CR3; 571 clk->clk_cr3 = 0; 572 clk->clk_cr2 = CLK_CR1; 573 clk->clk_cr1 = CLK_IENAB; 574 } 575 #endif 576 577 #endif 578 579 580 #ifdef PROFTIMER 581 /* 582 * This code allows the amiga kernel to use one of the extra timers on 583 * the clock chip for profiling, instead of the regular system timer. 584 * The advantage of this is that the profiling timer can be turned up to 585 * a higher interrupt rate, giving finer resolution timing. The profclock 586 * routine is called from the lev6intr in locore, and is a specialized 587 * routine that calls addupc. The overhead then is far less than if 588 * hardclock/softclock was called. Further, the context switch code in 589 * locore has been changed to turn the profile clock on/off when switching 590 * into/out of a process that is profiling (startprofclock/stopprofclock). 591 * This reduces the impact of the profiling clock on other users, and might 592 * possibly increase the accuracy of the profiling. 593 */ 594 int profint = PRF_INTERVAL; /* Clock ticks between interrupts */ 595 int profscale = 0; /* Scale factor from sys clock to prof clock */ 596 char profon = 0; /* Is profiling clock on? */ 597 598 /* profon values - do not change, locore.s assumes these values */ 599 #define PRF_NONE 0x00 600 #define PRF_USER 0x01 601 #define PRF_KERNEL 0x80 602 603 void 604 initprofclock(void) 605 { 606 #if NCLOCK > 0 607 struct proc *p = curproc; /* XXX */ 608 609 /* 610 * If the high-res timer is running, force profiling off. 611 * Unfortunately, this gets reflected back to the user not as 612 * an error but as a lack of results. 613 */ 614 if (clockon) { 615 p->p_stats->p_prof.pr_scale = 0; 616 return; 617 } 618 /* 619 * Keep track of the number of user processes that are profiling 620 * by checking the scale value. 621 * 622 * XXX: this all assumes that the profiling code is well behaved; 623 * i.e. profil() is called once per process with pcscale non-zero 624 * to turn it on, and once with pcscale zero to turn it off. 625 * Also assumes you don't do any forks or execs. Oh well, there 626 * is always adb... 627 */ 628 if (p->p_stats->p_prof.pr_scale) 629 profprocs++; 630 else 631 profprocs--; 632 #endif 633 /* 634 * The profile interrupt interval must be an even divisor 635 * of the amiga_clk_interval so that scaling from a system clock 636 * tick to a profile clock tick is possible using integer math. 637 */ 638 if (profint > amiga_clk_interval || (amiga_clk_interval % profint) != 0) 639 profint = amiga_clk_interval; 640 profscale = amiga_clk_interval / profint; 641 } 642 643 void 644 startprofclock(void) 645 { 646 unsigned short interval; 647 648 /* stop timer B */ 649 clockcia->crb = clockcia->crb & 0xc0; 650 651 /* load interval into registers. 652 the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz */ 653 654 interval = profint - 1; 655 656 /* order of setting is important ! */ 657 clockcia->tblo = interval & 0xff; 658 clockcia->tbhi = interval >> 8; 659 660 /* enable interrupts for timer B */ 661 clockcia->icr = (1<<7) | (1<<1); 662 663 /* start timer B in continuous shot mode */ 664 clockcia->crb = (clockcia->crb & 0xc0) | 1; 665 } 666 667 void 668 stopprofclock(void) 669 { 670 /* stop timer B */ 671 clockcia->crb = clockcia->crb & 0xc0; 672 } 673 674 #ifdef PROF 675 /* 676 * profclock() is expanded in line in lev6intr() unless profiling kernel. 677 * Assumes it is called with clock interrupts blocked. 678 */ 679 void 680 profclock(void *pc, int ps) 681 { 682 /* 683 * Came from user mode. 684 * If this process is being profiled record the tick. 685 */ 686 if (USERMODE(ps)) { 687 if (p->p_stats.p_prof.pr_scale) 688 addupc(pc, &curproc->p_stats.p_prof, 1); 689 } 690 /* 691 * Came from kernel (supervisor) mode. 692 * If we are profiling the kernel, record the tick. 693 */ 694 else if (profiling < 2) { 695 register int s = pc - s_lowpc; 696 697 if (s < s_textsize) 698 kcount[s / (HISTFRACTION * sizeof (*kcount))]++; 699 } 700 /* 701 * Kernel profiling was on but has been disabled. 702 * Mark as no longer profiling kernel and if all profiling done, 703 * disable the clock. 704 */ 705 if (profiling && (profon & PRF_KERNEL)) { 706 profon &= ~PRF_KERNEL; 707 if (profon == PRF_NONE) 708 stopprofclock(); 709 } 710 } 711 #endif 712 #endif 713