; ;================================================================================================== ; ZILOG SCC DRIVER (SERIAL PORT) ;================================================================================================== ; ; SETUP PARAMETER WORD: ; +-------+---+-------------------+ +---+---+-----------+---+-------+ ; | |RTS| ENCODED BAUD RATE | |DTR|XON| PARITY |STP| 8/7/6 | ; +-------+---+---+---------------+ ----+---+-----------+---+-------+ ; F E D C B A 9 8 7 6 5 4 3 2 1 0 ; -- MSB (D REGISTER) -- -- LSB (E REGISTER) -- ; ; TODO: ; - INTERRUPT MODE 2 IS UNTESTED ; SCC_BUFSZ .EQU 32 ; RECEIVE RING BUFFER SIZE ; SCC_NONE .EQU 0 SCC_8530 .EQU 1 SCC_85C30 .EQU 2 SCC_85230 .EQU 3 ; SCC_DEFBAUD .EQU 38400 ; FAILSAFE BAUD RATE SCC_DEFCLK .EQU 4915200 ; FAILSAVE BAUD CLOCK SCC_DEFDIV .EQU (SCC_DEFCLK / (2 * 16 * SCC_DEFBAUD)) - 2 ; SCC_RTSON .EQU $EA SCC_RTSOFF .EQU $E8 ; #IF ((SCCINTS) & (INTMODE > 0)) SCC_WR1VAL .EQU $10 ; WR1 VALUE FOR INT ON RECEIVED CHARS SCC_WR9VAL .EQU $08 ; WR9 VALUE FOR MASTER INTERRUPTS ENABLED #ELSE SCC_WR1VAL .EQU $00 ; WR1 VALUE FOR NO INTS SCC_WR9VAL .EQU $00 ; WR9 VALUE FOR MASTER INTERRUPTS DISABLED #ENDIF ; #IF ((SCCINTS) & (INTMODE >= 2)) ; SCC0_IVT .EQU IVT(INT_SCC0) SCC1_IVT .EQU IVT(INT_SCC1) SCC0_VEC .EQU VEC(INT_SCC0) SCC1_VEC .EQU VEC(INT_SCC1) ; #ENDIF ; #IF (SCC0MODE == SCCMODE_STD) SCC0A_CMD .EQU SCC0BASE + $01 SCC0A_DAT .EQU SCC0BASE + $00 SCC0B_CMD .EQU SCC0BASE + $03 SCC0B_DAT .EQU SCC0BASE + $02 #ENDIF ; #IF (SCC0MODE == SCCMODE_SZ80) SCC0A_CMD .EQU SCC0BASE + $01 SCC0A_DAT .EQU SCC0BASE + $03 SCC0B_CMD .EQU SCC0BASE + $00 SCC0B_DAT .EQU SCC0BASE + $02 #ENDIF ; #IF (SCCCNT >= 2) ; #IF (SCC1MODE == SCCMODE_STD) SCC1A_CMD .EQU SCC1BASE + $01 SCC1A_DAT .EQU SCC1BASE + $00 SCC1B_CMD .EQU SCC1BASE + $03 SCC1B_DAT .EQU SCC1BASE + $02 #ENDIF ; #IF (SCC0MODE == SCCMODE_SZ80) SCC1A_CMD .EQU SCC1BASE + $01 SCC1A_DAT .EQU SCC1BASE + $03 SCC1B_CMD .EQU SCC1BASE + $00 SCC1B_DAT .EQU SCC1BASE + $02 #ENDIF ; #ENDIF ; ;-------------------------------------------------------------------------------------------------- ; HBIOS MODULE HEADER ;-------------------------------------------------------------------------------------------------- ; ORG_SCC .EQU $ ; .DW SIZ_SCC ; MODULE SIZE .DW SCC_INITPHASE ; ADR OF INIT PHASE HANDLER ; SCC_INITPHASE: ; INIT PHASE HANDLER, A=PHASE CP HB_PHASE_PREINIT ; PREINIT PHASE? JP Z,SCC_PREINIT ; DO PREINIT CP HB_PHASE_INIT ; INIT PHASE? JP Z,SCC_INIT ; DO INIT RET ; DONE ; SCC_PREINIT: ; ; SETUP THE DISPATCH TABLE ENTRIES ; NOTE: INTS WILL BE DISABLED WHEN PREINIT IS CALLED AND THEY MUST REMIAIN ; DISABLED. ; CALL SCC_PROBE ; PROBE FOR CHIPS ; LD B,SCC_CFGCNT ; LOOP CONTROL XOR A ; ZERO TO ACCUM LD (SCC_DEV),A ; CURRENT DEVICE NUMBER LD IY,SCC_CFG ; POINT TO START OF CFG TABLE SCC_PREINIT0: PUSH BC ; SAVE LOOP CONTROL CALL SCC_INITUNIT ; HAND OFF TO GENERIC INIT CODE POP BC ; RESTORE LOOP CONTROL ; LD A,(IY+1) ; GET THE SCC TYPE DETECTED OR A ; SET FLAGS JR Z,SCC_PREINIT2 ; SKIP IT IF NOTHING FOUND ; PUSH BC ; SAVE LOOP CONTROL PUSH IY ; CFG ENTRY ADDRESS POP DE ; ... TO DE LD BC,SCC_FNTBL ; BC := FUNCTION TABLE ADDRESS CALL NZ,CIO_ADDENT ; ADD ENTRY IF SCC FOUND, BC:DE POP BC ; RESTORE LOOP CONTROL ; SCC_PREINIT2: LD DE,SCC_CFGSIZ ; SIZE OF CFG ENTRY ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ SCC_PREINIT0 ; LOOP UNTIL DONE ; #IF ((SCCINTS) & (INTMODE > 0)) ; SETUP INT VECTORS AS APPROPRIATE LD A,(SCC_DEV) ; GET DEVICE COUNT OR A ; SET FLAGS JR Z,SCC_PREINIT3 ; IF ZERO, NO SCC DEVICES, ABORT ; #IF (INTMODE == 1) ; ADD IM1 INT CALL LIST ENTRY LD HL,SCC_INT ; GET INT VECTOR CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST #ENDIF ; #IF ((INTMODE == 2) | (INTMODE == 3)) ; SETUP IM2/3 VECTORS LD HL,SCC_INT0 LD (SCC0_IVT),HL ; IVT INDEX ; #IF (SCCCNT >= 2) LD HL,SCC_INT1 LD (SCC1_IVT),HL ; IVT INDEX #ENDIF ; #ENDIF ; #ENDIF ; SCC_PREINIT3: XOR A ; SIGNAL SUCCESS RET ; AND RETURN ; ; SCC INITIALIZATION ROUTINE ; SCC_INITUNIT: CALL SCC_DETECT ; DETERMINE SCC TYPE LD (IY+1),A ; SAVE IN CONFIG TABLE OR A ; SET FLAGS RET Z ; ABORT IF NOTHING THERE ; UPDATE WORKING SCC DEVICE NUM LD HL,SCC_DEV ; POINT TO CURRENT UART DEVICE NUM LD A,(HL) ; PUT IN ACCUM INC (HL) ; INCREMENT IT (FOR NEXT LOOP) LD (IY),A ; UPDATE UNIT NUM ; IT IS EASY TO SPECIFY A SERIAL CONFIG THAT CANNOT BE IMPLEMENTED ; DUE TO THE CONSTRAINTS OF THE SCC. HERE WE FORCE A GENERIC ; FAILSAFE CONFIG ONTO THE CHANNEL. IF THE SUBSEQUENT "REAL" ; CONFIG FAILS, AT LEAST THE CHIP WILL BE ABLE TO SPIT DATA OUT ; AT A RATIONAL BAUD/DATA/PARITY/STOP CONFIG. CALL SCC_INITSAFE ; ; SET DEFAULT CONFIG LD DE,-1 ; LEAVE CONFIG ALONE JP SCC_INITDEV ; IMPLEMENT IT AND RETURN ; ; ; SCC_INIT: LD B,SCC_CFGCNT ; COUNT OF POSSIBLE SCC UNITS LD IY,SCC_CFG ; POINT TO START OF CFG TABLE SCC_INIT1: PUSH BC ; SAVE LOOP CONTROL LD A,(IY+1) ; GET SCC TYPE OR A ; SET FLAGS CALL NZ,SCC_PRTCFG ; PRINT IF NOT ZERO POP BC ; RESTORE LOOP CONTROL LD DE,SCC_CFGSIZ ; SIZE OF CFG ENTRY ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ SCC_INIT1 ; LOOP TILL DONE ; XOR A ; SIGNAL SUCCESS RET ; DONE ; ; RECEIVE INTERRUPT HANDLER ; #IF ((SCCINTS) & (INTMODE > 0)) ; ; IM1 ENTRY POINT ; SCC_INT: ; CHECK/HANDLE FIRST CARD (SCC0) IF IT EXISTS LD A,(SCC0A_CFG + 1) ; GET SCC TYPE FOR FIRST CHANNEL OF FIRST SCC OR A ; SET FLAGS CALL NZ,SCC_INT0 ; CALL IF CARD EXISTS RET NZ ; DONE IF INT HANDLED ; #IF (SCCCNT >= 2) ; CHECK/HANDLE SECOND CARD (SCC1) IF IT EXISTS LD A,(SCC1A_CFG + 1) ; GET SCC TYPE FOR FIRST CHANNEL OF SECOND SCC OR A ; SET FLAGS CALL NZ,SCC_INT1 ; CALL IF CARD EXISTS #ENDIF ; RET ; DONE ; ; IM2 ENTRY POINTS ; SCC_INT0: ; INTERRUPT HANDLER FOR FIRST SCC (SCC0) LD IY,SCC0A_CFG ; POINT TO SCC0A CFG CALL SCC_INTRCV ; TRY TO RECEIVE FROM IT RET NZ ; DONE IF INT HANDLED LD IY,SCC0B_CFG ; POINT TO SCC0B CFG JR SCC_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN ; #IF (SCCCNT >= 2) ; SCC_INT1: ; INTERRUPT HANDLER FOR SECOND SCC (SCC1) LD IY,SCC1A_CFG ; POINT TO SCC1A CFG CALL SCC_INTRCV ; TRY TO RECEIVE FROM IT RET NZ ; DONE IF INT HANDLED LD IY,SCC1B_CFG ; POINT TO SCC1B CFG JR SCC_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN ; #ENDIF ; ; HANDLE INT FOR A SPECIFIC CHANNEL ; BASED ON UNIT CFG POINTED TO BY IY ; SCC_INTRCV: ; CHECK TO SEE IF SOMETHING IS ACTUALLY THERE LD C,(IY+3) ; CMD/STAT PORT TO C XOR A ; A := 0 OUT (C),A ; ADDRESS RR0 IN A,(C) ; GET RR0 AND $01 ; ISOLATE RECEIVE READY BIT RET Z ; NOTHING AVAILABLE ON CURRENT CHANNEL ; SCC_INTRCV1: ; RECEIVE CHARACTER INTO BUFFER LD C,(IY+4) ; DATA PORT TO C IN A,(C) ; READ PORT #IF (SCCBOOT != 0) CP SCCBOOT ; REBOOT REQUEST? JP Z,SYS_RESCOLD ; IF SO, DO IT, NO RETURN #ENDIF LD B,A ; SAVE BYTE READ LD L,(IY+7) ; SET HL TO LD H,(IY+8) ; ... START OF BUFFER STRUCT LD A,(HL) ; GET COUNT CP SCC_BUFSZ ; COMPARE TO BUFFER SIZE JR Z,SCC_INTRCV4 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED INC A ; INCREMENT THE COUNT LD (HL),A ; AND SAVE IT CP SCC_BUFSZ / 2 ; BUFFER GETTING FULL? JR NZ,SCC_INTRCV2 ; IF NOT, BYPASS CLEARING RTS LD C,(IY+3) ; CMD/STAT PORT TO C LD A,5 ; RTS IS IN WR5 OUT (C),A ; ADDRESS WR5 LD A,SCC_RTSOFF ; VALUE TO CLEAR RTS OUT (C),A ; DO IT SCC_INTRCV2: INC HL ; HL NOW HAS ADR OF HEAD PTR PUSH HL ; SAVE ADR OF HEAD PTR LD A,(HL) ; DEREFERENCE HL INC HL LD H,(HL) LD L,A ; HL IS NOW ACTUAL HEAD PTR LD (HL),B ; SAVE CHARACTER RECEIVED IN BUFFER AT HEAD INC HL ; BUMP HEAD POINTER POP DE ; RECOVER ADR OF HEAD PTR LD A,L ; GET LOW BYTE OF HEAD PTR SUB SCC_BUFSZ+4 ; SUBTRACT SIZE OF BUFFER AND POINTER CP E ; IF EQUAL TO START, HEAD PTR IS PAST BUF END JR NZ,SCC_INTRCV3 ; IF NOT, BYPASS LD H,D ; SET HL TO LD L,E ; ... HEAD PTR ADR INC HL ; BUMP PAST HEAD PTR INC HL INC HL INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START SCC_INTRCV3: EX DE,HL ; DE := HEAD PTR VAL, HL := ADR OF HEAD PTR LD (HL),E ; SAVE UPDATED HEAD PTR INC HL LD (HL),D ; CHECK FOR MORE PENDING... LD C,(IY+3) ; CMD/STAT PORT TO C XOR A ; A := 0 OUT (C),A ; ADDRESS RD0 IN A,(C) ; GET RD0 RRA ; READY BIT TO CF JR C,SCC_INTRCV1 ; IF SET, DO SOME MORE SCC_INTRCV4: ; CHECK FOR ANY PENDING SPECIAL CONDITIONS ; IF AN ERROR OCCURS, THE RECEIVER WILL BE LOCKED UNTIL YOU ; CLEAR IT BY READ RR1 LD C,(IY+3) ; CMD/STAT PORT TO C LD A,1 ; RR1 OUT (C),A ; SELECT IT IN A,(C) ; READ IT AND %0111000 ; ISOLATE ERROR BITS JR Z,SCC_INTRCV5 ; DONE IF NO BITS SET ;CALL PRTHEXBYTE ; *DEBUG* ; ; ERROR RESET LD A,%00110000 ; ERROR RESET OUT (C),A ; SEND IT ; SCC_INTRCV5: ; RESET INTERRUPT UNDER SERVICE LD A,%00111000 ; RESET HIGHEST IUS OUT (C),A ; DO IT ; OR $FF ; NZ SET TO INDICATE INT HANDLED RET ; AND RETURN ; #ENDIF ; ; DRIVER FUNCTION TABLE ; SCC_FNTBL: .DW SCC_IN .DW SCC_OUT .DW SCC_IST .DW SCC_OST .DW SCC_INITDEV .DW SCC_QUERY .DW SCC_DEVICE #IF (($ - SCC_FNTBL) != (CIO_FNCNT * 2)) .ECHO "*** INVALID SCC FUNCTION TABLE ***\n" #ENDIF ; ; ; #IF ((SCCINTS) & (INTMODE > 0)) ; SCC_IN: CALL SCC_IST ; SEE IF CHAR AVAILABLE JR Z,SCC_IN ; LOOP UNTIL SO HB_DI ; AVOID COLLISCCN WITH INT HANDLER LD L,(IY+7) ; SET HL TO LD H,(IY+8) ; ... START OF BUFFER STRUCT LD A,(HL) ; GET COUNT DEC A ; DECREMENT COUNT LD (HL),A ; SAVE UPDATED COUNT CP SCC_BUFSZ / 4 ; BUFFER LOW THRESHOLD JR NZ,SCC_IN1 ; IF NOT, BYPASS SETTING RTS LD C,(IY+3) ; C IS CMD/STATUS PORT ADR LD A,5 ; RTS IS IN WR5 OUT (C),A ; ADDRESS WR5 LD A,SCC_RTSON ; VALUE TO SET RTS OUT (C),A ; DO IT SCC_IN1: INC HL INC HL INC HL ; HL NOW HAS ADR OF TAIL PTR PUSH HL ; SAVE ADR OF TAIL PTR LD A,(HL) ; DEREFERENCE HL INC HL LD H,(HL) LD L,A ; HL IS NOW ACTUAL TAIL PTR LD C,(HL) ; C := CHAR TO BE RETURNED INC HL ; BUMP TAIL PTR POP DE ; RECOVER ADR OF TAIL PTR LD A,L ; GET LOW BYTE OF TAIL PTR SUB SCC_BUFSZ+2 ; SUBTRACT SIZE OF BUFFER AND POINTER CP E ; IF EQUAL TO START, TAIL PTR IS PAST BUF END JR NZ,SCC_IN2 ; IF NOT, BYPASS LD H,D ; SET HL TO LD L,E ; ... TAIL PTR ADR INC HL ; BUMP PAST TAIL PTR INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START SCC_IN2: EX DE,HL ; DE := TAIL PTR VAL, HL := ADR OF TAIL PTR LD (HL),E ; SAVE UPDATED TAIL PTR INC HL LD (HL),D LD E,C ; MOVE CHAR TO RETURN TO E HB_EI ; INTERRUPTS OK AGAIN XOR A ; SIGNAL SUCCESS RET ; AND DONE ; #ELSE ; SCC_IN: CALL SCC_IST ; CHAR WAITING? JR Z,SCC_IN ; LOOP IF NOT LD C,(IY+4) ; DATA PORT IN E,(C) ; GET CHAR XOR A ; SIGNAL SUCCESS RET ; #ENDIF ; ; ; SCC_OUT: CALL SCC_OST ; READY FOR CHAR? JR Z,SCC_OUT ; LOOP IF NOT LD C,(IY+4) ; DATA PORT OUT (C),E ; SEND CHAR FROM E XOR A ; SIGNAL SUCCESS RET ; ; ; #IF ((SCCINTS) & (INTMODE > 0)) ; SCC_IST: LD L,(IY+7) ; GET ADDRESS LD H,(IY+8) ; ... OF RECEIVE BUFFER LD A,(HL) ; BUFFER UTILIZATION COUNT OR A ; SET FLAGS JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING RET ; #ELSE ; SCC_IST: LD C,(IY+3) ; CMD PORT XOR A ; WR0 OUT (C),A ; DO IT IN A,(C) ; GET STATUS AND $01 ; ISOLATE BIT 0 (RX READY) JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING XOR A ; ZERO ACCUM INC A ; ASCCUM := 1 TO SIGNAL 1 CHAR WAITING RET ; DONE ; #ENDIF ; ; ; SCC_OST: LD C,(IY+3) ; CMD PORT XOR A ; WR0 OUT (C),A ; DO IT IN A,(C) ; GET STATUS AND $04 ; ISOLATE BIT 2 (TX EMPTY) JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING XOR A ; ZERO ACCUM INC A ; ACCUM := 1 TO SIGNAL 1 BUFFER POSITION RET ; DONE ; ; AT INITIALIZATION THE SETUP PARAMETER WORD IS TRANSLATED TO THE FORMAT ; REQUIRED BY THE SCC AND STORED IN A PORT/REGISTER INITIALIZATION TABLE, ; WHICH IS THEN LOADED INTO THE SCC. ; ; RTS, DTR AND XON SETTING IS NOT CURRENTLY SUPPORTED. ; MARK & SPACE PARITY AND 1.5 STOP BITS IS NOT SUPPORTED BY THE SCC. ; INITIALIZATION WILL NOT BE COMPLETED IF AN INVALID SETTING IS DETECTED. ; SCC_INITDEV: ; INITDEV CAN BE CALLED PRIOR TO INTERRUPTS BEING ENABLED. WE ; NEED TO LEAVE INTERRUPTS ALONE IN THIS SCENARIO LD A,(INTSENAB) ; INTS ENABLED? OR A ; TEST VALUE JR Z,SCC_INITDEV0 ; BYPASS DI/EI IF NOT ENABLED ; ; INTERRUPTS DISABLED DURING INIT HB_DI ; DISABLE INTS CALL SCC_INITDEV0 ; DO THE WORK HB_EI ; INTS BACK ON RET ; DONE ; SCC_INITDEV0: ; ; THIS ENTRY POINT BYPASSES DISABLING/ENABLING INTS WHICH IS REQUIRED BY ; PREINIT ABOVE. PREINIT IS NOT ALLOWED TO ENABLE INTS! ; #IF (SCCDEBUG) CALL NEWLINE PRTS("SCC$") LD A,(IY+2) SRL A CALL PRTDECB LD A,(IY+2) AND $01 ADD A,'A' CALL COUT CALL PC_COLON #ENDIF ; ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) LD A,D ; TEST DE FOR AND E ; ... VALUE OF -1 INC A ; ... SO Z SET IF -1 JR NZ,SCC_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG ; ; LOAD EXISTING CONFIG TO REINIT LD E,(IY+5) ; LOW BYTE LD D,(IY+6) ; HIGH BYTE ; SCC_INITDEV1: ; #IF (SCCDEBUG) PUSH DE POP BC PRTS(" CFG=$") CALL PRTHEXWORD #ENDIF ; ; SETUP DEFAULT VALUES FOR SCC REGISTERS PUSH DE LD HL,SCC_INITDEFS LD DE,SCC_INITVALS LD BC,SCC_INITLEN LDIR POP DE ; ; BUILD WR4 LD A,(SCC_WR4) AND %11110000 ; CLEAR BITS WE ARE GOING TO CHG SET 2,A ; ASSUME 1 STOP BIT BIT 2,E ; WANT 2 STOP BITS? JR Z,SCC_INITDEV_WR4A ; SKIP IF NOT SET 3,A ; SET CONFIG BIT FOR 2 STOP BITS SCC_INITDEV_WR4A: BIT 3,E ; PARITY ENABLE? JR Z,SCC_INITDEV_WR4B ; SKIP IF NOT SET 0,A ; SET CONFIG BIT FOR PARITY ENABLE SCC_INITDEV_WR4B: BIT 4,E ; PARITY EVEN? JR Z,SCC_INITDEV_WR4C ; SKIP IF NOT SET 1,A ; SET CONFIG BIT FOR EVEN PARITY SCC_INITDEV_WR4C: LD (SCC_WR4),A ; SAVE WR4 VALUE ; ; CREATE BITS PER CHAR VALUE ; WARNING: SCC USES NON-SEQUENTIAL VALUES: ; %00 = 5 BITS ; %01 = 7 BITS ; %10 = 6 BITS ; %11 = 8 BITS LD B,0 ; INIT B FOR WORKING VALUE BIT 0,E ; BIT 0 OF E SET? JR Z,SCC_INITDEV_BPCA ; SKIP IF NOT SET 1,B ; SET BIT 1 OF B SCC_INITDEV_BPCA: BIT 1,E ; BIT 1 OF E SET? JR Z,SCC_INITDEV_BPCB ; SKIP IF NOT SET 0,B ; SET BIT 0 OF B SCC_INITDEV_BPCB: ; ; BUILD WR3 LD A,(SCC_WR3) ; GET CUR WR3 VAL AND %00111111 ; CLEAR BPC BITS RRC B ; MOVE BPC BITS RRC B ; ... INTO POSITION OR B ; APPLY NEW BPC BITS LD (SCC_WR3),A ; SAVE IT IN WR3 SET 0,A ; ADD RX ENABLE LD (SCC_WR3A),A ; AND SAVE IT IN WR3A ; ; BUILD WR5 LD A,(SCC_WR5) ; GET CUR WR5 VAL AND %10011111 ; CLEAR BPC BITS RRC B ; MOVE BPC BITS INTO POSITION OR B ; APPLY NEW BPC BITS LD (SCC_WR5),A ; SAVE IT IN WR5 SET 3,A ; ADD TX ENABLE LD (SCC_WR5A),A ; AND SAVE IT IN WR3A ; ; DETERMINE BAUD DIVISOR PUSH DE ; SAVE CONFIG CALL SCC_COMPDIV ; COMPUTE DIVISOR TO BC POP DE ; RESTORE CONFIG RET NZ ; ABORT IF COMPDIV FAILS! ; ; SCC BRG MUST BE PROGRAMMED WITH THE DIVISOR - 2 DEC BC ; SUBTRACT 2 DEC BC ; ... FROM DIVISOR BIT 7,B ; DID WE UNDERFLOW? JR NZ,SCC_INITERR ; ERROR IF SO ; ; BUILD WR12/13 LD A,C ; LSB OF BRG DIVISOR LD (SCC_WR12),A ; SAVE IT LD A,B ; MSB OF BRG DIVISOR LD (SCC_WR13),A ; SAVE IT ; ; SAVE CONFIG PERMANENTLY NOW ; LD (IY+5),E ; SAVE LOW WORD LD (IY+6),D ; SAVE HI WORD ; JR SCC_INITGO ; GO TO SEND INIT ; SCC_INITERR: OR $FF ; SIGNAL ERROR RET ; ; ENTER HERE TO PERFORM A "SAFE" INITIALIZTION. I.E., INIT THE ; CHANNEL USING THE DEFAULT, GENERIC REGISTER VALUES. THIS CAN BE ; USED TO ENSURE INITIALIZATION IF THE FULL CONFIGURATION ABOVE ; FAILS. ; SCC_INITSAFE: ; SETUP DEFAULT VALUES FOR SCC REGISTERS LD HL,SCC_INITDEFS LD DE,SCC_INITVALS LD BC,SCC_INITLEN LDIR ; SCC_INITGO: ; ; SET INTERRUPT VECTOR OFFSET WR2 ; #IF ((SCCINTS) & (INTMODE >= 2)) LD A,(IY+2) ; CHIP / CHANNEL SRL A ; SHIFT AWAY CHANNEL BIT LD L,SCC0_VEC ; ASSUME CHIP 0 JR Z,SCC_INITIVT ; IF SO, DO IT LD L,SCC1_VEC ; ASSUME CHIP 1 DEC A ; CHIP 1? JR Z,SCC_INITIVT ; IF SO, DO IT SYSCHKERR(ERR_NOUNIT) ; IMPOSSIBLE SITUATION RET SCC_INITIVT: LD A,L ; VALUE TO A LD (SCC_WR2),A ; SAVE IT ; #ENDIF ; #IF (SCCDEBUG) LD HL,SCC_INITVALS LD B,SCC_INITLEN/2 SCC_INITPRT: PRTS(" WR$") LD A,(HL) CALL PRTHEXBYTE INC HL LD A,'=' CALL COUT LD A,(HL) CALL PRTHEXBYTE INC HL DJNZ SCC_INITPRT LD DE,65 CALL VDELAY ; WAIT FOR FINAL CHAR TO SEND #ENDIF ; ; RESET THE CHANNEL LD C,(IY+3) ; CONTROL PORT LD A,9 ; REGISTER WR9 OUT (C),A ; SELECT IT LD A,%10000000 ; CHANNEL A RESET CMD BIT 0,(IY+2) ; TEST CHANNEL NUM JR Z,SCC_INITGO1 ; SKIP AHEAD IF CHANNEL 0 RRCA ; ADJUST FOR CHANNEL 1 SCC_INITGO1: OUT (C),A ; SEND IT NOP ; SMALL DELAY REQUIRED ; ; PROGRAM THE SCC CHIP CHANNEL LD C,(IY+3) ; COMMAND PORT LD HL,SCC_INITVALS ; POINT TO INIT VALUES LD B,SCC_INITLEN ; COUNT OF BYTES TO WRITE OTIR ; WRITE ALL VALUES ; #IF ((SCCINTS) & (INTMODE > 0)) ; ; RESET THE RECEIVE BUFFER LD E,(IY+7) LD D,(IY+8) ; DE := _CNT XOR A ; A := 0 LD (DE),A ; _CNT = 0 INC DE ; DE := ADR OF _HD PUSH DE ; SAVE IT INC DE INC DE INC DE INC DE ; DE := ADR OF _BUF POP HL ; HL := ADR OF _HD LD (HL),E INC HL LD (HL),D ; _HD := _BUF INC HL LD (HL),E INC HL LD (HL),D ; _TL := _BUF ; #ENDIF ; XOR A ; SIGNAL SUCCESS RET ; RETURN ; ; THE FOLLOWING TABLE IS A GENERIC, STATIC SET OF CONFIG VALUES THAT CAN ; BE USED TO INITIALIZE THE CHIP. THESE ARE THE STARTING VALUES USED FOR ; CHIP CONFIGURATION. ; ; THE SCC IS A LITTLE PRICKLY ABOUT THE ORDER IN WHICH REGISTERS ARE ; WRITTEN DURING CONFIGURATION. THE FOLLOWING TABLE ESTABLISHES THE ; ORDER THE REGISTERS WILL BE PROGRAMMED AND SETS UP THE DEFAULT ; VALUES COPIED TO SCC_INITVALS. INITVALS IS ADJUSTED AS NEEDED, ; THEN INITVALS IS WRITTEN TO THE CHIP. ; ; *** UPDATE SCC_WR... EQUATES BELOW IF TABLE CHANGES!!! ; SCC_INITDEFS: .DB 4, $44 ; ASYNC MODE, X16, 1 STOP, NO PARITY ; 0100 0100 .DB 1, SCC_WR1VAL ; CONFIGURE INTERRUPTS .DB 2, $00 ; INTERRUPT VECTOR ; 0000 0000 .DB 3, $C0 ; RX 8 BITS PER CHAR ; 1100 0000 .DB 5, $E2 ; TX 8 BITS PER CHAR ; 1110 0010 .DB 11, $56 ; RTxC VIA BRG ; 0101 0110 .DB 12, SCC_DEFDIV & $FF ; BAUD RATE DIVISOR LO BYTE .DB 13, SCC_DEFDIV >> 8 ; BAUD RATE DIVISOR HI BYTE #IF (SCCPCLK) .DB 14, $02 ; BRG SOURCE PCLK ; 0000 0000 .DB 14, $03 ; ENABLE BRG ; 0000 0001 #ELSE .DB 14, $00 ; BRG SOURCE RTxC ; 0000 0000 .DB 14, $01 ; ENABLE BRG ; 0000 0001 #ENDIF .DB 3, $C1 ; ENABLE RECEIVER ; 1100 0001 .DB 5, $EA ; ENABLE TRANSMITTER ; 1110 1010 .DB 9, SCC_WR9VAL ; MASTER INTERRUPT CONTROL ; SCC_INITLEN .EQU $ - SCC_INITDEFS ; SCC_INITVALS .FILL SCC_INITLEN,0 ; ; BELOW ADDRESSES USED TO ADDRESS CONFIGURATION VALUE REGISTERS ; *** MUST SYNC WITH SCC_INITDEFS !!! ; SCC_WR4 .EQU SCC_INITVALS + 1 SCC_WR1 .EQU SCC_WR4 + 2 SCC_WR2 .EQU SCC_WR1 + 2 SCC_WR3 .EQU SCC_WR2 + 2 SCC_WR5 .EQU SCC_WR3 + 2 SCC_WR11 .EQU SCC_WR5 + 2 SCC_WR12 .EQU SCC_WR11 + 2 SCC_WR13 .EQU SCC_WR12 + 2 SCC_WR14 .EQU SCC_WR13 + 2 SCC_WR14A .EQU SCC_WR14 + 2 SCC_WR3A .EQU SCC_WR14A + 2 SCC_WR5A .EQU SCC_WR3A + 2 SCC_WR9 .EQU SCC_WR5A + 2 ; ; ; SCC_QUERY: LD E,(IY+5) ; FIRST CONFIG BYTE TO E LD D,(IY+6) ; SECOND CONFIG BYTE TO D XOR A ; SIGNAL SUCCESS RET ; DONE ; ; ; SCC_DEVICE: LD D,CIODEV_SCC ; D := DEVICE TYPE LD E,(IY) ; E := PHYSICAL UNIT LD C,$00 ; C := DEVICE TYPE, 0x00 IS RS-232 LD H,(IY+14) ; H := MODE LD L,(IY+3) ; L := BASE I/O ADDRESS XOR A ; SIGNAL SUCCESS RET ; ; SCC CHIP PROBE ; CHECK FOR PRESENCE OF SCC CHIPS AND POPULATE THE ; SCC_MAP BITMAP (ONE BIT PER CHIP). THIS DETECTS ; CHIPS, NOT CHANNELS. EACH CHIP HAS 2 CHANNELS. ; MAX OF TWO CHIPS CURRENTLY. INT VEC VALUE IS TRASHED! ; SCC_PROBE: ; CLEAR THE PRESENCE BITMAP LD HL,SCC_MAP ; HL POINTS TO BITMAP XOR A ; ZERO LD (SCC_MAP),A ; CLEAR CHIP PRESENT BITMAP LD C,SCC0B_CMD ; FIRST CHIP CMD/STAT PORT CALL SCC_PROBECHIP ; PROBE IT JR NZ,SCC_PROBE1 ; IF NOT ZERO, NOT FOUND SET 0,(HL) ; SET BIT FOR FIRST CARD SCC_PROBE1: ; #IF (SCCCNT >= 2) LD C,SCC1B_CMD ; SECOND CHIP CMD/STAT PORT CALL SCC_PROBECHIP ; PROBE IT JR NZ,SCC_PROBE2 ; IF NOT ZERO, NOT FOUND SET 1,(HL) ; SET BIT FOR SECOND CARD SCC_PROBE2: #ENDIF ; RET ; SCC_PROBECHIP: LD B,12 ; R12 (LOW BYTE OF BRG) LD A,$A5 ; TEST VAL CALL SCC_WR ; WRITE IT CALL SCC_RD ; READ IT CP $A5 ; CORRECT VALUE RETURNED? RET NZ ; ABORT IF NOT LD A,$5A ; NEXT TEST VAL CALL SCC_WR ; WRITE IT CALL SCC_RD ; READ IT CP $5A ; CORRECT VALUE RETURNED? RET ; RET W ZF SET AS NEEDED ; ; READ/WRITE CHIP REGISTER. ENTER CHIP CMD/STAT PORT ADR IN C ; AND CHIP REGISTER NUMBER IN B. VALUE TO WRITE IN A OR VALUE ; RETURNED IN A. ; SCC_WR: OUT (C),B ; SELECT CHIP REGISTER OUT (C),A ; WRITE VALUE RET ; SCC_RD: OUT (C),B ; SELECT CHIP REGISTER IN A,(C) ; GET VALUE RET ; ; SCC DETECTION ROUTINE ; THERE IS ONLY ONE VARIATION OF SCC CHIP, SO HERE WE JUST CHECK THE ; CHIP PRESENCE BITMAP TO SET THE CHIP TYPE OF EITHER NONE OR SCC. ; SCC_DETECT: LD B,(IY+2) ; GET CHIP/CHANNEL SRL B ; SHIFT AWAY THE CHANNEL BIT INC B ; NUMBER OF TIMES TO ROTATE BITS LD A,(SCC_MAP) ; BIT MAP IN A SCC_DETECT1: ; ROTATE DESIRED CHIP BIT INTO CF RRA ; ROTATE NEXT BIT INTO CF DJNZ SCC_DETECT1 ; DO THIS UNTIL WE HAVE DESIRED BIT ; RETURN CHIP TYPE LD A,SCC_NONE ; ASSUME NOTHING HERE RET NC ; IF CF NOT SET, RETURN ; CHECK FOR CMOS OR BETTER LD C,(IY+3) ; CMD/STATUS PORT LD B,9 ; R9 LD A,%11000000 ; RESET CHIP CALL SCC_WR ; DO IT LD B,15 ; R15 LD A,1 ; PROBE VALUE CALL SCC_WR ; SEND IT CALL SCC_RD ; READ IT BACK CP $01 ; CMOS OR BETTER? JR Z,SCC_DETECT2 ; IF SO, JUMP AHEAD LD A,SCC_8530 ; ELSE CHIP TYPE IS 8530 (NMOS) RET ; DONE SCC_DETECT2: ; CHECK FOR ESCC LD B,8 ; XMIT BUF XOR A ; NULL BYTE CALL SCC_WR ; WRITE IT LD B,0 ; R0 CALL SCC_RD ; READ IT AND %00000100 ; ISOLATE TX BUF EMPTY JR NZ,SCC_DETECT3 ; BUF STILL EMPTY, MUST BE 230 LD A,SCC_85C30 ; CHIP TYPE IS 85C30 (CMOS) RET ; DONE SCC_DETECT3: LD A,SCC_85230 ; CHIP TYPE IS 85230 RET ; DONE ; ; COMPUTE DIVISOR TO BC ; SCC_COMPDIV: ; WE WANT TO DETERMINE A DIVISOR FOR THE BAUD CLOCK ; THAT RESULTS IN THE DESIRED BAUD RATE. ; BAUD RATE = BAUD CLK / DIVISOR, OR TO SOLVE FOR DIVISOR ; DIVISOR = BAUD CLK / BAUDRATE. ; THE BAUD CLOCK IS THE SCC OSC PRESCALED BY 32. WE CAN ; TAKE ADVANTAGE OF ENCODED BAUD RATES ALWAYS BEING A FACTOR OF 75. ; SO, WE CAN USE (BAUD CLK / 32 / 75) / (BAUDRATE / 75) ; ; FIRST WE DECODE THE BAUDRATE, BUT WE USE A CONSTANT OF 1 INSTEAD ; OF THE NORMAL 75. THIS PRODUCES (BAUDRATE / 75). LD A,D ; GET CONFIG MSB AND $1F ; ISOLATE ENCODED BAUD RATE LD L,A ; PUT IN L LD H,0 ; H IS ALWAYS ZERO LD DE,1 ; USE 1 FOR ENCODING CONSTANT CALL DECODE ; DE:HL := BAUD RATE, ERRORS IGNORED EX DE,HL ; DE := (BAUDRATE / 75), DISCARD HL PUSH DE ; SAVE FOR NOW ; ; GET THE BAUD CLOCK FROM CONFIG LD A,9 CALL LDHLIYA ; HL = ADR OF BAUD CLOCK DWORD CALL LD32 ; GET THE BAUD CLOCK SPEED ; ; DIVIDE BY 32, THEN 75 TO PRODUCE (BAUD CLK / 32 / 75) LD C,32 ; DIVIDE BY 32 CALL DIV32X8 ; DO IT LD C,75 ; DIVIDE BY 75 CALL DIV32X8 ; DO IT ; ; RECOVER THE BAUDRATE AND RETURN VIA FINAL DIVISION POP DE ; RECOVER DE (DECODED BAUD RATE) JP DIV16 ; BC := HL/DE == DIVISOR AND RETURN ; ; ; SCC_PRTCFG: ; ANNOUNCE PORT CALL NEWLINE ; FORMATTING PRTS("SCC$") ; FORMATTING LD A,(IY) ; DEVICE NUM CALL PRTDECB ; PRINT DEVICE NUM PRTS(": IO=0x$") ; FORMATTING LD A,(IY+3) ; GET BASE PORT CALL PRTHEXBYTE ; PRINT BASE PORT ; PRINT THE SCC TYPE CALL PC_SPACE ; FORMATTING LD A,(IY+1) ; GET SCC TYPE BYTE RLCA ; MAKE IT A WORD OFFSET LD HL,SCC_TYPE_MAP ; POINT HL TO TYPE MAP TABLE CALL ADDHLA ; HL := ENTRY LD E,(HL) ; DEREFERENCE INC HL ; ... LD D,(HL) ; ... TO GET STRING POINTER CALL WRITESTR ; PRINT IT ; ; ALL DONE IF NO SCC WAS DETECTED LD A,(IY+1) ; GET SCC TYPE BYTE OR A ; SET FLAGS RET Z ; IF ZERO, NOT PRESENT ; PRTS(" MODE=$") ; FORMATTING LD E,(IY+5) ; LOAD CONFIG LD D,(IY+6) ; ... WORD TO DE CALL PS_PRTSC0 ; PRINT CONFIG ; XOR A RET ; ; ; SCC_TYPE_MAP: .DW SCC_STR_NONE .DW SCC_STR_8530 .DW SCC_STR_85C30 .DW SCC_STR_85230 ; SCC_STR_NONE .DB "$" SCC_STR_8530 .DB "8530$" SCC_STR_85C30 .DB "85C30$" SCC_STR_85230 .DB "85230$" ; ; WORKING VARIABLES ; SCC_DEV .DB 0 ; DEVICE NUM USED DURING INIT SCC_MAP .DB 0 ; CHIP PRESENCE BITMAP ; #IF ((SCCINTS) & (INTMODE > 0)) ; ; SCC0 CHANNEL A RECEIVE BUFFER SCC0A_RCVBUF: SCC0A_CNT .DB 0 ; CHARACTERS IN RING BUFFER SCC0A_HD .DW SCC0A_BUF ; BUFFER HEAD POINTER SCC0A_TL .DW SCC0A_BUF ; BUFFER TAIL POINTER SCC0A_BUF .FILL SCC_BUFSZ,0 ; RECEIVE RING BUFFER ; ; SCC0 CHANNEL B RECEIVE BUFFER SCC0B_RCVBUF: SCC0B_CNT .DB 0 ; CHARACTERS IN RING BUFFER SCC0B_HD .DW SCC0B_BUF ; BUFFER HEAD POINTER SCC0B_TL .DW SCC0B_BUF ; BUFFER TAIL POINTER SCC0B_BUF .FILL SCC_BUFSZ,0 ; RECEIVE RING BUFFER ; #IF (SCCCNT >= 2) ; ; SCC1 CHANNEL A RECEIVE BUFFER SCC1A_RCVBUF: SCC1A_CNT .DB 0 ; CHARACTERS IN RING BUFFER SCC1A_HD .DW SCC1A_BUF ; BUFFER HEAD POINTER SCC1A_TL .DW SCC1A_BUF ; BUFFER TAIL POINTER SCC1A_BUF .FILL SCC_BUFSZ,0 ; RECEIVE RING BUFFER ; ; SCC1 CHANNEL B RECEIVE BUFFER SCC1B_RCVBUF: SCC1B_CNT .DB 0 ; CHARACTERS IN RING BUFFER SCC1B_HD .DW SCC1B_BUF ; BUFFER HEAD POINTER SCC1B_TL .DW SCC1B_BUF ; BUFFER TAIL POINTER SCC1B_BUF .FILL SCC_BUFSZ,0 ; RECEIVE RING BUFFER ; #ENDIF ; #ELSE ; SCC0A_RCVBUF .EQU 0 SCC0B_RCVBUF .EQU 0 ; #IF (SCCCNT >= 2) SCC1A_RCVBUF .EQU 0 SCC1B_RCVBUF .EQU 0 #ENDIF ; #ENDIF ; ; SCC PORT TABLE ; SCC_CFG: ; SCC0 CHANNEL A SCC0A_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SCC TYPE (SET DURING INIT) .DB $00 ; CHIP 0 / CHANNEL A (LOW BIT IS CHANNEL) .DB SCC0A_CMD ; CMD/STATUS PORT .DB SCC0A_DAT ; DATA PORT .DW SCC0ACFG ; LINE CONFIGURATION .DW SCC0A_RCVBUF ; POINTER TO RCV BUFFER STRUCT .DW SCC0ACLK & $FFFF ; CLOCK FREQ AS .DW SCC0ACLK >> 16 ; ... DWORD VALUE .DB SCC0ACTCC ; CTC CHANNEL .DB SCC0MODE ; MODE ; DEVECHO "SCC MODE=" #IF (SCC0MODE == SCCMODE_STD) DEVECHO "STD" #ENDIF #IF (SCC0MODE == SCCMODE_SZ80) DEVECHO "SZ80" #ENDIF DEVECHO ", IO=" DEVECHO SCC0BASE DEVECHO ", CHANNEL A" #IF ((SCCINTS) & (INTMODE > 0)) DEVECHO ", INTERRUPTS ENABLED" #ENDIF DEVECHO "\n" ; SCC_CFGSIZ .EQU $ - SCC_CFG ; SIZE OF ONE CFG TABLE ENTRY ; ; SCC0 CHANNEL B SCC0B_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SCC TYPE (SET DURING INIT) .DB $01 ; CHIP 0 / CHANNEL B (LOW BIT IS CHANNEL) .DB SCC0B_CMD ; CMD/STATUS PORT .DB SCC0B_DAT ; DATA PORT .DW SCC0BCFG ; LINE CONFIGURATION .DW SCC0B_RCVBUF ; POINTER TO RCV BUFFER STRUCT .DW SCC0BCLK & $FFFF ; CLOCK FREQ AS .DW SCC0BCLK >> 16 ; ... DWORD VALUE .DB SCC0BCTCC ; CTC CHANNEL .DB SCC0MODE ; MODE ; DEVECHO "SCC MODE=" #IF (SCC0MODE == SCCMODE_STD) DEVECHO "STD" #ENDIF #IF (SCC0MODE == SCCMODE_SZ80) DEVECHO "SZ80" #ENDIF DEVECHO ", IO=" DEVECHO SCC0BASE DEVECHO ", CHANNEL B" #IF ((SCCINTS) & (INTMODE > 0)) DEVECHO ", INTERRUPTS ENABLED" #ENDIF DEVECHO "\n" ; #IF (SCCCNT >= 2) ; ; SCC1 CHANNEL A SCC1A_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SCC TYPE (SET DURING INIT) .DB $02 ; CHIP 1 / CHANNEL A (LOW BIT IS CHANNEL) .DB SCC1A_CMD ; CMD/STATUS PORT .DB SCC1A_DAT ; DATA PORT .DW SCC1ACFG ; LINE CONFIGURATION .DW SCC1A_RCVBUF ; POINTER TO RCV BUFFER STRUCT .DW SCC1ACLK & $FFFF ; CLOCK FREQ AS .DW SCC1ACLK >> 16 ; ... DWORD VALUE .DB SCC1ACTCC ; CTC CHANNEL .DB SCC1MODE ; MODE ; DEVECHO "SCC MODE=" #IF (SCC1MODE == SCCMODE_STD) DEVECHO "STD" #ENDIF #IF (SCC1MODE == SCCMODE_SZ80) DEVECHO "SZ80" #ENDIF DEVECHO ", IO=" DEVECHO SCC1BASE DEVECHO ", CHANNEL A" #IF ((SCCINTS) & (INTMODE > 0)) DEVECHO ", INTERRUPTS ENABLED" #ENDIF DEVECHO "\n" ; ; SCC1 CHANNEL B SCC1B_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SCC TYPE (SET DURING INIT) .DB $03 ; CHIP 1 / CHANNEL B (LOW BIT IS CHANNEL) .DB SCC1B_CMD ; CMD/STATUS PORT .DB SCC1B_DAT ; DATA PORT .DW SCC1BCFG ; LINE CONFIGURATION .DW SCC1B_RCVBUF ; POINTER TO RCV BUFFER STRUCT .DW SCC1BCLK & $FFFF ; CLOCK FREQ AS .DW SCC1BCLK >> 16 ; ... DWORD VALUE .DB SCC1BCTCC ; CTC CHANNEL .DB SCC1MODE ; MODE ; DEVECHO "SCC MODE=" #IF (SCC1MODE == SCCMODE_STD) DEVECHO "STD" #ENDIF #IF (SCC1MODE == SCCMODE_SZ80) DEVECHO "SZ80" #ENDIF DEVECHO ", IO=" DEVECHO SCC1BASE DEVECHO ", CHANNEL B" #IF ((SCCINTS) & (INTMODE > 0)) DEVECHO ", INTERRUPTS ENABLED" #ENDIF DEVECHO "\n" ; #ENDIF ; SCC_CFGCNT .EQU ($ - SCC_CFG) / SCC_CFGSIZ ; ;-------------------------------------------------------------------------------------------------- ; HBIOS MODULE TRAILER ;-------------------------------------------------------------------------------------------------- ; END_SCC .EQU $ SIZ_SCC .EQU END_SCC - ORG_SCC ; MEMECHO "SCC occupies " MEMECHO SIZ_SCC MEMECHO " bytes.\n"