; Internal memory allocation

; 00-07:  Register bank 0         -- used for non-interrupt code
; 08-0F:  Register bank 1         -- used for interrupt code
; 10-17:  Register bank 2         \
; 18-1F:  Register bank 3          > misc. variables go here
; 20-2F:  Bit addressable space   /
; 30-7F:  Generic memory          -- used for stack

                ; Constants

                .equ maxflash,160       ; max. time on hook in 1/200 secs
                .equ minflash,50        ; min. time for flash in 1/200 secs
                .equ digitpause,40      ; min. interdigit pause in 1/200 secs
                .equ limbotimeout,60    ; dial tone timeout in seconds
                .equ dialtonedelay,50   ; delay until dialtone in 1/200 secs
                .equ answerdelay,4      ; delay from answer to connect
                                        ; in 1/200 seconds
                .equ dialqsize,23       ; Can buffer up 22 digits for pulsing
                                        ; out on external line
                .equ redialsavesize,21  ; Can re-dial up to 20 digits
                .equ xforwardsize,21    ; Can forward to external numbers
                                        ; up to 20 digits long.
                .equ speeddialsize,21   ; Can speed-dial up to 20 digits.

                .org 0x3b00
redialtbl:      .skip redialsavesize*8  ; Must not go over a page boundary
                .org 0x3c00
xforwardtbl:    .skip xforwardsize*8    ; Ditto
                .org 0x3d00
speeddialtbl:   .skip speeddialsize*10  ; Ditto

                ; Constants for the digit outpulser

                .equ dgmaketime,12      ; digit pulse time in 1/200 sec
                .equ dgbreaktime,8      ; interdigitpulse time in 1/200 sec
                .equ dgdigitpause,160   ; interdigit time in 1/200 sec
                .equ dgflashtime,100    ; flash time in 1/200 second

                .equ dtmfdigittime,18   ; DTMF digit make time in 1/200 sec
                .equ dtmfdigitpause,12  ; DTMF digit break time in 1/200 sec

                .equ hookreg,0x4000     ; hookswitch status bits
                .equ net1reg,0x4200
                .equ net0reg,0x4400
                .equ ringreg,0x4600
                .equ tonereg,0x4800
                .equ miscreg,0x4000
                .equ dtmfregs,0x6000    ; spaced 0x400 apart
                .equ dtmfregpage,0x60

                .equ tickcnt1,0x10
                .equ ringscaler,0x11
                .equ ringphase,0x12
                .equ channelflags,0x13
                .equ lineflags,0x14
                .equ lineptr,0x15
                .equ channelptr,0x16
                .equ tickcnt2,0x17
                .equ dtmf_flags,0x18
                .equ pulsegentimer,0x19

                .flag hookflag,0x20.0
                .flag difflag,0x20.1
                .flag extlineflag,0x20.2
                .flag lastchan,0x20.3
                .flag dialqflag,0x20.4
                .flag dialdoneflag,0x20.5
                .flag dtmftransflag,0x20.6

                .equ tonephase,0x28

                ; External variables

                .org 0x3a00

                ; This table has four bytes for each line:  Timer, scan
                ; state, mainline flag, and digit pulse counter.
                ; It must not cross a 256-byte page boundary so we can
                ; move up and down in it by just manipulating DPL.

hookscantbl:    .skip 8*4
ringtable:      .skip 8*4
ringshadowreg:  .skip 1
toneshadowreg:  .skip 1
miscshadowreg:  .skip 1
net0shadowreg:  .skip 1
net1shadowreg:  .skip 1
tonetbl:        .skip 3
call_states:    .skip 3
call_origs:     .skip 3
call_dests:     .skip 3
call_timers:    .skip 3
limboflags:     .skip 1
dtmf_digits:    .skip 3
extringtimer:   .skip 1
extlflags:      .skip 1
extringenab:    .skip 1
dialqueue:      .skip 2+dialqsize
pulsegenstate:  .skip 1
callvartbl:     .skip 3*3
ringagainqueue: .skip 9
extinchannel:   .skip 1
holdtable:      .skip 8
parktable:      .skip 8
forwardtable:   .skip 8
speedowntbl:    .skip 10

                .org 0

                ljmp    start

                .org 11

                ljmp    tick_isr

                ; CPU initialization
                ; ------------------

                ; Initialize CPU registers.

start:          mov     SP,#0x2f        ; Stack at 30H
                mov     PSW,#0          ; Registers at 0H
                mov     TCON,#0         ; Disable timers
                mov     IE,#0           ; Disable all interrupts
                mov     IP,#0
                mov     PCON,#0         ; Initialize other registers.
                mov     P1,#0xfd
                mov     P3,#0xff

                ; Hardware initialization
                ; -----------------------

                ; Clear all relays and tones

                clr     a
                mov     dptr,#net1reg
                movx    @dptr,a
                mov     dptr,#net0reg
                movx    @dptr,a
                mov     dptr,#ringreg
                movx    @dptr,a
                mov     dptr,#miscreg
                movx    @dptr,a
                mov     dptr,#0x4A00
                movx    @dptr,a

                ; Data area initialization
                ; ------------------------

                ; Initialize the tables used by the line scanner.

                mov     dptr,#hookscantbl
                mov     r1,#8*4
                clr     a
clearline:      movx    @dptr,a
                inc     dptr
                djnz    r1,clearline

                ; Initialize all lines to "not ringing".

                mov     dptr,#ringtable
                mov     r1,#8
                mov     a,#0xff
clearring:      movx    @dptr,a
                inc     dptr
                inc     dptr
                inc     dptr
                movx    @dptr,a
                inc     dptr
                djnz    r1,clearring

                ; Initialize the tone table to "all tones off"

                mov     dptr,#tonetbl
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a

                ; Initialize the call state table to "all channels idle"

                mov     dptr,#call_states
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a

                ; Initialize the call timers

                mov     dptr,#call_timers
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a

                ; Initialize the external ring variables

                clr     a
                mov     dptr,#extringtimer
                movx    @dptr,a
                mov     dptr,#extlflags
                movx    @dptr,a
                clr     extlineflag

                ; Initialize the shadow registers.

                clr     a
                mov     dptr,#ringshadowreg
                movx    @dptr,a
                mov     dptr,#toneshadowreg
                movx    @dptr,a
                mov     dptr,#net0shadowreg
                movx    @dptr,a
                mov     dptr,#net1shadowreg
                movx    @dptr,a
                mov     dptr,#miscshadowreg
                movx    @dptr,a

                ; Initialize the timers maintained by the interrupt routine.

                mov     tickcnt1,#1
                mov     tickcnt2,#1
                mov     ringscaler,#1
                mov     ringphase,#0
                mov     tonephase,#0

                ; Initialize the channel flags to "all channels available"

                mov     channelflags,#0x07

                ; External incoming calls should ring all lines by default.

                mov     a,#0xff
                mov     dptr,#extringenab
                movx    @dptr,a

                ; Initialize the ring again queue

                mov     dptr,#ringagainqueue
                clr     a
                movx    @dptr,a

                ; Initialize hold table.

                mov     dptr,#holdtable
                lcall   clear8

                ; Initialize park table.

                mov     dptr,#parktable
                lcall   clear8

                ; Initialize call forward table.

                mov     dptr,#forwardtable
                lcall   clear8

                ; Initialize the redial save table

                mov     dptr,#redialtbl
                mov     r0,#8
rdcloop:        clr     a
                movx    @dptr,a
                mov     a,#redialsavesize
                add     a,DPL
                mov     DPL,a
                djnz    r0,rdcloop

                ; Initialize the speed dial table

                mov     dptr,#speeddialtbl
                mov     r0,#10
sdcloop:        clr     a
                movx    @dptr,a
                mov     a,#speeddialsize
                add     a,DPL
                mov     DPL,a
                djnz    r0,sdcloop

                ; No incoming outside call is in progress.

                mov     dptr,#extinchannel
                clr     a
                movx    @dptr,a

                ; Initialize the limbo flags

                clr     a
                mov     dptr,#limboflags
                movx    @dptr,a

                ; Initialize the line flags to "no lines in use".

                mov     lineflags,#0

                ; Start scanning at the first line

                mov     lineptr,#0

                ; Initialize the DTMF flags

                mov     dtmf_flags,#0
                mov     dptr,#dtmf_digits
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a

                ; Interrupt and serial port initialization
                ; ----------------------------------------

                ; Set both timers to free running, 8 bit auto reload.

                mov     TMOD,#0x22

                ; Set up timer 0 to generate tick interrupts at the
                ; slowest possible rate (3600/sec).

                mov     TH0,#0
                setb    IP.1
                setb    IE.1
                setb    IE.7
                setb    TCON.4

                ; Reset the outgoing digit queue.  This must be done
                ; here because the subroutine disables and re-enables
                ; interrupts.

                lcall   resetdigitq

                ; Start baud rate generator and initialize serial port.

                mov     TH1,#0xfd
                setb    TCON.6
                mov     SCON,#0x52

                sjmp    linescan

                ; Subroutine to clear 8 bytes to zero at DPTR

clear8:         mov     r1,#8
                clr     a
cl8l:           movx    @dptr,a
                inc     dptr
                djnz    r1,cl8l
                ret

                ;--------------------------------------------------------
                ; Mainline

                ; If channels are available, scan through the lines to
                ; find one which is offhook and not seized by anything.

linescan:       mov     a,channelflags
                jnz     linescan2a
                ljmp    lsnochan                ; No channels

linescan2a:     mov     r0,lineptr              ; Next line to scan
                lcall   computer0mask           ; Convert to mask
                mov     r0,a
                mov     r1,lineptr

                ; Now we have R0 = bit mask for line to look at,
                ; R1 = number of line to look at

lscanloop:      mov     a,r0
                anl     a,lineflags
                jnz     lsnext                  ; Line already seized.

                ; Line not seized, see if it is offhook.

                mov     a,r1
                lcall   pointtoline
                movx    a,@dptr

                ; Offhook lines go from 0 to 3 then to 1.  While in 3,
                ; they have not been offhook long enough to warrant
                ; dialtone (we don't want a bouncing switchhook to cause
                ; false dialing).  So ignore lines whose state is 3.

                cjne    a,#3,lscheck0
                clr     a
lscheck0:       jz      lsnext                  ; Line on hook.

                ; Found a line which is not seized but is offhook.  Get
                ; a free channel and assign to it.  Keep scanning if more
                ; channels remain.  Otherwise bump the line pointer by
                ; 1 so that when channels become available again, the next
                ; line is scanned.  This assures fair allocation of channels
                ; in case of overload.

                ; R1 = line number, R0 = line mask bit

                ; Seize the line

                mov     a,r0
                orl     lineflags,a

                ; Seize a channel

                lcall   grabchan

                ; Initialize the channel data.

                mov     dptr,#call_states
                mov     a,r2
                add     a,DPL
                mov     DPL,a
                mov     a,#1
                movx    @dptr,a                 ; State 1

                mov     dptr,#call_origs
                mov     a,r2
                add     a,DPL
                mov     DPL,a
                mov     a,r1
                movx    @dptr,a                 ; Originating line number.

                ; If more channels remain, finish the scan.  Otherwise
                ; save where we are and stop scanning.

                clr     a
                cjne    a,channelflags,lsnext
                inc     r1
                cjne    r1,#8,lsnext3
                mov     r1,#0
lsnext3:        mov     lineptr,r1
                sjmp    lsnochan

lsnext:         mov     a,r0
                rl      a
                mov     r0,a
                inc     r1
                cjne    r1,#8,lsnext2
                mov     r1,#0
lsnext2:        mov     a,r1
                cjne    a,lineptr,lscanloop

                ; Fall through here after scanning all 8 lines.
                ; A channel is available or we wouldn't have finished
                ; scanning.

                ; Check if the outside line is ringing and not already
                ; being handled.

                jb      P1.0,lsnochan
                jb      extlineflag,lsnochan

                ; Fire up a channel.

                setb    extlineflag
                lcall   grabchan
                mov     dptr,#call_states
                mov     a,r2
                add     a,DPL
                mov     DPL,a
                mov     a,#7
                movx    @dptr,a                 ; State 7
                mov     a,#200
                mov     dptr,#extringtimer
                movx    @dptr,a                 ; 5 second timeout
                clr     a
                mov     dptr,#extlflags
                movx    @dptr,a                 ; no lines grabbed yet

                ; Loop through the three channels.

lsnochan:       clr     lastchan
                mov     channelptr,#0

                ; Here is where the call processing occurs.  The current
                ; channel is in "channelptr", its state is in the table
                ; "call_states".  When finished with the channel, we must
                ; jump to "nextchan".

chanloop:       mov     a,channelptr
                mov     dptr,#call_states
                movc    a,@a+dptr
                cjne    a,#0,cnotstate0a
                sjmp    cstate0
cnotstate0a:    ljmp    cnotstate0

                ;--------------------------------------------------------
                ; State 0:  Idle channel.
                ;
                ; Traverse the ring again queue, and if an entry is found
                ; where both ends are available, go to work on it.
                ;
                ; Note:  Only idle channels should be in state 0, so don't
                ;        even bother checking the channel allocation flag.

cstate0:        mov     dptr,#ringagainqueue
raloop:         movx    a,@dptr
                jz      radone
                push    DPL
                push    DPH
                mov     r2,a
                anl     a,#0x0f
                lcall   pokeline
                jnc     rabusy
                mov     a,r2
                swap    a
                anl     a,#0x0f
                lcall   pokeline
                jc      rafree
rabusy:         pop     DPH
                pop     DPL
                inc     dptr
                sjmp    raloop
radone:         ljmp    nextchan

                ; Found an entry which can be processed.  Remove it
                ; from the list.

rafree:         pop     DPH
                pop     DPL
rafloop:        inc     DPL
                movx    a,@dptr
                dec     DPL
                movx    @dptr,a
                inc     dptr
                jnz     rafloop

                ; Set up originating and destination line numbers, grab
                ; the originating line.

                mov     a,r2
                anl     a,#0x0f
                mov     r1,a
                lcall   setcalldest             ; Note: 1-9, not 0-7.
                mov     a,r2
                swap    a
                anl     a,#0x0f
                dec     a
                mov     r1,a
                mov     r0,a
                lcall   setcallorig
                lcall   seizeline

                ; Seize this channel.

                mov     r0,channelptr
                lcall   computer0mask
                cpl     a
                anl     channelflags,a

                ; Ring the originating line with a single 1/2 second ring.

                mov     dptr,#ringtable
                mov     a,r1
                rl      a
                rl      a
                add     a,DPL
                mov     DPL,a
                clr     IE.7
                mov     a,ringphase
                inc     a
                cjne    a,#240,sr0_notwrap
                clr     a
sr0_notwrap:    movx    @dptr,a
                inc     dptr
                mov     a,#1
                movx    @dptr,a
                inc     dptr
                inc     dptr
                mov     a,#240
                movx    @dptr,a
                setb    IE.7

                ; Wait for 15 seconds for the line to respond.

                mov     a,#15
                lcall   setcalltimer

                mov     a,#11
                ljmp    cnewstate

cnotstate0:     cjne    a,#1,cnotstate1

                ;--------------------------------------------------------
                ; State 1:  No dial tone yet.  Give dial tone and continue.
                ;           Must also set up the network connection here.
                ;           Start the call timer here to limit the amout
                ;           of time dialtone is given.

state1:         lcall   getdtmfdigit            ; Clear out old garbage
                lcall   settonerelay
                lcall   getorignum
                mov     r0,a
                mov     r1,channelptr
                lcall   connect

                ; If the originator has other lines on hold, remind him
                ; by a double stutter on the dial tone.

s1setups2:      lcall   getorignum
                mov     r3,a
                lcall   getholdnum
                jz      s1nothold
                mov     a,#0x31
                sjmp    s1setdtone

                ; If the originator's extension has a call parked on it,
                ; remind him with a break every 1/2 second.

s1nothold:      mov     dptr,#parktable
                mov     a,r3
                movc    a,@a+dptr
                jz      s1notpark
                mov     a,#0xf1
                sjmp    s1setdtone

                ; If the originator's phone is forwarded, remind him with
                ; dial tone that has a break every second.

s1notpark:      mov     dptr,#forwardtable
                mov     a,r3
                movc    a,@a+dptr
                jz      s1setregtone
                mov     a,#0xe1
                sjmp    s1setdtone

s1setregtone:   mov     a,#1                    ; Dial tone
s1setdtone:     lcall   settone
                mov     a,#limbotimeout         ; Dial tone timeout
                lcall   setcalltimer

                clr     a                       ; Dial state = 0
                mov     r0,a
                lcall   setcallvar

                mov     a,#1                    ; Default ring code = 0
                mov     r0,#0
                lcall   setcallvar

                mov     a,#2
                ljmp    cnewstate               ; Go to state 2.

cnotstate2a:    ljmp    cnotstate2
cnotstate1:     cjne    a,#2,cnotstate2a

                ;--------------------------------------------------------
                ; State 2:  Has dial tone but no digit dialed yet.

                lcall   getorignum
                lcall   checklinestate
                jnz     nothungup2

                ; This "hungup" routine can be called to clear a
                ; call which hasn't progressed beyond the originating
                ; line yet.

hungup2:        lcall   getorignum
                mov     r0,a
                lcall   unseizeline             ; Free line

                ; From here on it can be used for a line going into limbo.

hungup2a:       lcall   freechan                ; Free channel
                lcall   getorignum
                mov     r3,a
                clr     a
                lcall   setholdnum
                lcall   getorignum
                mov     r0,a
                lcall   disconnect              ; Disconnect line
                lcall   cleartonerelay          ; Clear tone relay
                clr     a
                lcall   settone                 ; Tone generator off

                ; The "lastchan" flag is a kludge.  When set, the channel
                ; processing loop aborts without handling the remaining
                ; channels so that the next thing that happens is the
                ; line scanner.  This is so when a call is hung up at
                ; one end, the line scanner can give dial tone to the
                ; other line before a greedy "ring all" state can grab it.
                ; If we didn't do this, the end that didn't hung up would
                ; instantly answer the "ring all" call.

                setb    lastchan

                clr     a                       ; Go to state 0
                ljmp    cnewstate

                ; Still offhook.  Did we get a digit?

nothungup2:     jb      ACC.2,haveevent2

                ; Did we get a DTMF digit?

checkdtmf2:     lcall   getdtmfdigit
                mov     r0,a
                jc      havedigit2a

                ; Check for timeout.

nodigit2a:      lcall   getcalltimer
                jnz     nottimeout2

                ; The line has had dialtone for the maximum allowable
                ; time without dialing.  Release the channel and put the
                ; line in "limbo", where it remains seized (so it can't
                ; get dial tone again) until hung up.

limbo2:         lcall   getorignum
                mov     r0,a
                lcall   computer0mask
                mov     r0,a
                mov     dptr,#limboflags
                movx    a,@dptr
                orl     a,r0
                movx    @dptr,a
                sjmp    hungup2a

nottimeout2:    ljmp    nextchan                ; No digit, keep waiting

haveevent2:     xrl     a,#0x05
                swap    a
                mov     r0,a
                anl     a,#0xf0
                jz      havedigit2a

                ; Check for flash.  5 XOR 6 = 3.  If we get a flash,
                ; and a call is on hold, rejoin it.  Otherwise
                ; reset things as if we had just picked up the phone.

                cjne    a,#0x30,checkdtmf2
                lcall   tryrejoin
                jz      s2rejoined
                ljmp    s1setups2
s2rejoined:     ljmp    cnewstate

                ; Now R0 has the digit that was dialed.  Handle it depending
                ; on the digit collection state.

havedigit2a:    mov     a,#limbotimeout         ; Reset dial tone timeout
                lcall   setcalltimer
                clr     a
                lcall   getcallvar              ; Get dial state
                jnz     not2s0
                cjne    r0,#10,callnum2a        ; State 0
                sjmp    state20
callnum2a:      cjne    r0,#12,callnum2b

                ; A "#" was dialed.  Set DPTR to point at the redial buffer
                ; for the current line and dial the digit string there.

redial2:        lcall   getorignum
                mov     B,#redialsavesize
                mul     AB
                mov     dptr,#redialtbl
                add     a,DPL
                mov     DPL,a
                ljmp    s2dialstring

callnum2b:      cjne    r0,#11,callnum2c

                ; A "*" was dialed.  Go to state 7 to get the speed dial
                ; number.

                clr     a
                mov     r0,#7
                lcall   setcallvar
                ljmp    nextchan

callnum2c:      ljmp    callnum2

                ; A "0" was dialed.  Kill dialtone, advance to state 1 and
                ; get another digit.

state20:        clr     a
                lcall   settone
                clr     a
                mov     r0,#1
                lcall   setcallvar
                ljmp    nextchan

not2s1a:        ljmp    not2s1
not2s0:         cjne    a,#1,not2s1a            ; State 1
                cjne    r0,#10,not2s00

                ; Code 00 - ring all stations

                ljmp    s2ringall

not2s00:        cjne    r0,#1,not2s01

                ; Code 01x - select ring code.  Go to state 2 to accept
                ; the ring code.

                clr     a
                mov     r0,#2
                lcall   setcallvar
                ljmp    nextchan

                ; Unimplemented 0x code - give fast busy.

not2s01:        cjne    r0,#2,not2s02

                ; Code 02 - Ring Again.  The ring again queue is a string
                ; of bytes where each contains the called station in the
                ; low 4 bits and the calling one in the high 4 bits.
                ; Station numbers are 1-8, the outside line is 9.
                ; The terminating byte is zero.

                lcall   getorignum
                inc     a
                mov     r3,a
                dec     a
                mov     B,#redialsavesize
                mul     AB
                mov     dptr,#redialtbl
                movc    a,@a+dptr
                jz      de2s02
                mov     r4,a
                cjne    a,3,notsamenum

                ; Don't allow ring again on own number.

de2s02:         ljmp    dialerror

                ; The other number is valid and is not our own.  Try calling
                ; it now.

notsamenum:     lcall   pokeline
                jnc     notavail

                ; We got it now!  Go and complete the call.

                cjne    r4,#9,notext2

                ; For the external line, do a full redial of the last number.

                ljmp    redial2

notext2:        mov     a,r4
                dec     a
                mov     r1,a            ; Number to call
                mov     r0,a
                lcall   computer0mask
                mov     r0,a
                ljmp    notbusy2

notavail:       mov     a,r3
                swap    a
                orl     4,a             ; R4 has the byte to enqueue

                ; Scan the queue.  If we are already in the queue, remove
                ; the old entry on the way up.  Count other stations
                ; waiting for the same line in R2.

                mov     r2,#0
                mov     dptr,#ringagainqueue
                mov     P2,DPH
                mov     r1,DPL          ; Two pointers.

sqloop:         movx    a,@dptr
                jz      sqgotend
                xrl     a,r4
                anl     a,#0xf0
                jz      sameorig
                movx    a,@dptr         ; Not same originator -- copy entry
                movx    @r1,a
                inc     r1
                xrl     a,r4
                anl     a,#0x0f
                jnz     sameorig
                inc     r2              ; Same dest -- count entry
sameorig:       inc     dptr
                sjmp    sqloop

                ; (P2,R1) points to free byte -- add our own.

sqgotend:       mov     a,r4
                movx    @r1,a
                inc     r1
                clr     a
                movx    @r1,a

                ; Confirm with dial tone stutters -- one stutter plus an
                ; extra one for each other line waiting.

                mov     a,r2
                inc     a
                inc     a
                swap    a
                orl     a,#1
                ljmp    dialtone2

not2s02:        cjne    r0,#3,not2s03

                ; Code 03.   Speed dial.

                clr     a
                mov     r0,#7
                lcall   setcallvar
                ljmp    nextchan

not2s03:        cjne    r0,#4,not2s04

                ; Code 04 - Call park/retrieve against any extension

                clr     a
                mov     r0,#4
                lcall   setcallvar
                ljmp    nextchan

not2s04:        cjne    r0,#5,not2s05

                ; Code 05 - Call park/retrieve against own extension

                lcall   getorignum
                mov     r3,a

                ; Common to 04x and 05 ... park/unpark against line R3.
                ; First check if the line already has a call parked against
                ; it.  If so, retrieve that call.

parkr3:         lcall   getparknum
                jz      nopark2s05

                lcall   unpark
                ljmp    cnewstate

                ; No call was parked against the extension.  Park the call
                ; we have on hold there.  If nothing on hold, give fast
                ; busy.

nopark2s05:     push    3
                lcall   getorignum
                mov     r3,a
                lcall   getholdnum
                pop     3
                jz      error2s05
                lcall   setparknum
                lcall   getorignum
                mov     r3,a
                clr     a
                lcall   setholdnum
                ljmp    dial1stut2

error2s05:      ljmp    dialerror

not2s05:        cjne    r0,#6,not2s06

                ; Code 06 - set call forward ... go to dial state 5.

                clr     a
                mov     r0,#5
                lcall   setcallvar
                ljmp    nextchan

not2s06:        cjne    r0,#8,not2s08

                ; Code 08 - Configuration options, etc.

                clr     a
                mov     r0,#3
                lcall   setcallvar
                ljmp    nextchan

not2s08:        cjne    r0,#9,not2s09

                ; Code 09.  Try to join in on the incoming outside call
                ;           that is in progress.

                mov     dptr,#extinchannel
                movx    a,@dptr
                jnz     gotextcall2a
code09error:    ljmp    dialerror

                ; We can't join an incoming outside call if we're already
                ; part of it, i.e. have it on hold.

gotextcall2a:   mov     r0,a
                lcall   getorignum
                mov     r3,a
                lcall   getholdnum
                cjne    a,0,gotextcall2
                sjmp    code09error

                ; There is a call.  Clean up everything here first.

gotextcall2:    lcall   freechan
                mov     r0,3
                lcall   disconnect              ; Disconnect line
                lcall   cleartonerelay          ; Clear tone relay
                clr     a
                lcall   settone                 ; Tone generator off

                ; Now join up the new call

                lcall   getorignum
                mov     r0,a
                mov     dptr,#extinchannel
                movx    a,@dptr
                dec     a
                mov     r1,a
                mov     dptr,#call_origs
                mov     a,DPL
                add     a,r1
                mov     DPL,a
                movx    a,@dptr
                mov     r2,a
                lcall   computer0mask
                orl     a,r2
                movx    @dptr,a

                ; Connect to it too.

                lcall   getorignum
                mov     r0,a
                lcall   connect

                ; Bye bye for this channel

                clr     a
                ljmp    cnewstate

not2s09:        ljmp    dialerror

                ; Dial state 2 -- current digit is the ring code.

not2s1:         cjne    a,#2,not2s2

                cjne    r0,#10,not2s110
                mov     r0,#0
not2s110:       mov     a,r0
                clr     c
                subb    a,#10
                jnc     s22dialerror

                mov     a,#1
                lcall   setcallvar

                ; This can be called from elsewhere.

dial1stut2:     mov     a,#0x21                 ; Dial tone with 1 stutter
dialtone2:      lcall   settone
                clr     a
                mov     r0,a
                lcall   setcallvar
                mov     a,#limbotimeout
                lcall   setcalltimer
                ljmp    nextchan
s22dialerror:   ljmp    dialerror

not2s2:         cjne    a,#3,not2s3

                ; Dial state 3 - Code 08x

                cjne    r0,#1,not2s081

                ; Code 081 - Set speed dial.  Go to dial state 6 to get the
                ; number of the speed dial to set.

                clr     a
                mov     r0,#6
                lcall   setcallvar
                ljmp    nextchan

not2s081:       cjne    r0,#2,not2s082

                ; Enable/disable external rings at this phone.

                lcall   getorignum
                mov     r0,a
                lcall   computer0mask
                mov     r0,a
                mov     dptr,#extringenab
                movx    a,@dptr
                xrl     a,r0
                movx    @dptr,a
                anl     0,a
                mov     a,#0x31         ; 2 stutters
                cjne    r0,#0,not2stut
                mov     a,#0x21         ; only one stutter if disabled
not2stut:       ljmp    dialtone2

not2s082:       ljmp    dialerror

not2s3:         cjne    a,#4,not2s4

                ; Dial state 4 - Code 04x.  Check that 'x' is a valid
                ; extension, then join up the park/retrieve code.

                mov     a,r0
                jz      error2s3
                clr     c
                subb    a,#9
                jnc     error2s3
                mov     r3,0
                dec     r3
                ljmp    parkr3
error2s3:       ljmp    dialerror

                ; Dial state 5 - code 06x.  Write 0-9 into the call forward
                ; table.  If 9 (external forward) proceed to collect the
                ; external number.

not2s4:         cjne    a,#5,not2s5

                mov     a,r0
                cjne    a,#10,not2s40
                mov     r0,#0
                sjmp    s2s4ok
not2s40:        lcall   getorignum
                inc     a
                cjne    a,0,not2s4own
                mov     r0,#0
                sjmp    s2s4ok
not2s4own:      mov     a,r0
                clr     c
                subb    a,#10
                jnc     error2s4
s2s4ok:         lcall   getorignum
                mov     dptr,#forwardtable
                add     a,DPL
                mov     DPL,a
                mov     a,r0
                movx    @dptr,a
                cjne    r0,#9,s2s4notext

                ; Forward to an external number.  Initialize call variables
                ; 0 and 1 with DPL/DPH respectively pointing to the
                ; originator's external forward table entry, and call
                ; variable 2 with the number of digits that may be stored
                ; there.

                lcall   getorignum
                mov     B,#xforwardsize
                mul     ab
                mov     dptr,#xforwardtbl
                add     a,DPL
                mov     DPL,a
                mov     r0,a
                clr     a
                movx    @dptr,a                 ; Initialize to empty
                push    DPH
                lcall   setcallvar
                pop     0
                mov     a,#1
                lcall   setcallvar
                mov     r0,#xforwardsize-1
                mov     a,#2
                lcall   setcallvar

                ; Go to state 13 to gather the digits.

                mov     a,#13
                ljmp    cnewstate

s2s4notext:     ljmp    dial1stut2
error2s4:       ljmp    dialerror

not2s5:         cjne    a,#6,not2s6

                ; Dial state 6:  Code 081x -- Set speed dial 'x'

                ; First get the owner of that speed dial entry.

                mov     a,r0
                mov     r3,a                            ; R3 = speed dial #
                mov     dptr,#speedowntbl-1
                movc    a,@a+dptr
                mov     r1,a                            ; R1 = owner
                lcall   getorignum
                mov     r2,a                            ; R2 = orig. number

                ; Compute pointer to the table entry

                mov     a,r3
                dec     a
                mov     B,#speeddialsize
                mul     ab
                mov     dptr,#speeddialtbl
                add     a,DPL
                mov     DPL,a

                ; If not null and not owned by this line, give fast busy.

                movx    a,@dptr
                jz      sdisfree
                mov     a,r2
                clr     c
                subb    a,r1
                jz      sdisfree
                ljmp    dialerror

                ; OK, we can change the table entry.  First null it out and
                ; store the pointer to it.  Note: A=0

sdisfree:       movx    @dptr,a
                mov     r0,DPL
                push    DPH
                lcall   setcallvar
                pop     0
                mov     a,#1
                lcall   setcallvar

                ; Set ownership of the table entry

                mov     dptr,#speedowntbl
                mov     a,r3
                dec     a
                add     a,DPL
                mov     DPL,a
                mov     a,r2
                movx    @dptr,a

                ; Set number of digits allowed to store, and go to state
                ; 13 to collect them.

                mov     r0,#speeddialsize-1
                mov     a,#2
                lcall   setcallvar
                mov     a,#13
                ljmp    cnewstate

                ; Dial state 7:  Speed dial.  Look up the appropriate table
                ; entry.

not2s6:         mov     dptr,#speeddialtbl
                mov     a,r0
                dec     a
                mov     B,#speeddialsize
                mul     ab
                add     a,DPL
                mov     DPL,a

                ; If the entry is not set, give fast busy.

                movx    a,@dptr
                jnz     ok2s6
                ljmp    dialerror

                ; Call the number.

ok2s6:          ljmp    s2dsext


                ; Continue here if the digit was the number to call.

callnum2:       mov     a,r0
                jz      dialerror
                clr     c
                subb    a,#10
                jnc     dialerror

                ; Number is in range 1-9.  Stash it in the redial table.

                lcall   getorignum
                mov     B,#redialsavesize
                mul     AB
                mov     dptr,#redialtbl
                add     a,DPL
                mov     DPL,a
                mov     a,r0
                movx    @dptr,a
                clr     a                       ; Null-terminate the entry
                inc     dptr
                movx    @dptr,a
                mov     a,r0

                cjne    a,#9,notdialout

                ; Dialing '9' while already having the external line on
                ; hold means to send a flash out the external line.  See if
                ; that is what is wanted.

                lcall   getorignum
                mov     r3,a
                lcall   getholdnum              ; Is a channel on hold?
                jz      dialout2a
                mov     r1,channelptr
                dec     a
                mov     channelptr,a
                mov     a,#1
                lcall   getcallvar              ; Get its ext. line flag
                mov     channelptr,r1
                jz      dialout2a

                ; Enqueue the "flash" code.

                mov     a,#15
                lcall   enqueue
                jnc     deflashout2

                ; Go back to the external call.

flashout2ok:    lcall   tryrejoin
                jz      rejoinok
deflashout2:    ljmp    dialerror
rejoinok:       ljmp    cnewstate

                ; Dial out.  If the external line is not free, give busy.

dialout2a:      jb      extlineflag,busy2
dialout2:       setb    extlineflag             ; Grab it

                setb    P1.1                    ; Take it offhook

                lcall   cleartonerelay          ; Remove internal dial tone
                clr     a
                lcall   settone

                ; Wait approximately 1/20 second before connecting the
                ; call through.  That way the originator doesn't get the
                ; "click" from the external line being picked up.

                mov     dptr,#extringtimer
                mov     a,#2
                movx    @dptr,a
                mov     a,#8
                ljmp    cnewstate

                ; Bad digit.  Turn on fast busy tone and go to state 3.

dialerror:      mov     a,#0x13
                sjmp    busy2a

                ; Have a digit 1-8 in R0.  Test the destination line.
                ; If seized or offhook, give busy.

                ; Handle call forward first.  Allow no more than 8 call
                ; forward "hops"; this is a simple method to prevent infinite
                ; call forward loops.

notdialout:     mov     r1,#8

fwloop:         mov     a,r0
                mov     dptr,#forwardtable-1
                movc    a,@a+dptr
                cjne    a,#9,notforwardout

                ; The forward is to an outside line.  If this is the
                ; case, dial the outside number.

                mov     a,r0
                dec     a
                mov     B,#xforwardsize
                mul     ab
                mov     dptr,#xforwardtbl
                add     a,DPL
                mov     DPL,a
                ljmp    s2dsext

notforwardout:  jz      fwdone
                mov     r0,a
                djnz    r1,fwloop
                sjmp    busy2

fwdone:         mov     a,r0
                dec     a
                mov     r0,a
                mov     r1,a                    ; Line number now 0-7 in R0
                rl      a
                rl      a
                mov     dptr,#hookscantbl+2
                movc    a,@a+dptr               ; Get dest. line state
                jnz     busy2
                lcall   computer0mask
                mov     r0,a                    ; Save mask
                anl     a,lineflags
                jz      notbusy2                ; Busy because line seized

                ; Give regular busy tone and go to state 3.

busy2:          mov     a,#0x03
busy2a:         lcall   settone
                mov     a,#limbotimeout         ; Busy tone timeout
                lcall   setcalltimer
                mov     a,#3
                ljmp    cnewstate               ; Go to state 3.

                ; The destination line is not busy.  Seize it, using the
                ; mask still stored in R0.

notbusy2:       mov     r3,#4                   ; State to go to.

notbusy2a:      mov     a,r0
                orl     lineflags,a             ; Seize dest. line

                lcall   setcalldest

                ; Start the destination line ringing, give ringback tone
                ; to the source line, and go to state 4.

                mov     a,#1
                lcall   getcallvar
                mov     dptr,#ringcodetbl
                movc    a,@a+dptr
                mov     r2,a

                mov     r0,1                    ; Dest. line number
                lcall   startring
                mov     a,r1
                swap    a
                orl     a,#2                    ; Ringback + line #
                lcall   settone

                ; Convert the originating number to a bitmap.

                lcall   getorignum
                mov     r0,a
                lcall   computer0mask
                mov     r1,a
                lcall   setcallorig

                ; Indicate that the outside line is not involved

                mov     a,#1
                mov     r0,#0
                lcall   setcallvar

                mov     a,r3
                ljmp    cnewstate


                ; Ring all stations
                ; -----------------

                ; Grab the first available station, start ringing it, and
                ; go to state 10.  State 10 will grab all the other stations
                ; and ring them too.

s2ringall:      lcall   getorignum
                mov     r0,a
                lcall   computer0mask
                orl     a,lineflags
                cpl     a
                mov     r1,#-1
s2rafindloop:   jz      s2ranolines
                clr     c
                rrc     a
                inc     r1
                jnc     s2rafindloop

                ; Got a line, number in R1.  Call it but instead of going
                ; to state 4, go to state 10.

                mov     r0,1
                lcall   computer0mask
                mov     r0,a
                clr     a
                lcall   setcallvar
                mov     r3,#10                  ; State 10
                ljmp    notbusy2a

s2ranolines:    ljmp    busy2

                ; Ring codes (1 char = 1/4 second)
                ;
                ;   0  ---.---.    (default)
                ;   1  --.--.--
                ;   2  -.----..
                ;   3  ----.-..
                ;   4  ---.....
                ;   5  -.......
                ;   6  -.-.....
                ;   7  -.-.-...
                ;   8  -.-.-.-.
                ;   9  --------

ringcodetbl:    .byte   0x77,0xdb,0x3d,0x2f,0x07,0x01,0x05,0x15,0x55,0xff


                ; Dial a digit string from DPTR
                ; -----------------------------

                ; The string is either a single digit 1-8, or 9 followed
                ; by digits to dial on the outside line.

s2dialstring:   movx    a,@dptr
                mov     r0,a
                jnz     s2dsnot0

                ; Back to dial tone if no string saved here.

                ljmp    s1setups2
s2dsnot0:       clr     c
                subb    a,#9
                inc     dptr
                jz      s2dsext

                ; If it's an internal number, go to the regular call code.
                ; This needs the digit in R0.

                ljmp    notdialout

s2dsext:        jnb     extlineflag,s2dsextok
                ljmp    busy2
s2dsextok:      setb    extlineflag             ; Grab external line

                push    DPL
                push    DPH

                ; The following is an edited version of the state 8 -> 9
                ; transition code.

                ; Read the DIP switch.

                mov     dptr,#dtmfregs
                movx    a,@dptr
                mov     c,ACC.7
                mov     dtmftransflag,c

                lcall   resetdigitq
                clr     a
                lcall   setcalltimer

                mov     a,#60
                mov     dptr,#extringtimer
                movx    @dptr,a

                ; Take the outside line offhook but don't connect it -- it
                ; will be connected (unmuted) when dialing is finished.

                setb    P1.1
                clr     a
                lcall   settone
                lcall   settonerelay

                ; Don't save any digits in the redial table.

                clr     a
                mov     r0,a
                lcall   setcallvar

                ; Finally, copy the redial number into the dial queue.
                ; Don't check for queue overflow since we start with the
                ; queue known empty and it must be big enough to hold any
                ; re/speed-dialable number.

sdnext:         pop     DPH
                pop     DPL
                movx    a,@dptr
                jz      sddone
                inc     dptr
                push    DPL
                push    DPH
                mov     c,dtmftransflag
                mov     ACC.4,c
                lcall   enqueue
                sjmp    sdnext

                ; If nothing was enqueued for dialing out (the redial number
                ; was just "9" for some reason), then un-mute the speech
                ; path right away.

sddone:         jb      dialqflag,sddone2
                mov     a,channelptr
                inc     a
                swap    a
                lcall   connectext
                lcall   cleartonerelay

sddone2:        clr     dtmftransflag
                mov     a,#9
                ljmp    cnewstate


cnotstate2:     cjne    a,#3,cnotstate3

                ;--------------------------------------------------------
                ; State 3:  Has busy tone (fast or regular), await
                ;           hangup.  A "flash" here returns us to dial
                ;           tone or a held call.

                lcall   getorignum
                lcall   checklinestate
                jz      hungup3
                cjne    a,#6,notflash3
                lcall   tryrejoin
                jz      cns3
                mov     a,#1
cns3:           ljmp    cnewstate
notflash3:      lcall   getcalltimer
                jnz     nottimeout3
                ljmp    limbo2
hungup3:        ljmp    hungup2
nottimeout3:    ljmp    nextchan

cnotstate3:     cjne    a,#4,cnotstate4

                ;--------------------------------------------------------
                ; State 4:  Destination line ringing.  Continue until
                ;           number of callers is zero or the callee
                ;           picks up.

                lcall   checkcallers
                jnz     stillhere4

                ; The caller(s) hung up.  Stop ringing the destination line,
                ; free the destination line, and leave.

                lcall   getdestnum              ; Get dest. line
                push    ACC
                mov     r0,a
                lcall   stopring                ; Stop ringing it.
                pop     0                       ; Get dest. line
                lcall   unseizeline             ; Unseize it

                ; Clear holds on the current channel.

                lcall   clearholds
                lcall   clearparks

                ; Give up the channel

                clr     a
                lcall   settone
                lcall   cleartonerelay
                lcall   freechan
                clr     a
                ljmp    cnewstate

                ; Check the state of the destination line.  State 1
                ; means picked up and the normal dial tone delay expired.
                ; State 3 means still counting through the dial tone
                ; delay.  Rather than add more states to the switchhook
                ; scanner, just monitor its internal timer from here and
                ; switch the connection through partway through state 3.

stillhere4:     lcall   getdestnum
                lcall   checklinestate
                cjne    a,#3,not4s3

                ; DPTR is still pointing at the switchhook data structure.

                dec     DPL
                dec     DPL
                movx    a,@dptr
                subb    a,#answerdelay
                jc      notpickup4
                sjmp    pickup4

not4s3:         jz      notpickup4

                ; The destination line has been picked up.  Connect the
                ; voice path through.

pickup4:        lcall   cleartonerelay
                clr     a
                lcall   settone
                lcall   getdestnum
                mov     r0,a
                mov     r1,channelptr
                lcall   connect

                ; Add the called number to the set of callers and go to
                ; the conversation state.

                lcall   getdestnum
                mov     r0,a
                lcall   computer0mask
                mov     r1,a
                lcall   getorignum
                orl     1,a
                lcall   setcallorig

                ; Indicate that we do NOT own the outside line.

                clr     a
                mov     r0,a
                lcall   setcallvar

                mov     a,#12
                ljmp    cnewstate

notpickup4:     ljmp    nextchan

cnotstate7a:    ljmp    cnotstate7
cnotstate4:     cjne    a,#7,cnotstate7a

                ;--------------------------------------------------------
                ; State 7.  The outside line is ringing.  Stay in this
                ;           state until it stops or until one of the inside
                ;           lines being rung picks up.

                ; Check if it's still ringing.

                mov     dptr,#extringtimer
                jb      P1.0,nord7
                mov     a,#200
                movx    @dptr,a
                sjmp    gotrd7
nord7:          movx    a,@dptr
                jnz     gotrd7

                ; No ring detect for 5 seconds.  It must have stopped
                ; ringing.  Release all seized lines, release the channel
                ; and exit.

                mov     r0,channelptr           ; Free channel
                lcall   computer0mask
                orl     channelflags,a

                mov     dptr,#extlflags         ; Free lines
                movx    a,@dptr
                push    ACC
                clr     a
                movx    @dptr,a
                pop     ACC
                lcall   cancelset
                clr     extlineflag

                clr     a                       ; Go to state 0
                ljmp    cnewstate

                ; Continue here if the external line is still ringing.
                ; Seize (newly) available lines first.

gotrd7:         mov     dptr,#extringenab       ; Lines to ring
                movx    a,@dptr
                lcall   greedy

                mov     R0,a
                mov     dptr,#extlflags
                movx    a,@dptr                 ; Lines already grabbed
                orl     a,R0
                movx    @dptr,a

                ; Look for an offhook on one of the seized lines.

                lcall   checkset
                jc      pickup7
                ljmp    nextchan

                ; One of the lines has been picked up.  Stop ringing and
                ; get rid of the rest.

pickup7:        mov     r3,1
                mov     r0,1                    ; Picked-up line
                lcall   computer0mask
                cpl     a
                mov     r0,a
                mov     dptr,#extlflags
                movx    a,@dptr
                anl     0,a
                clr     a
                movx    @dptr,a                 ; Zero extlflags
                mov     a,r0
                lcall   cancelset               ; Cancel other lines

                ; Connect the inside line

                mov     r0,3
                mov     r1,channelptr
                lcall   connect

                ; Initialize the set of lines talking

                mov     r0,3
                lcall   computer0mask
                mov     r1,a
                lcall   setcallorig

                ; Take the outside line offhook and connect it to the
                ; appropriate network channel.

                setb    P1.1
                mov     a,channelptr
                inc     a
                swap    a
                lcall   connectext

                clr     dialdoneflag

                ; Indicate that there is a group call in progress that
                ; others can join by dialing 09.

                mov     dptr,#extinchannel
                mov     a,channelptr
                inc     a
                movx    @dptr,a

                ; Register that state 12 has ownership of the outside line.

                mov     a,#1
                mov     r0,a
                lcall   setcallvar

                mov     a,#12
                ljmp    cnewstate

cnotstate7:     cjne    a,#8,cnotstate8

                ;--------------------------------------------------------
                ; State 8.  The "originating" line wants to connect to the
                ;           outside line, which is seized and offhook.
                ;           The extringtimer must run down before we
                ;           connect through.

                mov     dptr,#extringtimer
                movx    a,@dptr
                jnz     s8nextchan

                ; Waited long enough.  Connect through.  Read the DIP switch
                ; to find out whether to translate DTMF to dial pulses or
                ; not.

                mov     dptr,#dtmfregs
                movx    a,@dptr
                mov     c,ACC.7
                mov     dtmftransflag,c
                lcall   resetdigitq
                mov     a,#10
                lcall   setcalltimer

                ; Wait 1.5 seconds before passing any dialing through
                ; to the outside so it can get dialtone first.

                ; NOTE: The ISR monitors this timer directly and will not
                ; dial unless it has run down to zero.

                mov     a,#60
                mov     dptr,#extringtimer
                movx    @dptr,a

                ; Take the outside line offhook and connect it to the
                ; appropriate network channel.

                setb    P1.1
                mov     a,channelptr
                inc     a
                swap    a
                lcall   connectext

                ; In state 9, call variable 0 is the number of digits
                ; already saved in the redial table.  The "9" that got us
                ; this far is already there.

                mov     r0,#1
                clr     a
                lcall   setcallvar

                mov     a,#9
                ljmp    cnewstate

s8nextchan:     ljmp    nextchan


cnotstate9a:    ljmp    cnotstate9
cnotstate8:     cjne    a,#9,cnotstate9a

                ;--------------------------------------------------------
                ; State 9.  Calling out on the outside line.
                ;           Remain here until the inside line hangs up
                ;           since we can't tell anything about the
                ;           state of the outside line.

                lcall   getorignum
                lcall   checklinestate
                jnz     nothungup9

                ; Inside line hung up.   Cancel outgoing dialing if
                ; in progress.

                lcall   resetdigitq

                ; Hang up and disconnect the external line.

hungup9:        clr     P1.1
                clr     a
                lcall   connectext
                clr     extlineflag
                ljmp    hungup2

                ; The connection is still up and A contains the return
                ; code from "checklinestate".  Handle pulse-dialed digits
                ; by queuing them up for resending on the outside line.

nothungup9:     mov     r0,a
                anl     a,#0x0f
                cjne    a,#6,notflash9

                ; Flash.  First of all, verify if we already have someone
                ; on hold, and if so, join that call.

                lcall   tryrejoin
                jnz     notjoined9

                ; It worked.  This channel is ready to set to state 0 but
                ; first we must move over the external line.

                mov     r1,channelptr
                mov     channelptr,r2
                mov     a,#1
                mov     r0,a
                lcall   setcallvar
                mov     channelptr,r1

                ; If the external line is not currently muted, reconnect it
                ; too.

                mov     dptr,#miscshadowreg
                movx    a,@dptr
                anl     a,#0x30
                jz      s9ismuted

                mov     a,r2
                inc     a
                swap    a
                lcall   connectext

s9ismuted:      clr     a
                ljmp    cnewstate

                ; Go to state 12 to hang on to the external line
                ; and provide a point for the caller to return to.  Pulse
                ; dialing out is not possible after this event.

                ; First, verify that a channel is available and if not,
                ; ignore the flash.

notjoined9:     mov     a,channelflags
                jz      notdigit9

                ; Remember in the current channel that we are being held.

                lcall   getorignum
                mov     r3,a
                mov     a,channelptr
                inc     a
                lcall   setholdnum

                ; Disconnect from the current channel

                mov     r0,3
                lcall   disconnect

                ; Get a new channel for the caller.

                lcall   grabchan

                ; Initialize channel R2 to state 1 and set the originating
                ; number.

                mov     dptr,#call_states
                mov     a,DPL
                add     a,2
                mov     DPL,a
                mov     a,#1
                movx    @dptr,a                 ; State 1

                lcall   getorignum
                mov     r3,a

                mov     dptr,#call_origs
                mov     a,DPL
                add     a,2
                mov     DPL,a
                mov     a,r3
                movx    @dptr,a                 ; Originator R3

                ; Indicate to state 12 that we have the outside line and
                ; no inside lines.

                mov     a,#1
                mov     r0,a
                lcall   setcallvar              ; We have the outside line

                mov     r1,#0
                lcall   setcallorig             ; No inside lines

                mov     a,#12
                ljmp    cnewstate

notflash9:      cjne    a,#5,notdigit9

                ; We got a pulse dial digit.  Enqueue it for repeating on
                ; the outside line.  If the outside line supports DTMF,
                ; re-send it as DTMF.

                mov     a,#10
                lcall   setcalltimer
                mov     dptr,#dtmfregs
                movx    a,@dptr
                mov     c,ACC.7

                mov     a,r0
                swap    a
                anl     a,#0x0f

                mov     ACC.4,c

                sjmp    enqdigit

                ; Check for DTMF digits.

notdigit9:      lcall   getdtmfdigit
                jnc     notdtmf9
                mov     r1,a
                mov     a,#10
                lcall   setcalltimer
                mov     a,r1
                jnb     dtmftransflag,transdtmf9
                lcall   savedigit
                sjmp    notdtmf9

                ; Have a DTMF digit.  If 0-9 (codes 1-10) enqueue it.
                ; Otherwise turn off the DTMF-pulse translation.

transdtmf9:     jz      badtone9
                clr     c
                subb    a,#11
                jc      goodtone9
badtone9:       setb    dtmftransflag
                clr     a
                mov     r0,a
                lcall   setcallvar      ; Don't save any more digits.
                ljmp    notdtmf9

goodtone9:      mov     a,r1

                ; Mute audio.  Disconnect the external line from the network
                ; but connect the tone source (turned off) instead, so that
                ; the audio coupling capacitor is bled.  Otherwise it will
                ; glitch the offhook relay when it is reconnected.

enqdigit:       push    ACC
                lcall   savedigit
                clr     a
                lcall   connectext
                clr     a
                lcall   settone
                lcall   settonerelay
                pop     ACC

                lcall   enqueue
                jc      notdtmf9

                ; Queue overflow!  Release the outside line and give
                ; fast busy to the inside line.

                lcall   resetdigitq
                clr     P1.1
                clr     a
                lcall   connectext
                clr     extlineflag
                lcall   settonerelay
                ljmp    dialerror

                ; If the dialdoneflag is set, the ISR has finished pulsing
                ; out digits.

notdtmf9:       jnb     dialdoneflag,nextchan9

                ; However, we don't unmute the audio path until
                ; pulse-dialing stops on the inside line.  This is to
                ; reduce the danger of a glitch in the offhook relay state.

                lcall   getorignum
                lcall   pointtoline
                dec     DPL
                movx    a,@dptr
                cjne    a,#1,nextchan9

                mov     a,channelptr
                inc     a
                swap    a
                lcall   connectext
                lcall   cleartonerelay
                clr     dialdoneflag

                ; If the call timer runs down, turn off DTMF-pulse dial
                ; translation.

nextchan9:      lcall   getcalltimer
                jnz     nextchan9a
                setb    dtmftransflag   ; Don't translate DTMF any more

                clr     a
                mov     r0,a
                lcall   setcallvar      ; Don't save any more digits.
nextchan9a:     ljmp    nextchan

                ; Subroutine to save a digit in the redial table.

savedigit:      mov     r0,a
                clr     a
                lcall   getcallvar
                jz      sdnosave
                mov     r2,a
                lcall   getorignum
                mov     B,#redialsavesize
                mul     AB
                mov     dptr,#redialtbl
                add     a,DPL
                add     a,r2
                mov     DPL,a
                mov     a,r0
                movx    @dptr,a
                inc     dptr            ; Null terminate
                clr     a
                movx    @dptr,a
                mov     a,r2
                inc     a
                mov     r0,a
                clr     c
                subb    a,#redialsavesize-1
                jc      sdsaveptr
                mov     r0,#0
sdsaveptr:      clr     a
                lcall   setcallvar
sdnosave:       ret

cnotstate10a:   ljmp    cnotstate10
cnotstate9:     cjne    a,#10,cnotstate10a

                ;--------------------------------------------------------
                ; State 10.  Ringing all available stations.  Grab any
                ;            stations that become available too.

                ; Find any newly available stations

                mov     a,#0xff                 ; All stations
                lcall   greedy

                ; A is a bitmap of newly seized lines.

                mov     r0,a                    ; Newly grabbed lines
                mov     r1,a                    ; Save for later
                clr     a
                lcall   getcallvar              ; Get set we already have
                orl     0,a
                clr     a
                lcall   setcallvar

                lcall   getdestnum
                rl      a
                rl      a
                mov     dptr,#ringtable+2
                add     a,DPL
                mov     DPL,a
                movx    a,@dptr
                mov     r2,a                    ; Ring pattern
                inc     dptr
                movx    a,@dptr
                mov     r3,a                    ; Ring start time

                ; R1 = bitmap, R2 = ring pattern, R3 = ring start time.

                mov     dptr,#ringtable
rsloop:         mov     a,r1
                jz      ringstartdone
                clr     c
                rrc     a
                mov     r1,a
                jnc     skipentry
                mov     a,r3
                movx    @dptr,a
                inc     dptr
                mov     a,r2
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                mov     a,r3
                movx    @dptr,a
                sjmp    rsskiplast

skipentry:      inc     dptr
                inc     dptr
                inc     dptr
rsskiplast:     inc     dptr
                sjmp    rsloop

                ; Ringing has been started on all the newly grabbed lines.

ringstartdone:  lcall   checkcallers
                jnz     nothungup10

                ; The caller(s) all hung up.  Stop ringing all the owned
                ; lines, free them all, then go away.

                lcall   clearholds
                lcall   clearparks

                clr     a
                lcall   getcallvar
                lcall   cancelset

                clr     a
                lcall   settone
                lcall   cleartonerelay
                lcall   freechan
                clr     a
                ljmp    cnewstate

                ; Now check if any of the destination lines picked up.

nothungup10:    clr     a
                lcall   getcallvar              ; Get line flags
                lcall   checkset
                jnc     nextchan10

                ; Line in R1 picked up.  Stop ringing and release the rest.

                push    1
                clr     a
                lcall   getcallvar              ; Lines owned
                mov     r2,a
                mov     r0,1
                lcall   computer0mask
                cpl     a
                anl     a,2
                lcall   cancelset
                pop     1

                lcall   setcalldest

                ljmp    pickup4                 ; Go connect it

nextchan10:     ljmp    nextchan

cnotstate10:    cjne    a,#11,cnotstate11

                ;--------------------------------------------------------
                ; State 11:  Originating line has received "ring again"
                ;            signal and we are waiting for it to be
                ;            picked up.

                lcall   getorignum
                lcall   checklinestate
                jnz     pickup11
                lcall   getcalltimer
                jnz     notimeout11

                ; Timeout.  Release the originating line and exit.

                lcall   getorignum
                mov     r0,a
                lcall   unseizeline
                lcall   freechan
                clr     a
                ljmp    cnewstate
notimeout11:    ljmp    nextchan

                ; The originating line has picked up.  Make a call to
                ; the number previously stored.

pickup11:       lcall   settonerelay
                lcall   getorignum
                mov     r0,a
                mov     r1,channelptr
                lcall   connect
                lcall   getdestnum
                mov     r0,a
                ljmp    callnum2

cnotstate12a:   ljmp    cnotstate12
cnotstate11:    cjne    a,#12,cnotstate12a

                ;--------------------------------------------------------
                ; State 12.  A "set" of inside lines (bitmap in
                ;            "call_orig") and maybe the outside line
                ;            (flag in call variable #0) are talking.
                ;
                ; Stay here until just one participant remains (if no
                ; holds) or no participants (if holds).

                lcall   checkcallers
                mov     r1,a
                mov     a,#1
                mov     r2,a
                lcall   getcallvar
                jz      s12noext1
                dec     r2
s12noext1:      mov     a,r1
                lcall   holdmagic
                mov     r0,a                    ; Internal lines
                mov     a,#1
                lcall   getcallvar
                jz      s12noext
                mov     a,#1                    ; Count external line too
s12noext:       add     a,r0
                jz      s12bye                  ; If nobody here, go away.
                cjne    a,#1,s12stay

                ; Exactly one caller remains.  Is he on hold?

                lcall   amiheld
                jc      s12stay
                lcall   amiparked
                jc      s12stay

                ; Not on hold.  Take down the call.  If the single remaining
                ; line is the outside one, get rid of everything.

                mov     a,#1
                lcall   getcallvar
                jnz     s12intbye

                ; The single remaining line is an inside one.  Return it
                ; to the "dial tone" state.  First, find which caller
                ; remains.

                lcall   getorignum
                lcall   log2

                ; Convert back to a regular originating number, and give
                ; dial tone.

                mov     r1,0
                lcall   setcallorig
                ljmp    state1

                ; If we had the external line, get rid of it now and
                ; indicate that there is no longer an external call to join.

s12intbye:      lcall   resetdigitq
                clr     P1.1
                clr     a
                lcall   connectext
                clr     extlineflag
                mov     dptr,#extinchannel
                clr     a
                movx    @dptr,a

                ; Ditch the channel.  Make sure the tone stuff is off too.

s12bye:         lcall   clearholds
                lcall   clearparks
                lcall   freechan
                lcall   cleartonerelay
                clr     a
                lcall   settone
                clr     a
                ljmp    cnewstate

                ; If we own the external line, handle un-muting it
                ; after dialing here.

s12stay:        jnb     dialdoneflag,s12notextun
                mov     a,#1
                lcall   getcallvar
                jz      s12notextun
                mov     a,channelptr
                inc     a
                swap    a
                lcall   connectext
                lcall   cleartonerelay
                clr     dialdoneflag
s12notextun:    ljmp    nextchan

cnotstate12:    cjne    a,#13,cnotstate13

                ;--------------------------------------------------------
                ; State 13:  Gathering digits into a buffer.  Call
                ;            variables are:  0 - addr low, 1 - addr high,
                ;            2 - number of bytes remaining.

                lcall   getorignum
                lcall   checklinestate
                jnz     nothungup13
                ljmp    hungup2
nothungup13:    cjne    a,#6,notflash13

                ; Flash.

                lcall   tryrejoin
                ljmp    s1setups2
s13fdone:       ljmp    cnewstate

notflash13:     mov     r1,a
                anl     a,#0x0f
                cjne    a,#5,checkdtmf13

                ; Pulse digit.

                mov     a,r1
                swap    a
                anl     a,#0x0f
                mov     r1,a
                sjmp    s13havedigit

                ; Did we get a DTMF digit?

checkdtmf13:    lcall   getdtmfdigit
                mov     r1,a
                jc      s13havedigit

                ; Check for timeout.

                lcall   getcalltimer
                jz      limbo13
                ljmp    nextchan
limbo13:        ljmp    limbo2

                ; Have a digit in A and R1.  Store 0-9 in the table.
                ; Return to dial tone if 12 (DTMF #).  Go to fast busy
                ; for any other value or if table overflows.

s13havedigit:   jz      error13
                cjne    a,#12,not12s13
dtone13:        ljmp    dial1stut2
not12s13:       clr     c
                subb    a,#11
                jc      storedigit13
error13:        ljmp    dialerror

                ; Decrement number of digits remaining to be stored, go to
                ; fast busy if already zero.

storedigit13:   mov     a,#2
                lcall   getcallvar
                jz      error13
                dec     a
                mov     r0,a
                mov     a,#2
                lcall   setcallvar

                ; Retrieve the pointer, store the digit, increment the
                ; pointer, and store it again.

                clr     a
                lcall   getcallvar
                push    ACC
                mov     a,#1
                lcall   getcallvar
                mov     DPH,a
                pop     DPL
                mov     a,r1
                movx    @dptr,a
                inc     dptr
                clr     a
                movx    @dptr,a         ; Null terminate
                mov     r0,DPL
                clr     a
                push    DPH
                lcall   setcallvar
                pop     0
                mov     a,#1
                lcall   setcallvar

                ; Reset the call timer

                mov     a,#limbotimeout
                lcall   setcalltimer

                ljmp    nextchan

cnotstate13:    ljmp    nextchan

cnewstate:      mov     dptr,#call_states
                push    ACC
                mov     a,channelptr
                add     a,DPL
                mov     DPL,a
                pop     ACC
                movx    @dptr,a

                ; Kludge:  If the "lastchan" flag is set, don't give turns
                ; to any more channels.  See comments elsewhere.

                jbc     lastchan,handlelimbo

nextchan:       inc     channelptr
                mov     a,#3
                cjne    a,channelptr,nextchan2
                sjmp    handlelimbo
nextchan2:      ljmp    chanloop

                ; Here we check for lines "in limbo" finally being hung up.

handlelimbo:    mov     dptr,#limboflags
                movx    a,@dptr
                mov     r2,#1                   ; Mask
                mov     r0,a                    ; Flags
                mov     r1,#0                   ; Counter
limboloop:      mov     a,r2
                anl     a,r0
                jz      limboskip               ; Line not in limbo
                mov     a,r1
                lcall   checklinestate
                jnz     limboskip               ; Line still in limbo

                ; Found a limboed line that is now hung up.  Clear the
                ; limbo flag and also the line seize flag.

                mov     a,r2
                cpl     a
                anl     0,a                     ; R0
                anl     lineflags,a

limboskip:      inc     r1
                clr     c
                mov     a,r2
                rlc     a
                mov     r2,a
                jnc     limboloop

                ; All lines done.  Write the limbo flags back.

                mov     dptr,#limboflags
                mov     a,r0
                movx    @dptr,a
                ljmp    linescan

                ; Set call variable R0 for current channel to A.

setcallvar:     add     a,channelptr
                add     a,channelptr
                add     a,channelptr
                mov     dptr,#callvartbl
                add     a,DPL
                mov     DPL,a
                mov     a,r0
                movx    @dptr,a
                ret

getcallvar:     add     a,channelptr
                add     a,channelptr
                add     a,channelptr
                mov     dptr,#callvartbl
                add     a,DPL
                mov     DPL,a
                movx    a,@dptr
                ret

                ; Reset the digit queue

resetdigitq:    clr     dialqflag
                mov     dptr,#pulsegenstate
                clr     a
                clr     IE.7
                movx    @dptr,a
                mov     dptr,#dialqueue
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                mov     dptr,#0x4A00
                clr     a
                movx    @dptr,a
                setb    IE.7
                ret

                ; Set the network connection of the external line.

connectext:     mov     r0,a
                mov     dptr,#miscshadowreg
                movx    a,@dptr
                anl     a,#0xcf
                orl     a,r0
                movx    @dptr,a
                mov     dptr,#miscreg
                movx    @dptr,a
                ret

                ; Seize a channel, return channel number in R2 and channel
                ; mask bit in R3.  The caller must make sure that a channel
                ; is available before calling this.

grabchan:       mov     a,#1
                mov     r2,#0
lsfindchan:     mov     r3,a
                anl     a,channelflags
                jnz     gotchan
                mov     a,r3
                rl      a
                inc     r2
                sjmp    lsfindchan
gotchan:        cpl     a
                anl     channelflags,a
                ret

                ; Look for a DTMF digit.  Returns C set if A contains
                ; a valid digit.

getdtmfdigit:   mov     a,channelptr
                mov     dptr,#dtmf_digits
                add     a,DPL
                mov     DPL,a
                clr     c
                movx    a,@dptr
                jz      gdt_ret
                anl     a,#0x0f
                push    ACC
                clr     a
                movx    @dptr,a
                pop     ACC
                setb    c
gdt_ret:        ret

                ; Set the call timer

setcalltimer:   push    ACC
                mov     a,channelptr
                mov     dptr,#call_timers
                add     a,DPL
                mov     DPL,a
                pop     ACC
                movx    @dptr,a
                ret

                ; Get the call timer

getcalltimer:   mov     dptr,#call_timers
                mov     a,channelptr
                movc    a,@a+dptr
                ret

                ; Get the originating line number

getorignum:     mov     dptr,#call_origs
                sjmp    getdest2

                ; Get the destination line number

getdestnum:     mov     dptr,#call_dests
getdest2:       mov     a,channelptr
                movc    a,@a+dptr
                ret

                ; Start ringing line R0.  Mask interrupts and initialize
                ; its table entry so that the pattern is begun on the very
                ; next ring update.

                ; The ring pattern is supplied in R2.

startring:      mov     dptr,#ringtable
                mov     a,r0
                rl      a
                rl      a
                add     a,DPL
                mov     DPL,a
                clr     IE.7
                mov     a,ringphase
                add     a,#10
                mov     r0,a
                clr     c
                subb    a,#240
                jnc     sr_notwrap
                mov     a,r0
sr_notwrap:     movx    @dptr,a
                push    ACC
                inc     dptr
                mov     a,r2
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                pop     ACC
                movx    @dptr,a
                setb    IE.7
                ret

                ; Stop ringing line R0.  Write 240 into its table entries
                ; and also turn off its ring relay.  The interrupt routine
                ; will take care of turning off the ring voltage supply.

stopring:       mov     dptr,#ringtable
                mov     a,r0
                rl      a
                rl      a
                add     a,DPL
                mov     DPL,a

                lcall   computer0mask
                cpl     a
                mov     r0,a

                mov     a,#240

                clr     IE.7
                movx    @dptr,a                 ; Clear start time
                inc     dptr
                inc     dptr
                inc     dptr
                movx    @dptr,a

                mov     dptr,#ringshadowreg
                movx    a,@dptr
                anl     a,r0
                movx    @dptr,a
                mov     dptr,#ringreg
                movx    @dptr,a                 ; Clear relay
                setb    IE.7
                ret

settone:        push    ACC
                mov     a,channelptr
                mov     dptr,#tonetbl
                add     a,DPL
                mov     DPL,a
                pop     ACC
                movx    @dptr,a
                ret

pointtoline:    rl      a
                rl      a
                mov     dptr,#hookscantbl+2
                add     a,DPL
                mov     DPL,a                   ; DPTR points to line state
                ret

checklinestate: lcall   pointtoline
                clr     IE.7
                movx    a,@dptr
                push    ACC
                clr     ACC.2
                movx    @dptr,a                 ; Ackowledge digit/flash
                setb    IE.7
                pop     ACC
                ret

unseizeline:    lcall   computer0mask
                cpl     a
                anl     lineflags,a
                ret

seizeline:      lcall   computer0mask
                orl     lineflags,a
                ret

freechan:       mov     r0,channelptr
                lcall   computer0mask
                orl     channelflags,a
                ret

                ; This subroutine connects line R0 with channel R1.
                ; The line must be previously unconnected.

connect:        lcall   computer0mask
connectset:     inc     r1
                mov     r0,a
                mov     a,r1
                jnb     ACC.1,notcnet1
                mov     dptr,#net1shadowreg
                movx    a,@dptr
                orl     a,r0
                movx    @dptr,a
                mov     dptr,#net1reg
                movx    @dptr,a
notcnet1:       mov     a,r1
                jnb     ACC.0,notcnet0
                mov     dptr,#net0shadowreg
                movx    a,@dptr
                orl     a,r0
                movx    @dptr,a
                mov     dptr,#net0reg
                movx    @dptr,a
notcnet0:       ret

disconnect:     lcall   computer0mask
disconnectset:  cpl     a
                mov     r0,a
                mov     dptr,#net1shadowreg
                movx    a,@dptr
                anl     a,r0
                movx    @dptr,a
                mov     dptr,#net1reg
                movx    @dptr,a
                mov     dptr,#net0shadowreg
                movx    a,@dptr
                anl     a,r0
                movx    @dptr,a
                mov     dptr,#net0reg
                movx    @dptr,a
                ret

settonerelay:   mov     r0,channelptr
                lcall   computer0mask
                mov     r0,a
                mov     dptr,#miscshadowreg
                movx    a,@dptr
                orl     a,r0
                movx    @dptr,a
                mov     dptr,#miscreg
                movx    @dptr,a
                ret

cleartonerelay: mov     r0,channelptr
                lcall   computer0mask
                mov     r0,a
                mov     dptr,#miscshadowreg
                movx    a,@dptr
                xch     a,r0
                cpl     a
                anl     a,r0
                movx    @dptr,a
                mov     dptr,#miscreg
                movx    @dptr,a
                ret

computer0mask:  clr     a
                inc     r0
                setb    c
cr0mloop:       rlc     a
                djnz    r0,cr0mloop
                ret

printhex2:      mov     R0,a
                swap    a
                acall   prtdig
                mov     a,R0
prtdig:         anl     a,#0fh
                cjne    a,#10,prt2
prt2:           jc      notalpha
                add     a,#7
notalpha:       add     a,#'0'

putchar:        jnb     SCON.1,putchar
                mov     SBUF,A
                clr     SCON.1
                ret

                ; Subroutine to see if one of several lines got picked up.

                ; Bitmap of the lines to look at is in A.  Returns carry set
                ; and picked-up line in R1, or carry clear if none picked up.

checkset:       mov     R0,a                    ; Line flags
                mov     R1,#-1

cslsloop:       inc     R1
                mov     a,R0
                clr     c
                jz      csret
                rrc     a
                mov     R0,a
                jnc     cslsloop                ; Not this line

                mov     a,R1
                lcall   checklinestate
                cjne    a,#3,csnots3

                ; DPTR is still pointing at the switchhook data structure.

                dec     DPL
                dec     DPL
                movx    a,@dptr
                subb    a,#answerdelay
                jc      cslsloop
                sjmp    cspickup

csnots3:        jz      cslsloop

                ; Line R1 picked up.

cspickup:       setb    c
csret:          ret


                ; Subroutine to store R1 in the call_dest variable of the
                ; current channel.

setcalldest:    mov     dptr,#call_dests
                mov     a,channelptr
                add     a,DPL
                mov     DPL,a
                mov     a,r1
                movx    @dptr,a                 ; Store dest. line #
                ret

                ; Ditto but for call_orig.

setcallorig:    mov     dptr,#call_origs
                mov     a,channelptr
                add     a,DPL
                mov     DPL,a
                mov     a,r1
                movx    @dptr,a
                ret

                ; Subroutine to stop ringing and release a set of lines.
                ; The set is given as a bitmap in A.

cancelset:      mov     r1,a
                cpl     a
                anl     lineflags,a             ; Unseize lines
                mov     r2,#0
cstloop:        mov     a,r1
                jz      cstdone
                clr     c
                rrc     a
                mov     r1,a
                jnc     cstskip
                mov     r0,2
                lcall   stopring
cstskip:        inc     r2
                sjmp    cstloop
cstdone:        ret

                ; Subroutine to grab available lines from a set that we
                ; want.  A is the bitmap of the set we want.

greedy:         cpl     a
                orl     a,lineflags             ; AND with available lines
                cpl     a
                orl     lineflags,a             ; Seize them
                ret

crlf:           mov     a,#13
                lcall   putchar
                mov     a,#10
                ljmp    putchar

puts:           movx    a,@dptr
                jz      pdone
                lcall   putchar
                inc     dptr
                sjmp    puts
pdone:          ret


                ; Subroutine to test a line whose number is in A.

pokeline:       cjne    a,#9,pnotext
                jb      extlineflag,pokefail
                setb    c
                ret
pnotext:        dec     a
                mov     r1,a
                mov     r0,a
                lcall   computer0mask
                anl     a,lineflags
                jnz     pokefail
                mov     a,r1
                lcall   pointtoline
                movx    a,@dptr
                jnz     pokefail
                setb    c
                ret
pokefail:       clr     c
                ret


                ; Subroutine to find the lowest-order set bit in a byte.
                ; Return carry clear if no bits set.

log2:           mov     r0,#0
                clr     c
log2loop:       jz      log2done
                rrc     a
                jc      log2done
                inc     r0
                sjmp    log2loop
log2done:       ret


                ; GROUP CALL LOGIC
                ; ----------------

                ; In the "group call" states, there is no originator or
                ; destination.  There is just a group of equal participants,
                ; a bitmap of which is in the call_orig variable.  The
                ; subroutine below manages people dropping out of the
                ; group.  It checks the state of all the participants,
                ; deletes participants who have hung up, and returns the
                ; number of participants still present.

checkcallers:   lcall   getorignum
                mov     r1,a                    ; Bitmap
                mov     r2,#1                   ; Mask
                mov     r3,#0                   ; Counter
                mov     r4,#0                   ; Counter of participants

ccloop:         mov     a,r1
                anl     a,r2
                jz      ccnext

                ; Found a participant.  Check his state.

                mov     a,r3
                lcall   checklinestate
                cjne    a,#6,ccnotflash
                sjmp    ccflash
ccnotflash:     jnz     cccount

                ; He's gone.

                clr     a
                lcall   setholdnum

                ; Disconnect his network connection, free up his line.

                mov     r0,3
                lcall   disconnect
                mov     r0,3
                lcall   computer0mask
                cpl     a
                anl     1,a
                anl     lineflags,a
                sjmp    ccnext

cccount:        inc     r4
ccnext:         inc     r3
                mov     a,r2
                clr     c
                rlc     a
                mov     r2,a
                jnc     ccloop
                lcall   setcallorig             ; From R1
                mov     a,r4
                ret

                ; The participant has pressed "flash".  See if another call
                ; is already on hold.

ccflash:        lcall   getholdnum
                jz      ccnothold

                ; Nobody on hold any more here.

                push    ACC
                clr     a
                lcall   setholdnum
                pop     ACC

                ; Merge calls.  Move all the participants from this one
                ; to the other call.

                dec     a
                mov     r2,a                    ; R2 = new channel
                lcall   getorignum
                mov     r3,a                    ; R3 = participants here
                mov     dptr,#call_origs
                mov     a,DPL
                add     a,r2
                mov     DPL,a
                movx    a,@dptr
                orl     a,r3
                movx    @dptr,a

                ; If the current channel has the "have external line" flag,
                ; then (a) move it to the other channel, (b) connect the
                ; external line to the other channel.

                mov     a,#1
                lcall   getcallvar
                jz      ccnoext

                mov     r1,channelptr
                mov     channelptr,r2
                mov     a,#1
                mov     r0,a
                lcall   setcallvar
                mov     channelptr,r1

                mov     a,r2
                inc     a
                swap    a
                lcall   connectext

                ; Move everyone over to the other channel

ccnoext:        mov     a,r3
                lcall   disconnectset
                mov     r1,2
                mov     a,r3
                lcall   connectset

                ; Nobody left here

                mov     r1,#0
                lcall   setcallorig
                mov     a,#1
                mov     r0,#0
                lcall   setcallvar

                ; Return with the caller count set to 0.

                clr     a
                ret

                ; The caller is trying to "flash out" of a call.  Try to
                ; get a channel, if none available, ignore the flash.

ccnothold:      mov     a,channelflags
                jz      cccount

                ; Remove the caller from the current session

                mov     r0,3
                lcall   disconnect
                mov     r0,3
                lcall   computer0mask
                cpl     a
                anl     1,a

                ; Get the channel.  This trashes R2 and R3 so stash them.

                push    2
                push    3

                lcall   grabchan

                pop     3

                mov     a,channelptr
                inc     a
                lcall   setholdnum

                ; Initialize channel R2 to state 1 and set the originating
                ; number.

                mov     dptr,#call_states
                mov     a,DPL
                add     a,2
                mov     DPL,a
                mov     a,#1
                movx    @dptr,a                 ; State 1

                mov     dptr,#call_origs
                mov     a,DPL
                add     a,2
                mov     DPL,a
                mov     a,r3
                movx    @dptr,a                 ; Originator R3

                ; All done with this line.

                pop     2
                ljmp    ccnext


                ; This subroutine is called when a caller aborts call
                ; setup (dialing, busy signal) with "flash".

tryrejoin:      lcall   getorignum
                mov     r3,a
                lcall   getholdnum
                jnz     tryok
                inc     a
                ret

                ; Re-joining a held call is possible.  Release this channel,
                ; reconnect the caller to the new channel and add him to
                ; the set talking on that channel.  Return the number of
                ; the new channel in R2.

tryok:          lcall   getorignum
                mov     r3,a                    ; R3 = my line
                lcall   getholdnum
                dec     a
                mov     r2,a                    ; R2 = channel to join
                clr     a
                lcall   setholdnum

tryok2:         lcall   cleartonerelay
                clr     a
                lcall   settone
                lcall   freechan
                mov     r0,3
                lcall   computer0mask
                mov     r1,a                    ; R1 = line mask
                mov     dptr,#call_origs
                mov     a,DPL
                add     a,r2
                mov     DPL,a
                movx    a,@dptr                 ; Add to other channel's
                orl     a,r1                    ; set of lines
                movx    @dptr,a
                mov     r0,3
                lcall   disconnect              ; Disconnect
                mov     r0,3
                mov     r1,2
                lcall   connect                 ; Connect to other channel
                xch     a,r1
                clr     a
                ret

                ; This subroutine unparks a call and joins it.  It's the
                ; same procedure as rejoining a held call and as much as
                ; possible the same code.  The caller supplies the number
                ; against whom the call is parked in R3.

unpark:         lcall   getparknum
                dec     a
                mov     r2,a                    ; R2 = channel to join
                clr     a
                lcall   setparknum
                lcall   getorignum
                mov     r3,a                    ; R3 = my line

                ; Sanity requirement:  We must never get into a call that
                ; we also have on hold.  If unparking a call that is also
                ; on hold, clear the hold too.

                lcall   getholdnum
                dec     a
                cjne    a,2,tryok2
                clr     a
                lcall   setholdnum
                sjmp    tryok2


                ; This subroutine provides the periodic "beep beep" tone
                ; to a line that is on hold.  It is called directly after
                ; "checkcallers" and must preserve ACC.  R2 is the number
                ; of internal callers that must be present for the hold
                ; tone to be generated (1 for internal, 0 for outside calls).

holdmagic:      mov     r1,a
                mov     r0,channelptr
                lcall   computer0mask
                mov     r0,a
                mov     dptr,#miscshadowreg
                movx    a,@dptr
                anl     0,a
                mov     a,r1
                cjne    a,2,hmnot1
                cjne    r0,#0,hmret

                ; There is exactly one caller and the tone relay is not
                ; set.  Set it and apply hold tone.

                lcall   settonerelay
                mov     a,#0x23
                lcall   settone
                sjmp    hmret

hmnot1:         mov     a,r0
                jz      hmret

                ; There is not exactly one caller and the tone relay is
                ; still set.  Clear it again and clear the tone generator.

                lcall   cleartonerelay
                clr     a
                lcall   settone

hmret:          mov     a,r1
                ret

getparknum:     mov     dptr,#parktable
                sjmp    getholdnum1
getholdnum:     mov     dptr,#holdtable
getholdnum1:    mov     a,r3
                movc    a,@a+dptr
                ret

setparknum:     mov     dptr,#parktable
                sjmp    setholdnum1
setholdnum:     mov     dptr,#holdtable
setholdnum1:    push    ACC
                mov     a,r3
                add     a,DPL
                mov     DPL,a
                pop     ACC
                movx    @dptr,a
                ret

amiparked:      mov     dptr,#parktable
                sjmp    amiheld1
amiheld:        mov     dptr,#holdtable
amiheld1:       mov     a,channelptr
                inc     a
                mov     r1,a
                mov     r0,#8
ahloop:         movx    a,@dptr
                cjne    a,1,ahloopbot
                setb    c
                ret
ahloopbot:      inc     dptr
                djnz    r0,ahloop
                clr     c
                ret

clearparks:     mov     dptr,#parktable
                sjmp    clearholds1
clearholds:     mov     dptr,#holdtable
clearholds1:    mov     a,channelptr
                inc     a
                mov     r1,a
                mov     r0,#8
chloop:         movx    a,@dptr
                cjne    a,1,chloopbot
                clr     a
                movx    @dptr,a
chloopbot:      inc     dptr
                djnz    r0,chloop
                ret

                ; Subroutine to put a byte on the digit dial queue for the
                ; external line.

enqueue:        clr     dialdoneflag

                ; Enqueue the digit for pulsing out by the interrupt
                ; service routine.  Returns carry set if successful, clear
                ; if the queue was full and the byte was not enqueued.

                push    ACC
                mov     dptr,#dialqueue
                movx    a,@dptr                 ; Get head ptr
                push    ACC                     ; Save uninc'd pointer
                inc     a
                cjne    a,#dialqsize,enq_ok
                clr     a
enq_ok:         mov     r1,a
                inc     dptr
                clr     IE.7                    ; Interrupts off
                movx    a,@dptr                 ; Get tail ptr
                clr     c
                subb    a,r1                    ; If equal to inc'd head ptr
                jz      enqfull                 ; then the queue is full
                dec     DPL
                mov     a,r1
                movx    @dptr,a
                pop     ACC                     ; Restore uninc'd pointer
                inc     a                       ; Adjust for DPTR offset
                inc     a
                add     a,DPL
                mov     DPL,a                   ; Point to queue entry
                pop     ACC                     ; Restore data to write
                movx    @dptr,a                 ; Write in queue
                setb    dialqflag
                setb    IE.7                    ; Interrupts on
                setb    c
                ret

                ; Error exit

enqfull:        setb    IE.7
                pop     ACC
                pop     ACC
                clr     c
                ret

;-----------------------------------------------------------
; Interrupt service routine
;

tick_isr:       djnz    tickcnt1,isr_exit

                ; Reload the counter and save context.

                mov     tickcnt1,#18
                push    PSW
                push    ACC
                push    DPL
                push    DPH

                mov     dptr,#real_entry
                push    DPL
                push    DPH
isr_exit:       reti

                ; This is executed 200 times per second.  Select register
                ; bank 1 (bank 0 will be restored by the "pop psw" at exit).

real_entry:     mov     PSW,#08h

; ============= LINE SCAN ================================================

                ; Since there are only 8 lines, we might as well scan
                ; all of them 200 times a second, not just the ones
                ; where dial pulses might occur.

                mov     dptr,#hookreg
                movx    a,@dptr                 ; Get hookswitch states
                mov     r1,a
                mov     r3,a                    ; Save for later
                mov     r0,#8                   ; Line counter
                mov     dptr,#hookscantbl

lsloop:         mov     a,r1
                rrc     a
                mov     r1,a
                mov     hookflag,c

                ; DPTR points table entry, hookflag contains hookswitch
                ; status.  Table entry contains
                ;   0 - timer (counts down to zero, then stops)
                ;   1 - state
                ;   2 - mainline communication flag
                ;   4 - dial pulse counter

                ; First, handle the timer.  Timers run up until they hit
                ; 0xff, then they stay there.

                movx    a,@dptr
                mov     r2,a
                inc     a
                jz      hst0
                movx    @dptr,a
                inc     r2                      ; R2 contains timer value

                ; Line loop.  R0 counts down the lines.  R1 saves the
                ; hookswitch bits.  R2 is the timer value associated with
                ; the current line.

hst0:           inc     DPL                     ; Point to byte 1
                movx    a,@dptr
                jnz     notstate0

                ; -----------------------------------------------
                ; State 0:  Line is on hook.

                jb      hookflag,s0_offhook
s0done:         inc     dptr                    ; byte 2
                inc     dptr
                ljmp    lloop_end

s0_offhook:     dec     DPL                     ; Zero the timer
                clr     a
                movx    @dptr,a
                inc     dptr

                mov     a,#5                    ; Go to state 5
                movx    @dptr,a
                inc     dptr
                mov     a,#3
                movx    @dptr,a                 ; Signal mainline
                inc     dptr
                ljmp    lloop_end

notstate0:      cjne    a,#5,notstate5

                ; -----------------------------------------------
                ; State 5:  Line has just gone offhook.

                ; The mainline has gotten the "3" flag indicating
                ; that the line is indeed offhook.  This is so
                ; a call answer can be connected right away.
                ; But the line has not been offhook long enough
                ; to warrant dial tone -- we don't want a noisy
                ; switchhook dialing a bogus digit by bouncing.

                ; If the line is onhook again, return to state 0
                ; and reset the host flag.

                jb      hookflag,s5stilloff
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a
                inc     dptr
                ljmp    lloop_end

                ; Still offhook.  Check the timer, stay in this
                ; state until it times out.

s5stilloff:     dec     DPL
                movx    a,@dptr
                inc     dptr
                cjne    a,#dialtonedelay,s0done

                ; The line has been offhook long enough.  Set
                ; the state to 1.  Set the host flag to 1 only
                ; if it's still 3.

                mov     a,#1
                movx    @dptr,a
                inc     dptr
                movx    a,@dptr
                cjne    a,#3,s5not3
                mov     a,#1
                movx    @dptr,a
s5not3:         inc     dptr
                ljmp    lloop_end

notstate5:      cjne    a,#1,notstate1

                ; -----------------------------------------------
                ; State 1:  Line is offhook

                jb      hookflag,s0done

                ; Line gone onhook again.  Advance to state 2 but
                ; don't tell the mainline anything yet.  Start
                ; the timer.

                dec     DPL                     ; Zero the timer
                clr     a
                movx    @dptr,a
                inc     dptr
                mov     a,#2                    ; Go to state 2
                movx    @dptr,a
                sjmp    s0done

notstate1:      cjne    a,#2,notstate2

                ; -----------------------------------------------
                ; State 2:  Line has gone on hook again, timer is
                ;           running.

                ; First of all, if the line has been on hook for
                ; >maxflash time units, then it's hung up, not
                ; signaling anything.

                jb      hookflag,s2_offhook
                mov     a,r2                    ; Timer value
                clr     c
                subb    a,#maxflash
                jc      s0done                  ; Not yet...

                ; Hung up.  Set both the host flag and the state to 0.

                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a                 ; Signal mainline
                inc     dptr
                ljmp    lloop_end

                ; Gone offhook again in state 2.  Check for the minimum
                ; flash threshold.

s2_offhook:     mov     a,r2
                clr     c
                subb    a,#minflash
                jnc     s2_flash

                ; Too short for a flash, so it's the first pulse of a digit.

                ; Start the timer again and set the count to 1.

                dec     DPL                     ; Timer
                clr     a
                movx    @dptr,a
                inc     dptr                    ; State
                mov     a,#3
                movx    @dptr,a
                inc     dptr                    ; Host flag
                inc     dptr                    ; Pulse count
                mov     a,#1
                movx    @dptr,a
                ljmp    lloop_end

                ; This was a flash.  Signal it and return to state 1.

s2_flash:       mov     a,#1
                movx    @dptr,a
                inc     dptr                    ; Host flag

                mov     a,#6
                movx    @dptr,a
s2_finishup:    inc     dptr                    ; Pulse count
                ljmp    lloop_end

notstate2:      cjne    a,#3,notstate3

                ; -----------------------------------------------
                ; State 3:  Line is offhook between digit pulses.

                jnb     hookflag,s3_onhook
                mov     a,r2
                clr     c
                subb    a,#digitpause
                jnc     s3_gotpause
                inc     dptr
                inc     dptr
                ljmp    lloop_end

                ; Still offhook after digit timeout.  Signal the mainline
                ; and go to state 1.

s3_gotpause:    inc     dptr
                inc     dptr
                movx    a,@dptr                 ; Get count
                swap    a
                orl     a,#5
                dec     DPL
                movx    @dptr,a                 ; Signal mainline
                dec     DPL
                mov     a,#1
s3_setstate:    movx    @dptr,a
s3_done:        inc     dptr
                inc     dptr
                ljmp    lloop_end

                ; On hook again after something less than an interdigit
                ; pause.  Restart the timer and go to state 4.

s3_onhook:      clr     a
                dec     DPL
                movx    @dptr,a                 ; Clear timer
                inc     dptr
                mov     a,#4
                sjmp    s3_setstate

                ; -----------------------------------------------
                ; State 4:  Onhook again while counting digit
                ;           pulses.

notstate3:      jb      hookflag,s4_offhook
                mov     a,r2
                clr     c
                subb    a,#maxflash
                jc      s3_done
                clr     a
                movx    @dptr,a
                inc     dptr
                movx    @dptr,a                 ; Signal mainline
                inc     dptr
                ljmp    lloop_end

                ; Offhook again.  Clear the timer, return to state 3,
                ; increment the digit pulse counter.

s4_offhook:     dec     DPL
                clr     a
                movx    @dptr,a
                inc     dptr
                mov     a,#3
                movx    @dptr,a
                inc     dptr
                inc     dptr

                ; Advance the pulse count but not past 11 pulses.

                movx    a,@dptr
                inc     a
                cjne    a,#12,s4_storecount
                dec     a
s4_storecount:  movx    @dptr,a

                ; fall through

lloop_end:      inc     dptr                    ; Next timer
                djnz    r0,cont_scan
                sjmp    scan_exit
cont_scan:      ljmp    lsloop

; ============= RING TRIP ================================================

                ; Handle ring trip.  Any line that is offhook must have
                ; ringing cancelled immediately.

scan_exit:      mov     dptr,#ringshadowreg
                movx    a,@dptr                 ; Ring relay states
                anl     a,r3                    ; Offhook states

                ; Any bits now asserted are lines that are offhook and
                ; ringing.

                jz      rt_exit                 ; No ring trips
                cpl     a
                mov     r4,a                    ; R4 has 0s for tripped lines
                movx    a,@dptr
                anl     a,r4
                movx    @dptr,a
                mov     dptr,#ringreg
                movx    @dptr,a

                ; Ring is now off on the tripped lines.  Set the
                ; ring start and next event times of all offhook
                ; lines to 240 to prevent future rings.

rt_exit:        mov     dptr,#ringtable
                mov     a,r3
                mov     r1,#8

rtloop:         rrc     a
                jnc     rt_ok
                push    ACC
                mov     a,#240
                movx    @dptr,a
                inc     dptr
                inc     dptr
                inc     dptr
                movx    @dptr,a
                pop     ACC
                sjmp    inc_last
rt_ok:          inc     dptr
                inc     dptr
                inc     dptr
inc_last:       inc     dptr
                djnz    r1,rtloop

; ============= DTMF RECEIVER SCAN =======================================

                ; Poll the DTMF receivers.  Do this at interrupt level for
                ; mainline convenience.  Also do it 200 times/sec because
                ; there are plenty of processor cycles.

                mov     dptr,#dtmfregs
                movx    a,@dptr
                anl     a,#0x70                 ; Get the three "valid" bits
                mov     r0,a
                xrl     a,dtmf_flags            ; Compare to previous state
                jz      dtmf_done

                ; The DTMF valid bits have changed.  Update the saved
                ; version, then for all bits which have changed and are
                ; now "off", collect the digit.  Do it that way so that
                ; nothing happens until the touch tone button is released,
                ; just like in the real phone system.  The DTMF decoders
                ; keep the previous valid code latched.

                mov     dtmf_flags,r0           ; Update previous states
                xch     a,r0
                cpl     a
                anl     a,r0
                swap    a                       ; New digit flags in A<2:0>

                mov     r0,a
                mov     r1,#0
                push    P2
                mov     P2,#dtmfregpage
                mov     dptr,#dtmf_digits

dtmfloop:       mov     a,r0
                jz      dtmf_done2
                clr     c
                rrc     a
                mov     r0,a
                jnc     dtmf_nodigit
                movx    a,@r1                   ; Get digit
                movx    @dptr,a                 ; Store in table
dtmf_nodigit:   inc     P2                      ; Go to next DTMF register
                inc     P2
                inc     P2
                inc     P2
                inc     DPTR                    ; Go to next table entry
                sjmp    dtmfloop
dtmf_done2:     pop     P2

; ============= DIAL PULSE GENERATION ====================================

                ; Handle outpulsing of digits on a pulse-dial external line.

dtmf_done:      jnb     dialqflag,pgs0exit
                mov     dptr,#pulsegenstate
                movx    a,@dptr                 ; Get state
                jnz     pgnotstate0

                ; State 0:  Ready for action.  Check if it is OK to dial yet.

                mov     dptr,#extringtimer
                movx    a,@dptr
                jnz     pgs0exit

                ; Check the digit queue.

                mov     dptr,#dialqueue
                movx    a,@dptr
                mov     r0,a
                inc     dptr
                movx    a,@dptr
                cjne    a,8,pghavework

                ; The queue is empty.  Reset the queue flag and set the
                ; "done" flag so the speech path can be unmuted.

                clr     dialqflag
                setb    dialdoneflag

pgs0exit:       ljmp    outdig_done

                ; Save the queue pointer, bump it up, and read the digit
                ; from the queue.

pghavework:     mov     r0,a
                inc     a
                cjne    a,#dialqsize,pgnowrap
                clr     a
pgnowrap:       movx    @dptr,a
                inc     dptr
                mov     a,DPL
                add     a,r0
                mov     DPL,a
                movx    a,@dptr

                ; If 15, it's a flash.

                cjne    a,#15,pgnotflash
                clr     P1.1
                mov     dptr,#pulsegenstate
                mov     a,#11
                movx    @dptr,a
                mov     pulsegentimer,#dgflashtime
                ljmp    outdig_done

                ; If bit 4 is set, it's a DTMF digit to send.

pgnotflash:     jnb     ACC.4,pgdopulse

                anl     a,#0x0f
                mov     dptr,#dtmfgentbl-1
                movc    a,@a+dptr
                mov     dptr,#0x4A00
                movx    @dptr,a
                mov     dptr,#pulsegenstate
                mov     a,#13
                movx    @dptr,a
                mov     pulsegentimer,#dtmfdigittime
                ljmp    outdig_done

                ; Translation table of DTMF digits (1-12 valid) to values
                ; to write to the DTMF generator register.

dtmfgentbl:     .byte   0x4,0x8,0xc,0x5,0x9,0xd,0x6,0xa,0xe,0xb,0x7,0xf

                ; Else it's a digit to pulse out

pgdopulse:      clr     P1.1
                mov     dptr,#pulsegenstate
                movx    @dptr,a
                mov     pulsegentimer,#dgmaketime+dgbreaktime
                ljmp    outdig_done

pgnotstate0:    cjne    a,#11,pgnotstate11

                ; State 11.  Timing out a flash.  Decrement the timer
                ;            until it's zero

                djnz    pulsegentimer,pgs11done
                setb    P1.1
interdigpause:  mov     pulsegentimer,#dgdigitpause
                mov     dptr,#pulsegenstate
                mov     a,#12
                movx    @dptr,a
pgs11done:      sjmp    outdig_done

pgnotstate11:   cjne    a,#12,pgnotstate12

                ; State 12.  Timing out an interdigit pause.

                djnz    pulsegentimer,pgs12done
                clr     a
                mov     dptr,#pulsegenstate
                movx    @dptr,a
pgs12done:      sjmp    outdig_done

pgnotstate12:   cjne    a,#13,pgnotstate13

                ; State 13.  Generating a DTMF tone.

                djnz    pulsegentimer,outdig_done
                mov     dptr,#0x4A00
                clr     a
                movx    @dptr,a
                mov     dptr,#pulsegenstate
                mov     a,#12
                movx    @dptr,a
                mov     pulsegentimer,#dtmfdigitpause
                sjmp    outdig_done

                ; Any other state means that a digit is being pulsed out.

pgnotstate13:   djnz    pulsegentimer,pgnottimeout

                ; Timer has reached 0, this means both the pulse and
                ; the interdigit time have been generated.  Decrement the
                ; state and start another pulse.

                dec     a
                jnz     pgdopulse
                sjmp    interdigpause

                ; When the timer passes "dgbreaktime", end the pulse.

pgnottimeout:   mov     a,#dgbreaktime
                cjne    a,pulsegentimer,outdig_done
                setb    P1.1

                ; fall through

                ; Scale the 200/sec interrupt rate down to 40/sec for the
                ; ring timing.

outdig_done:    djnz    ringscaler,int_exit
                mov     ringscaler,#5
                sjmp    int40

int_exit:       pop     DPH
                pop     DPL
                pop     ACC
                pop     PSW
                ret

                ; The following is executed 40 times a second.

; ============= RING CADENCE =============================================

int40:          mov     dptr,#ringshadowreg
                movx    a,@dptr
                mov     r0,a                    ; Relay settings
                mov     r1,#1                   ; Relay set mask
                mov     dptr,#ringtable

                ; R0:    Relay settings
                ; R1:    Bit corresponding to current line
                ; DPTR:  Points to current table entry

r_loop:         movx    a,@dptr                 ; Get event time
                cjne    a,ringphase,r_nextl0    ; Nothing to do right now

                ; Event for this line.  Get next bit.

                inc     dptr
                movx    a,@dptr
                mov     r2,a
                clr     c
                rrc     a

                ; Set/clear relay as appropriate

                mov     a,r1
                jnc     ringoff
                orl     8,a                     ; Relay on
                sjmp    reldone
ringoff:        cpl     a
                anl     8,a

                ; If more bits remain in the pattern, then schedule next
                ; event 12 ticks from now, and write the shifted pattern
                ; back.

reldone:        mov     a,r2
                jz      nomorebits
                clr     c
                rrc     a
                movx    @dptr,a

                dec     DPL
                mov     a,ringphase
                clr     c
                subb    a,#228
                jnc     writeback
                add     a,#240
writeback:      movx    @dptr,a
                sjmp    r_nextl0

                ; The shifted pattern ran out.  Restore the start time
                ; and full pattern.

nomorebits:     inc     dptr
                inc     dptr
                movx    a,@dptr
                push    ACC
                dec     DPL
                movx    a,@dptr
                dec     DPL
                movx    @dptr,a
                pop     ACC
                dec     DPL
                movx    @dptr,a

                ; fall through

r_nextl0:       inc     dptr
                inc     dptr
                inc     dptr
                inc     dptr
                mov     a,r1
                clr     c
                rlc     a
                mov     r1,a
                jnc     r_loop

; ============= RING HOUSEKEEPING ========================================

                ; R0 now has the enable bits for all lines that are being
                ; rung from other internal lines.  Now handle ringing from
                ; the external line.

                mov     dptr,#extlflags
                movx    a,@dptr
                cpl     a
                anl     8,a                     ; R0 in reg bank 1
                jb      P1.0,exroff
                cpl     a
                orl     8,a                     ; R0 in reg bank 1

                ; Done.  Update the ring relays, bump the ring phase up
                ; by one and exit.

exroff:         mov     a,r0
                mov     dptr,#ringreg
                movx    @dptr,a
                mov     dptr,#ringshadowreg
                movx    @dptr,a

                ; If any ring relay is on, turn on the ring voltage
                ; generator.  Otherwise turn it off.

                jz      r_toneoff
                mov     a,#0x40
r_toneoff:      mov     r0,a
                mov     dptr,#toneshadowreg
                movx    a,@dptr
                anl     a,#0xbf
                orl     a,r0
                movx    @dptr,a
                mov     dptr,#tonereg
                movx    @dptr,a

                inc     ringphase
                mov     a,ringphase
                cjne    a,#240,r_exit
                clr     a
                mov     ringphase,a

; ============= CALL PROGRESS TONES ======================================

                ; Handle call progress tones.  The tone table has one
                ; byte entry for each tone generator, encoded as follows:
                ;
                ;   00 - no tone
                ;   01 - continuous dial tone
                ;   F1 - dial tone with a break every 1/2 second
                ;   E1 - dial tone with break every full second
                ;   x1 - x-1 dial tone "stutters" followed by continuous
                ;        (5 stutters/sec)
                ;   x2 - ringback, following the ring relay of line x
                ;   03 - regular busy (1/sec)
                ;   13 - fast busy    (2/sec)
                ;   23 - hold (two stutters of "busy" every 6 seconds)
                ;
                ; Pulsating tones do not turn on in mid-pulse but do turn
                ; off in mid-pulse.

r_exit:         mov     dptr,#toneshadowreg
                movx    a,@dptr
                mov     r0,a                    ; Tone reg image
                mov     dptr,#tonetbl
                mov     r1,#3                   ; Tone generator counter

                ; Scan the table

tonelooptop:    movx    a,@dptr                 ; Get tone mode
                jz      tone_off                ; Mode 0: Tone off
                anl     a,#0x0f
                cjne    a,#1,notdial

                ; Mode x1: Dial tone, stuttering or otherwise.

                movx    a,@dptr
                swap    a
                anl     a,#0x0f                 ; Number of stutters.
                cjne    a,#0x0f,notbreak1

                ; Continuous with break every 1/2 second.  That is, off from
                ; 0-3 and from 20-23.

                mov     a,tonephase
                anl     a,#0xfc
                jz      tone_off
                cjne    a,#20,tone_on
                sjmp    tone_off

notbreak1:      cjne    a,#0x0e,notbreak
                mov     a,tonephase
                anl     a,#0xfc
                jz      tone_off
                sjmp    tone_on

notbreak:       jz      tone_on                 ; On immediately if none.

                ; Stutter "off" periods are 4-7, 12-15, etc (i.e. bit 3
                ; is set).

                jb      tonephase.2,tone_off

                ; Stutter "on" times are 0, 8, 16, 24, 32 (i.e. lower 3 bits
                ; clear).  Already checked bit 2.

                jb      tonephase.1,toneloopbot
                jb      tonephase.0,toneloopbot

                ; It's a stutter-on time.  Decrement the stutter counter
                ; and turn the tone on.  If the counter goes to 0, the tone
                ; will stay on because the mode is now "continuous dial
                ; tone".

                dec     a
                swap    a
                orl     a,#1
                movx    @dptr,a
                sjmp    tone_on

notdial:        cjne    a,#2,notringback

                ; Ringback tone.  Fetch the number of the line to monitor.

                movx    a,@dptr
                swap    a
                anl     a,#0x0f
                mov     r2,a
                inc     r2
                clr     a
                setb    c
rbtloop:        rlc     a
                djnz    r2,rbtloop

                ; Check the appropriate bit in the ring relay shadow
                ; register.

                mov     r2,a
                push    DPL
                push    DPH
                mov     dptr,#ringshadowreg
                movx    a,@dptr
                pop     DPH
                pop     DPL
                anl     a,r2
                jnz     tone_on
                sjmp    tone_off

notringback:    movx    a,@dptr
                cjne    a,#0x03,notslowbusy

                ; Slow busy:  On at 0, off at 20-39.

                mov     a,tonephase
                jz      tone_on                 ; On at 0
                clr     c
                subb    a,#20
                jnc     tone_off                ; Off at >= 20
                sjmp    toneloopbot

                ; Fast busy:  0.3 sec (12 ticks) on, 0.2 sec (8 ticks) off.

notslowbusy:    cjne    a,#0x13,notfastbusy
                mov     a,tonephase
                clr     c
                subb    a,#20
                jnc     nsb_ok
                mov     a,tonephase
nsb_ok:         jz      tone_on
                clr     c
                subb    a,#12
                jc      toneloopbot
                sjmp    tone_off

notfastbusy:    mov     a,ringphase
                anl     a,#0xf4
                jz      tone_on

                ; fall through

tone_off:       mov     a,#0xfc
                anl     a,r0
                mov     r0,a
                sjmp    toneloopbot

not13:          jnz     toneloopbot

tone_on:        movx    a,@dptr
                anl     a,#0x03
                xch     a,r0
                anl     a,#0xfc
                orl     a,r0
                mov     r0,a

toneloopbot:    inc     dptr                    ; Next table entry
                mov     a,r0
                rr      a
                rr      a
                mov     r0,a

                dec     r1
                mov     a,r1
                jz      toneloopend
                ljmp    tonelooptop

toneloopend:    mov     a,r0
                rr      a
                rr      a
                mov     dptr,#toneshadowreg
                movx    @dptr,a
                mov     dptr,#tonereg
                movx    @dptr,a

                ; bump the tone phase counter

                inc     tonephase
                mov     a,#40
                cjne    a,tonephase,t_nowrap0
                mov     tonephase,#0

; ============= MISC. TIMERS =============================================

                ; handle the 40 tick/sec external ring timer

t_nowrap0:      mov     dptr,#extringtimer
                movx    a,@dptr
                jz      t_nowrap
                dec     a
                movx    @dptr,a

                ; Scale the event rate down to 1 per second

t_nowrap:       djnz    tickcnt2,t_exit
                mov     tickcnt2,#40

                ; Go through the call timers, decrementing any that aren't
                ; at zero.

                mov     dptr,#call_timers
                mov     r0,#3
ctloop:         movx    a,@dptr
                jz      ct_is0
                dec     a
                movx    @dptr,a
ct_is0:         inc     dptr
                djnz    r0,ctloop

t_exit:         ljmp    int_exit
Back