Source file: /~heha/basteln/PC/USB2LPT/usb2lpt.zip/src/firmware/USB2LPT2.A51

;FX2-Firmware für USB2LPT2, haftmann#software 08/05
;Bis jetzt wird nur ein dptr benutzt (also kein DPS), nur Registerbank 0
;Zu übersetzen mit ASEM51; EEPROM-Datei erstellen mit
;HEX2BIX -F 0xC2 -I -V 0x16C0 -P 0x06B3
;Pro OUT-Befehl werden zwei Bytes in die OUT-Pipe geschrieben.
;Für jeden IN-Befehl wird ein Byte [Adresse] in die OUT-Pipe gesetzt,
; als Folge kommt ein Byte [Daten] in die IN-Pipe. Genaueres siehe "upv"!
;Seriennummern-Position: 1 Byte @ FFFFh (alt), 4 Byte @ FFFCh (Intel, neu)
;06xxxx	EEPROM+XRAM-Routinen
;060622	Gekaufte VID+PID eingetragen
;060629	Einzelpin-Richtungsumschaltung
;060630 Open-Collector-Simulation (Daten- und Steuerpins)
;060706	Richtungsbit-Simulation korrigiert (stets 0 bei Mode 0 und 2)
;060710	EPP: allgemeine Fehlerbeseitigung, getestet
;060711	Schattenregister für Steuerport, Code-Erweiterbarkeit für Anwender
;060809	DirectIo, korrigiertes Open-Collector-Verhalten (Steuerport), EEPROM
;060811	Drei ungenutzte PortD-Leitungen auf drei Massepins geroutet
;070128	Richtungsbits geändert auf 0=Eingang, 1=Ausgang (wie EZUSB und ATmega)
;070128	Steuerport-Richtungsfestlegung über High-Nibble von GpifIdleCfg
;070209	Achtelung der Helligkeit allzu heller blauer LEDs mit Schaltbit
;101212	Drittes HID-Interface für User-Mode-Zugriff ohne Kerneltreiber
;110922	HID-Interface mit differenzierten Usages (immer noch ungetestet)
;Zu tun:

	$nopaging
	$nosymbols
	$nomod51
	$nolist
	$include(ezusbfx2.mcu)
	$include(makros.i51)
	$list
	$genonly		;das "Innere" von Makros
	$condonly		;das "Innere" von IFxx

DateYear	equ	2011	;"Versionskennung"
DateMonth	equ	9
DateDay		equ	22
Interfaces	equ	3	;1 = ohne "USB-Druckerunterstützung"
				;(so lässt es sich besser debuggen)
MyId		equ	1	;0 = Cypress-ID (Debuggen mit Cypress-Tools)
Always_Renum	equ	0	;1: Firmware-Update im Gerätemanager versagt
Gpif		equ	0	;Benutze GPIF - oder Handarbeit fürs Drucken

;###############
;## Schaltung ##
;###############
;Datenport  (Basisadresse+0) = PortB (mit korrekter Bit-Anordnung, = FIFO-Port)
;Statusport (Basisadresse+1) = PortA (Bits 5-4-3 auf Portbits 5-6-1)
;			   und RDYx  (Bits 7-6 auf Portbits 0-1)
;Rev.3:			     = PortD (Bits 7-3 auf Portbits 7-3), RDYx gleich
;Rev.4:			     zusätzlich PortD (Bits 2-0 auf Extra-Bits 2-0)
;Steuerport (Basisadresse+2) = PortA (Bit 2 auf Portbit 2)
;			   und CTLx  (Bits 0-1-3 auf Portbits 0-1-2)
;Rev.3:			     = PortA (Bits 3-0 auf Portbits 3-0), CTLx gleich
;Die Zuweisung auf CTLx und RDYx erfolgte für Implementierbarkeit von
;SPP, ECP und EPP mithilfe von GPIF (also sehr schnell!),
;die übrige Zuweisung auf PortA nach günstigster Leiterbahnführung.
;Problem: Steuerport ist nicht rücklesbar! Korrektur in Revision 3,
;	  durch Routing der CTLx-Leitungen auf PortD
;Die Symbole PE,ONL,ERR,INI werden nur für Rev.2 verwendet.						JTAG		Rev.3
 BSY	EQU	0	;RDY1	(/7)	(ist mit D6 verbunden)	IOD.7
 ACK	EQU	1	;RDY0	( 6)				IOD.6
 PE	EQU	5	;IOA.5	( 5)	(ist mit D6 verbunden)	IOD.5
 ONL	EQU	6	;IOA.6	( 4)	D/P	TDO		IOD.4
 ERR	EQU	1	;IOA.1	( 3)	Ucc	Ucc		IOD.3
 
 SEL	EQU	2	;CTL2	(/3)				IOA.3
 INI	EQU	2	;IOA.2	( 2)				IOA.2
 AF	EQU	1	;CTL1	(/1)				IOA.1
 STB	EQU	0	;CTL0	(/0)				IOA.0
;			;D4		/PROG (zieht TDO auf Low)
;			;D3		/CTRL (aktiviert 2 Ausgangstreiber)
;			;D2		PROG	TMS
;			;D1		CCLK	TCK
;			;D0		DIN	TDI
 Ledr2	equ	IOA.0	;LOW-aktiv				IOA.4
 Led2r2	equ	IOA.7	;LOW-aktiv				IOA.5
Led	equ	IOA.4
Led2	equ	IOA.5


DefIOA	equ	0FFh	;alles High, auch LEDs
 DefOEAr2 equ	10011101b
;IOA.1 und IOA.3 sind noch frei, evtl. Steuerung der Pull-UpWiderstände
;	in Rev. 3 zur Energieeinsparung bei Idle, IOA.1 für ACK-Interrupt
DefOEA equ	10110100b
;###############				
;## Endpoints ##		USB2LPT-Rev.	0,1	2-4,7	5,6
;###############		Speed		Full	High	Low
;Pipe	Funktion				EZUSB	FX2	V-USB
;0	OUT-Adressen, OUT-Daten, IN-Adressen	EP2Out*	EP2Out*	EP1Out
;1	IN-Daten				EP2In*	EP6In*	EP1In
;2(=0)	USB-Druckerunterstützung Vorwärtskanal	EP4Out*	EP4Out*	-
;3(=1)	USB-Druckerunterstützung Rückkanal	EP4In*	EP8In*	-
;4(=0)	HID Out					EP1Out	EP1Out	-
;5(=1)	HID In					EP1In	EP1In	EP3In
;* = doppelt gepuffert
;-----------
;## DATEN ##	Der Datenbereich wird beim Firmware-Start mit Nullen gelöscht
;-----------
DSEG AT 20h	;bitadressierbar
IntReq:		ds	1	;für USB-Interruptrequests, bitadressierbar
Configuration:	ds	1	;hier: Null (adressiert) oder Eins (konfig.)
AltSetting1:	ds	1	;Null, Eins oder Zwei (nur für 2. Interface)
bits:		ds	1	;diverse Bits
HighSpeed	BIT	bits.0	;Kopie von UsbIrq.HsGrant
Led_B		BIT	bits.1	;Solange gelbe LED blinkt
Led2_B		BIT	bits.2	;Solange blaue (High-Speed-)LED blinkt
Rev2		BIT	bits.3	;Zur Verzweigung bei dennoch gleicher Firmware
FeatureChanged	BIT	bits.4	;Feature-Byte in EEPROM brennen (persistent)
Led2State	BIT	bits.5	;Eigentlicher Zustand der blauen LED
Data5V		BIT	bits.6	;wie OCData && !direction
Control5V	BIT	bits.7	;wie (SPP && !TPControl) || OCControl
;===Druckerport===
DCR:		ds	1	;Device Control Register (+2)
ackIntEn	BIT	DCR.4	;1=ein, rücklesbar, aber nicht unterstützt
direction	BIT	DCR.5	;0=OUT, 1=IN, klebt auf 0 bei Mode=0 oder =2
ECR:		ds	1	;Extended Control Register (ECP 402)
fifoe		BIT	ECR.0	;1 wenn FIFO leer
fifof		BIT	ECR.1	;1 wenn FIFO voll
ECR_Bits:	ds	1	;Mit immer nur einem Bit gesetzt
EPPTimeOut:	ds	1	;Bit0=0: kein TimeOut aufgetreten
				;Bit2=0: Interrupt aufgetreten (zz. ungenutzt)
				;Alle anderen Bits müssen =1 sein!
Feature:	ds	1
OCData		BIT	Feature.0 ;Offene-Senke-Simulation für Daten (+0)
TPControl	BIT	Feature.1 ;Totempfahl auch bei SPP (bei Rev.2 immer 1)
OCControl	BIT	Feature.2 ;Offene-Senke-Simulation für Steuerport (+2)
DarkBlue	BIT	Feature.5 ;dunklere blaue LED
DirectIo	BIT	Feature.6 ;keine Invertierungen, kein Datenrichtungsbit
PullUps		BIT	Feature.7 ;Deaktivierung der externen Pullups ab Rev.3
;===I²C===
I2C_ALen:	ds	1	;entspricht etwa wIndexH beim Vendor-Request A2
 I2C_ALen0	BIT	I2C_ALen.0	; 1 = 1 Byte, oder 2 Bytes vertauscht
 I2C_ALen1	BIT	I2C_ALen.1	; 1 = 2 Bytes (sonst 0 oder 1 Byte)
 I2C_NoStop	BIT	I2C_ALen.2
 I2C_NoStart	BIT	I2C_ALen.3
 I2C_Paging	BIT	I2C_ALen.4	; 1 = Paging aktiv (nicht wIndexH)
 I2C_Verify	BIT	I2C_ALen.5	; zusammen mit Paging immer Verify

DSEG AT 30h	;nicht bitadressierbar
FIFOSIZE	equ	16
;===FIFO===... hat hier eine Tiefe von FIFOSIZE Wörtern zu je 9 Bit
Fifo:		ds	FIFOSIZE*2	;Wegen ECP brauchen wir 9 bit Breite!
fifor:		ds	1	;Fifo-Lesezeiger
fifow:		ds	1	;Fifo-Schreibzeiger
;===allgemein===
Led_T:		ds	1	;"Nachblinkzeit" der LED in ms
Led_F:		ds	1	;"Blinkfrequenz" in ms (halbe Periode)
Led_C:		ds	1	;Blink-Zähler
Led2_T:		ds	1
Led2_F:		ds	1
Led2_C:		ds	1
FrameCnt:	ds	1	;zum Mitzählen "ganzer" USB-Rahmen (HighSpeed)
PendingByte:	ds	1
UniIdxL:	ds	1	;Index in OUT4BUF zum byteweisen Lesen
UniIdxH:	ds	1
ep2ll:		ds	1	;Längenzähler für EP2-Verarbeitung
ep2lh:		ds	1
;=== Variablen für I²C/EEPROM ===
I2C_Addr:	ds	1	;I²C-Adresse (EEPROM)
I2C_PMask:	ds	1	;Für seiten-weises EEPROM-Schreiben,
	;00 = einzelbyteweise, 07 = 8-Byte-Seite, 1F = 32-Byte-Seite usw.
	;07 (8bit), 1F (16bit) oder (1<<High-Nibble von wIndexH)-1 (Request ED)
;Die PMask wird mit dem LOW-Teil der laufenden Adresse geODERt; bei
;Ergebnis Null wird der I²C-Transfer unterbrochen und das Brennen ausgelöst.

IF Gpif
 OutB	equ	GpifSglDatLX
 TRISB MACRO v
	STX	GpifIdleCS,v
 ENDM
ELSE
 OutB	equ	IOB
 TRISB MACRO v
  IFNB <v>
	mov	OEB,#v
  ELSE
  	mov	OEB,a
  ENDIF
 ENDM
ENDIF

;--------------
;## PROGRAMM ##
;--------------
CSEG AT 0
	ljmp	main	;am Reset-Vektor
SAVEORG 6,0
	dwi	%(DateYear-1980)*512+DateMonth*32+DateDay
	
SAVEORG 2Eh
Scratch:ds	1	;für EEPROM-Transfer
usr2f:	RETC	;@002F Ansprung bei Ausgabebyte > 20h, ersetzen mit AJMP
usr31:	RETC	;@0031 unbekannter Request über EP0, ersetzen mit AJMP

SAVEORG RESUME	;RESUME-ISR @33h (erforderlich, sonst geht das Wecken nicht)
	clr	EICON.4
	reti
usr36:	ret	;@0036 Zyklischer Ansprung, ersetzen mit AJMP (oder LJMP)
	

ORG 1000h			;4 Kilobyte für User
;--------------------------------------------------------------------
ResetFifos:
	STX	GpifAbort
	SyncDelay
	STX	FifoReset,80h
	SyncDelay
	STX	,2
	SyncDelay
	STX	,4
	SyncDelay
	STX	,6
	SyncDelay
	STX	,8
	SyncDelay
	STX	,0
	SyncDelay
	STX	OutPktEnd,82h	;EP2Out-Block in USB-Besitz bringen
	SyncDelay
	STX			;beide Blöcke (Doppel-Pufferung)
	SyncDelay
	STX	OutPktEnd,84h	;EP4Out-Block in USB-Besitz bringen
	SyncDelay
	STX			;beide Blöcke (Doppel-Pufferung)
	SyncDelay
	STX	EP1OutCfg,0B0h	;Interrupt-Modus (nicht Bulk)
	STX	NEXT		;EP1InCfg
	SyncDelay
	STX	EP1OutBC	;EP1Out-Block in USB-Besitz bringen
	ret

;EEPROM-Adresse (gerade; Schreibadresse) am I²C-Bus
EADDR	equ	0A0h		;für 8-bit-EEPROM, 16-bit-EEPROMS haben EADR+2

;EEPROM-Default-"Seitengröße" zum Brennen mehrerer Bytes auf einmal
EPAGE8	equ	8		;für 8-bit-EEPROM 24LC01/02
EPAGE16	equ	32		;für 16-bit EEPROM 24LC32/64
;Andere EEPROM-Typen müssen per wIndex ausgewählt werden.
;Eine "Seitengröße" von 1 führt zum Einzelbyte-Brennen (viel langsamer)

;Die Auswahl 8/16 bit erfolgt automatisch anhand I2CS.4 wie im EZUSB-Kern

;======================
;== Byte-Ein/Ausgabe ==
;======================

i2c_wr:	;Byte auf I²C schreiben (kann blockieren!)
;PE: A=Ausgabe-Byte
;PA: CY=1 bei Fehler oder NAK, ACC.4=ID1
	STX	I2DAT
	dec	r0		;auf I2CS
i2c_w:	LDX
	mov	c,ACC.2		;BERR-Bit
	jc	i2c_err
	jnb	ACC.0,i2c_w	;DONE-Bit
	orl	c,/ACC.1	;ACK-Bit=0 -> C=1
	ret
	
i2c_rd:	;Byte von I²C lesen (kann blockieren!)
;PE: R4=Anzahl noch zu lesender Bytes (für LASTRD und STOP)
;PA: A=Eingabe-Byte
	mov	r0,#LOW(I2CS)
i2c_r:	LDX	
	mov	c,ACC.2		;BERR-Bit
	jc	i2c_err
	jnb	ACC.0,i2c_r	;DONE-Bit
i2c_d:	;Dummy-Lesezugriff (Seiteneinstieg mit A=0 und R0=I2CS)
	jb	I2C_NoStop,i2c_b
	cjne	r4,#2,i2c_a
	setb	ACC.5		;LASTRD setzen
i2c_a:	cjne	r4,#1,i2c_b
	setb	ACC.6		;STOP setzen
i2c_b:	STX			;LASTRD bzw. STOP setzen
	LDX	NEXT		;I2DAT lesen
i2c_err:ret

;==============================
;== I²C(EEPROM)-Adressierung ==
;==============================

EAdr:	;I²C starten und (Adress-)Bytes ausgeben
;PE: DPTR=Adresse (DPL allein, wenn ALEN<2)
;PA: CY=1 bei Fehler
;VR: A,R0,R2
;N: Diese Routine kann bis 10 ms blockieren, bis der EEPROM bereit ist
;   (weil er sich bis zum Fertigschreiben I²C-busseitig tot stellt)
	LDX	I2CS
	jb	ACC.6,EAdr	;Stoppsequenz abwarten
	mov	r2,#0		;max. 256 Versuche, 10 ms = 120000 CPU-Takte
eal:	jb	I2C_NoStart,ea_nostart
	STX	I2CS,80h	;(4) Startsequenz ausgeben
ea_nostart:
	mov	a,I2C_Addr	;(2) I²C-Adresse: Schreiben
	call	i2c_wr		;(>=25)
	jnc	ea1		;(3) EEPROM könnte mit Brennen beschäftigt sein
	jb	ACC.2,eae	;(3) wenn BERR nicht noch einmal versuchen
	jnb	I2C_Paging,eae	;(3) raus mit Fehler (nicht neu versuchen)
	mov	r0,#144		;(2)
	djnz	r0,$		;(144*3) warten
	djnz	r2,eal		;(3) ... Adressierung wiederholen!
eae:	ret			;raus (ggf. mit Fehler)
ea1:	;Adressbyte-Ausgabe (Tabelle s.u.)
	jnb	I2C_ALen0,ea_x0
	mov	a,DPL0		;EEPROM-Adresse Low-Teil - oder Low-Teil zuerst
	call	i2c_wr
	jc	i2c_err
	jnb	I2C_ALen1,eae	; kein Fehler: fertig
	mov	a,DPH0		;EEPROM-Adresse High-Teil
	jmp	i2c_wr
ea_x0:
	jnb	I2C_ALen1,eae	; kein Fehler: fertig ohne ein Adressbyte
	mov	a,DPH0		;EEPROM-Adresse High-Teil zuerst
	call	i2c_wr
	jc	i2c_err
	mov	a,DPL0		;EEPROM-Adresse Low-Teil als zweites
	jmp	i2c_wr		;immer schreiben und Ende
;Adressbyte-Ausgabe tabellarisch
; I2C_ALen	wIndexH	!	 I²C-Ausgabe
;ALen1	ALen0	ALEN	!	1.Byte	2.Byte	
;0	0	0	!	-	-
;0	1	1	!	DPL	-		DPL = wValueL
;1	0	2	!	DPL	DPH		DPH = wValueH
;1	1	3	!	DPH	DPL

;=============================
;== I²C(EEPROM)-Ein/Ausgabe ==
;=============================

EWrite:	;(Boot-EEPROM oder beliebiges) I²C-Gerät schreiben
;PE: DPTR=(EEPROM-)Adresse (Ziel)
;    I2C_Addr = I²C-Adresse
;    I2C_ALen = I²C-Adresslänge sowie übrige Bits
;    I2C_PMask= Seiten-Maske
;    AutoPtr=Puffer-Adresse (zu schreibende Daten)
;    R4=Länge der Daten (0 = keine Daten)
;PA: CY=1 bei Fehler, dann R4=verbliebene Bytes im Puffer
;    DPTR erhöht
;VR: A,DPTR,R0,R2,R4
	call	EAdr
	jc	ewe
	inc	r4
	sjmp	ewf
ewl:	
	LDX1
	call	i2c_wr		;1 Byte schreiben (noch nicht brennen)
	jc	ewe
	inc	dptr		;EEPROM-Adresse mitzählen
	jnb	I2C_Paging,ewf
	mov	a,I2C_PMask
	anl	a,DPL0
	jz	ewp		;Seite zu Ende!
ewf:	djnz	r4,ewl		;in Seite, nächstes Byte
	jb	I2C_NoStop,ewr
ewe:	STX	I2CS,40h	;Stoppsequenz
ewr:	ret
ewp:	STX	I2CS,40h	;Stoppsequenz, damit brennen
	djnz	r4,EWrite	;weitermachen, nächster Block
	ret

EE_W:	jnb	I2C_Verify,EWrite	;sofort zum Schreiben gehen!
	push	DPL0		;Adresse zwecks Vergleichen retten
	push	DPH0
	push	AR4
	 call	EWrite
	pop	AR4
	pop	DPH0
	pop	DPL0		;Adresse zurückstellen
	MOVW	AutoPtr1,Out0Buf;auch Vergleichspuffer zurück
	setb	F0
	jnc	ERead		;Vergleichen
	ret

ERead:	;Boot-EEPROM lesen oder überprüfen
;PE: DPTR=EEPROM-Adresse (Quelle)
;    I2C_Addr = I²C-Adresse
;    I2C_ALen = I²C-Adresslänge sowie NoStart und NoStop-Bits
;    AutoPtr=Puffer-Adresse (Lese-Puffer), R1=XAutoDat
;    R4=Länge Daten/Puffer (0 = nicht erlaubt!)
;    F0=0: lesen, F0=1: prüfen/vergleichen (Verify)
;PA: CY=1 bei Fehler, dann R4=verbliebene Bytes im Puffer
;    DPTR erhöht
;VR: A,DPTR,R0,R2,R4
	jb	I2C_NoStart,erns	;Hier: Auch keine Adressbytes ausgeben!
	call	EAdr
	jc	ere
	STX	I2CS,80h	;Noch eine Startsequenz ausgeben
erns:	mov	a,I2C_Addr	;I²C-Adresse
	inc	a		;Leseadresse
	call	i2c_wr
	jc	ere
	inc	r4		;Problem: Klappt nicht mit R4=0
	call	i2c_d		;Dummy-Lesezugriff von I2DAT
	dec	r4
erl:
	call	i2c_rd		;Byte lesen
	jc	ere
	jb	F0,erv		;Vergleichen
	STX1
	sjmp	er1
erv:	mov	r0,a
	LDX1
	xrl	a,r0		;A=0 wenn gleich
	jnz	eru		;raus mit Fehler wenn ungleich
er1:	inc	dptr		;EEPROM-Adresse mitzählen
	djnz	r4,erl
	jb	I2C_NoStop,er9	;Stopp-Unterdrückung nur bei regulärem Ende!
	db	0E5h		;mov a,xx
eru:	setb	c
ere:	STX	I2CS,40h	;Stoppsequenz
er9:	ret

;===================================
;== Arbeitszellen-Initialisierung ==
;===================================

EReqInit:	;A2-Request auswerten und drei I²C-Parameter zusammenstellen
;PE: wIndex = USB-Parameter (ACHTUNG bei EZUSB Control Panel [Rindfleisch])
;VR: A,R0,I2C_Addr,I2C_ALen,I2C_PMask
	LDX	SetupDat+4	;wIndexL
	jnz	eri_nBootRom
EReqInitBoot:	;Seiteneinstieg für Startup
	LDX	I2CS		;bei wIndexL=0 Boot-ROM automatisch wählen
	mov	c,ACC.4		;ID1-Bit = 1 bei 16-bit-EEPROM
	mov	a,#EADDR
	mov	ACC.1,c		;ggf. aus A0 ein A2 machen
	mov	I2C_Addr,a
	clr	a
	addc	a,#1		;Adresslänge 1 oder 2 Bytes
	mov	I2C_ALen,a
	sjmp	eri_Auto
eri_nBootRom:
	clr	ACC.0		;Hier: Schreibadresse!
	mov	I2C_Addr,a
	LDX	NEXT		;wIndexH
	anl	a,#0Fh		;Low-Nibble
	mov	I2C_ALen,a	;übernehmen, wie es ist
	LDX			;noch einmal wIndexH
	swap	a
	anl	a,#0Fh		;High-Nibble
	jnz	eri_nAuto
eri_Auto:			;bei HINIBBLE(wIndexH)=0 Paginierung automatisch
	mov	a,I2C_Addr
	anl	a,#0F0h
	xrl	a,#0A0h		;Irgendein serieller EEPROM-Typ (Axh)?
	jnz	eri_e		;nein, keine (automatische) Paginierung
	mov	a,I2C_ALen
	cjne	a,#1,eri_n1	;Adress-Länge 1? (Und kein NoStop/NoStart?)
	mov	a,#EPAGE8-1	;Maske setzen
	sjmp	eri_s
eri_n1:	cjne	a,#2,eri_e	;Adress-Länge 2? Alles andere: ohne Auto-Paging!
	mov	a,#EPAGE16-1	;Maske setzen
	sjmp	eri_s
eri_nAuto:
	mov	r0,a		;Bitmaske erzeugen: 1->00, 2->01, 3->03
	clr	a		;4->07, 5->0F, 6->1F, 7->3F, 8->7F, 9->FF
	sjmp	eri_f
eri_l:	setb	c
	rlc	a		;nach links schieben, Einsen einfügen
eri_f:	djnz	r0,eri_l
eri_s:	mov	I2C_PMask,a
	setb	I2C_Paging	;Auto-Paging aktivieren
	setb	I2C_Verify	;Verify aktivieren (vorerst gemeinsam)
eri_e:	RETNC			;(womöglich später auch beim Lesen, wenn erf.)

;=============================
;== EEPROM- und RAM-Zugriff ==
;=============================
MemR:	LODS		;lesen (Zweiter Autopointer versagt
	STX1		;sowieso wegen Bug im FX2!!)
	djnz	r4,MemR
	ret
MemW:	LDX1
	STOS		;schreiben
	djnz	r4,MemW
	ret
	
Partial_64:
;Subtrahiert 64 (bzw. R4) von der Gesamtlänge; R7=0 wenn letzter Block
	mov	r4,#64
Partial_r4:
	mov	a,r6
	subb	a,r4
	jnc	pa1
	clr	c
	djnz	r7,pa1	;Letzte "Runde" mit R7=0
	LD	r4,a,r6
	inc	r4
	ret
pa1:	mov	r6,a
	ret
	
LongEP0:
;Diese Routine funktioniert "absichtlich" nicht bei Längen >=FF00h,
;Control-Transfers sind ohnehin auf 1000h (4KB) beschränkt.
;Behandlung aller "langen" EP0-Transfers, mit selbstmodifizierendem Code
;dptr=Zeiger Leseroutine (R1=AutoPtr-EP0Buf, DPTR=wValue, R4=Länge)
;R7:R6=Zeiger Schreibroutine (dito, für OUT-Transfers)
;Alle diese Routinen bekommen DPTR=wValue, R4=Transferlänge, R1=AutoDat,
;und dürfen R6, R7 nicht verändern. (R7=0 für letzten Teiltransfer)
;F0 ist (zunächst) gelöscht
;R4=bmRequestType
	clr	F0
	mov	a,DPH0
	mov	r5,DPL0	;retten
	mov	dptr,#PatchR+1
	STORX
	mov	a,r5
	STORX	NEXT
	mov	a,r7
	mov	dptr,#PatchW+1
	STORX			;High-Teil
	mov	a,r6
	STORX	NEXT		;Low-Teil
	LDX	SetupDat+2	;wValueL
	mov	DPL0,a
	LDX	NEXT		;SetupDat+3
	mov	DPH0,a
	LDX	SetupDat+6	;wLengthL
	add	a,#0FFh
	mov	r6,a
	LDX	NEXT		;wLengthH
	addc	a,#0		;erhöhen für Funktion von Partial_xx
	mov	r7,a
	cjne	r4,#01000000b,a3_no	;bmRequestType
	jz	LEP0E		;Keine Daten!
LEP0W:	WAIT_EP0_OUT
	mov	r4,a
	call	Partial_R4
	MOVW	AutoPtr1,Out0Buf
	mov	r1,#LOW(XAutoDat1)
PatchW:	lcall	0
	jc	LEP0E
	mov	a,r7
	jnz	LEP0W		;nächste Runde, sowie CY=0
	ret
	
a3_no:	cjne	r4,#11000000b,err2	;bmRequestType
	jz	LEP0E
LEP0R:	call	Partial_64
	WAIT_EP0_IN
	push	AR4		;retten für In0BC
	 MOVW	AutoPtr1,In0Buf
	 mov	r1,#LOW(XAutoDat1)
PatchR:	 lcall	0
	pop	ACC
	jc	LEP0E
	STX	In0BC
	mov	a,r7
	jnz	LEP0R		;nächste Runde, sowie CY=0
LEP0E:	ret
err2:	RETC

;=========================
;== Routinen für Ende 0 ==	;PE: R0=SetupDat, R4=SDAT[0], R5=SDAT[2]
;=========================	                  R6=SDAT[4], R7=SDAT[5]
IF MyID
 ClassOut	equ	00100001b	;Klassenrequest (Drucker)
 ClassIn	equ	10100001b
ELSE
 ClassOut	equ	01000000b	;VendorRequest zum Test im EzMr
 ClassIn	equ	11000000b
ENDIF
;CountString: String Nr. R5-1 aus Descriptor-Liste auswählen
;PE: DPTR=String-Beschreiber-Liste (gerade Adresse!)
;    DPS=0 (sonst geht's nicht!)
;    R5=String-Nummer+1
;PA: CY=1 wenn String-Liste "vorfristig" zu Ende
;    DPTR=String-Beschreiber (stets gerade Adresse)
;VR: dptr,r5,a
cs_l:	LOADX
	jz	cs_ep0stall
	add	a,dpl0
	mov	dpl0,a
	jnc	CountString
	 inc	dph0
CountString:
	djnz	r5,cs_l
	RETNC
cs_ep0stall:	RETC

ep_cs:
;Rechnet Endpoint-ID in INxCS/OUTxCS-Adresse um
;PE: R6=[SetupDat+4]=Enden-Adresse
;PA: R0=Zeiger auf EPxCS (Low-Teil)
;VR: A,R0,C
	mov	a,r6
	;Zehn mögliche Fälle:	 01 81 02 82 04 84 06 86 08 88
	cjne	a,#1,ep_cs1	;01
	mov	a,#0FFh		;FF
	sjmp	ep_cs2
ep_cs1:	rr	a		;   40 01 41 02 42 03 43 04 44
	anl	a,#7		;   00 01 01 02 02 03 03 04 04
ep_cs2:	add	a,#LOW(EP1InCS)
	mov	r0,a
	ret
ResTog:	;Togglebit von R6=Endpoint rücksetzen
	mov	a,r6
	mov	c,ACC.7
	mov	ACC.4,c
	anl	a,#1Fh
ResTo:	STX	TOGCTL		;Toggle-Bit adressieren
	setb	ACC.5
	STX			;Togglebit zurücksetzen
	ret
ResTogAll:	;Alle Togglebits rücksetzen, VR: R5
	mov	r5,#1Fh
ResTo0:	mov	a,r5
	call	ResTo		;alle außer EP0Out (der geht immer)
	djnz	r5,ResTo0
	ret

GetStatus:	;bRequest=0, numerisch gleich: GetDeviceId (Drucker-String)
	cjne	r4,#ClassIn,gs_nid	;GET_DEVICE_ID? (Nur Interface 1)
;hier: GetDeviceId
	;cjne	r6,#1,err1	;Interface 1: Drucker (klappt nicht!!)
	ajmp	GetDeviceId
gs_nid:
	cjne	r4,#80h,gs_no_dev	;GS_DEVICE
nullin:	clr	a		;kein WakeUp(Bit1), kein SelfPower(Bit0)
wordin:	STORX	In0Buf
	STORX	NEXT,0
	STX	In0BC,2
	ret
gs_no_dev:
	cjne	r4,#81h,gs_no_if	;GS_INTERFACE
	jmp	nullin		;auch zwei Nullen melden
gs_no_if:
	cjne	r4,#82h,err1	;GS_ENDPOINT
	call	ep_cs
	LDX			;STALL-Bit angeln
	anl	a,#1
	jmp	wordin

ClearFeature:	;bRequest=1, numerisch gleich: GetPortStatus; GetReport
	cjne	r4,#ClassIn,cf_nps	;Klassenrequest
;hier: GetReport (wValueL=Report-ID, wValueH=3=Feature-Report, wIndexL=Interface)
	cjne	r6,#2,cf_ngr	;Interface, muss 2 sein für HID
	ajmp	HidGetReport
cf_ngr:
;hier: GetPortStatus
	;cjne	r6,#1,err1	;Interface 1: Drucker (klappt nicht!!)
	acall	in1
	anl	a,#00111000b
	ajmp	OneByteIn	;Info-Byte senden
cf_nps:
	cjne	r4,#2,err1
	cjne	r5,#0,err1	;einziges Feature: STALL
	call	ep_cs		;Endpoint zur Control+Status-Adresse umrechnen
	STX	,0		;STALL entfernen
	jmp	ResTog

SoftReset:
	cjne	r4,#ClassOut,err1
	;cjne	r6,#1,err1	;nur Interface 1 (klappt nicht!!)
	clr	IOA.INI		;RESET-Impuls ausgeben
	LDX	GpifIdleCtl
	anl	a,#70h
	orl	a,#011b		;SEL=L, AF=H, STB=H
	STX
	setb	IOA.INI
	ret

SetFeature:
	cjne	r4,#2,err1	;FT_ENDPOINT
	cjne	r5,#0,err1	;einziges Feature: STALL
	call	ep_cs		;Endpoint zur Control+Status-Adresse umrechnen
	STX	,1		;STALL setzen
	ret
err1:	RETC

GetDescriptor:			;bRequest=6
	LDX	SetupDat+3		;wValueH
	cjne	r4,#80h,gd_no_devin	;bmRequestType (In/Std/Device)
	cjne	a,#01h,gd_no_dev	;GD_DEVICE
	mov	dptr,#DeviceDescriptor
gd_sto:	mov	a,DPH0
	STX	SudPtrH
	mov	a,DPL0
	STX	NEXT
	ret
gd_no_dev:
	cjne	a,#02h,gd_no_conf	;GD_CONFIGURATION
	orl	c,HighSpeed
gd_cd:	mov	dptr,#ConfigDescFS
	jnc	gd_sto
	mov	dptr,#ConfigDescHS
	clr	c
	jmp	gd_sto
gd_no_conf:
	cjne	a,#03h,gd_no_str	;GD_STRING
	mov	dptr,#StringDesc
	inc	r5			;wValueL (String-Index)
	call	CountString
	jc	err1
	jmp	gd_sto
gd_no_str:
	cjne	a,#06h,gd_no_qual	;GD_QUALIFIER
	mov	dptr,#DeviceQualifier
	jmp	gd_sto
gd_no_qual:
	cjne	a,#07h,err1		;GD_OTHERSPEED
	orl	c,/HighSpeed
	jmp	gd_cd
gd_no_devin:
	cjne	r4,#081h,err1		;bmRequestType (In/Std/Interface)
	cjne	a,#21h,gd_no_hid
	mov	dptr,#HidDesc
	jmp	gd_sto
gd_no_hid:
	cjne	a,#22h,err1
IF 0
	STORX	SudPtrCtl,0
	;SyncDelay
	STORX	EP0BCH
	;SyncDelay
	mov	a,HRDE-HidReportDesc
	STORX	EP0BCL
	mov	dptr,#HidReportDesc
	jmp	gd_sto
ELSE
	;Deskriptor <dptr> Länge <r6> via EP0 senden
	;Die Umschaltung von SUDPTRCTL funktioniert einfach nicht!!
	STORX	PatchR+1,HIGH(MemR)
	STORX	NEXT,LOW(MemR)
	mov	dptr,#HidReportDesc
	mov	r6,#HRDE-HidReportDesc
	LDX	SetupDat+7	;wLengthH
	jnz	nolim
	LDX	PREV		;wLengthL
	CMP	a,AR6		;W2k,XP will 64 Bytes mehr lesen, wieso?
	jnc	nolim
	mov	r6,a
nolim:	dec	r6
	mov	r7,#1
	jmp	LEP0R
ENDIF

GetConfiguration:
	cjne	r4,#80h,err3		;nur DEVICE_IN
	mov	a,Configuration	 ;Entweder konfiguriert oder unkonfiguriert
OneByteIn:	
	STORX	In0Buf
	STX	In0BC,1
	ret

SetConfiguration:
	cjne	r4,#ClassOut,sc1
	cjne	r6,#2,err3		;Interface, muss 2 sein für HID
	ajmp	HidSetReport
sc1:	cjne	r4,#0,err3		;nur DEVICE_OUT
	mov	a,r5
	add	a,#-2
	jc	err3			;nur Konfiguration 0 oder 1
	mov	Configuration,r5
	ret

GetAltSet:
	cjne	r4,#81h,err3		;nur INTERFACE IN
	cjne	r6,#1,as_n1
	mov	a,AltSetting1
	jmp	OneByteIn
as_n1:	jnc	err3		;wenn Interface > 1
	clr	c
	clr	a
	jmp	OneByteIn

SetAltSet:
	cjne	r4,#1,err3		;nur INTERFACE OUT
;	cjne	r6,#0,as_n0	;Interface 0: h#s Parallelport
;	ret
as_n0:
	cjne	r6,#1,err3	;Interface 1: USB-Druckerunterstützung
	mov	a,r5
	add	a,#-3
	jc	err3		;Nur Alternative 0, 1 oder 2 zulassen
	mov	AltSetting1,r5
;	STX	OutPktEnd,84h	;EP4 (doppelt gepuffert) scharfmachen
;	STX
sude:	ret

SUD_Tab:
	ajmp	GetStatus	;0 - auch: GET_DEVICE_ID
	ajmp	ClearFeature	;1 - auch: GET_PORT_STATUS - HID_GET_REPORT
	ajmp	SoftReset	;2 (normalerweise err1)
	ajmp	SetFeature	;3
	RETC			;4
err3:	RETC	;SetAddress	;5, sollte nicht vorkommen
	ajmp	GetDescriptor	;6
	RETC	;SetDescriptor	;7
	ajmp	GetConfiguration;8
	ajmp	SetConfiguration;9 - auch: HID_SET_REPORT
	ajmp	GetAltSet	;10
	ajmp	SetAltSet	;11
	;RETC	;SyncFrame	;12

HandleSUD:			;liefert CY=1 für err1
	LDX	SetupDat+0	;bmRequestType -> R4
	mov	r4,a
	LDX	NEXT		;SetupDat+1: bRequest -> A
	cjne	a,#0A2h,hs1	;EEPROM-Zugriff?
	call	EReqInit	;wIndex heranziehen
	mov	dptr,#ERead
	MOVR	r6,r7,EE_W
	jmp	LongEP0
hs1:	cjne	a,#0A3h,hs2	;XRAM-Zugriff?
memep0:	mov	dptr,#MemR
	MOVR	r6,r7,MemW
	jmp	LongEP0
hs2:	mov	r7,a
	add	a,#-12		;nur 0..11 zulassen
	jc	err3
	LDX	NEXT		;SetupDat+2: wValueL -> R5
	mov	r5,a
	LDX	SetupDat+4	;wIndexL -> R6 (oft: Interface)
	mov	r6,a
	LDX	NEXT		;SetupDat+5: wIndexH -> R7
	xch	a,r7
	call	usr31		;darf bei CY=1 kein Register ändern!
	jnc	sude		;bei CY=0 ist's des Users Eigenverantwortung
	JMPTBL	SUD_Tab

;=========
;== HID ==
;=========
HidGetReport:
	MOVW	AutoPtr1,AnswerBuf
	MOVW	AutoPtr2,In0Buf
	mov	r0,#LOW(XAutoDat1)
	mov	r1,#LOW(XAutoDat2)
	LDX			;1. Byte = Längen-Byte = Report-ID (1..8)
	jz	hgee		;Keine Daten wenn Null
	mov	r2,a
	mov	r4,a
hge1:	STX1
	LDX
	djnz	r4,hge1
	mov	a,r2
	STX	In0BC		;Datenblock liefern
hgee:	ret

HidSetReport:
	WAIT_EP0_OUT		;Eingehende Daten abwarten (in einem Stück!)
	MOVW	AutoPtr1,Out0Buf
	MOVW	AutoPtr2,AnswerBuf+1	;doppelt benutzt!
	LDX	XAutoDat1
	mov	ep2ll,a		;Report-ID = Länge
	mov	ep2lh,#0
	call	ProcessInOut
	mov	a,AutoPtrL2
	subb	a,#LOW(AnswerBuf)
	mov	dptr,#AnswerBuf
	STORX			;Anzahl Antwortbytes abspeichern
	ret

;==============================
;== Routinen für Druckerport ==
;==============================
SetECR:	;ECR-Byte setzen, FIFOs leeren
	anl	a,#0F8h
	orl	a,#5		;FIFO leer setzen
	mov	ECR,a
	swap	a
	rr	a
	anl	a,#7
	inc	a
	mov	r3,a
	clr	a
	setb	c
se1:	rlc	a
	djnz	r3,se1		;Bit draus machen, testet sich besser
	mov	ECR_Bits,a

	mov	c,ECR_Bits.4	;EPP-Bit
	mov	a,#0FFh
	subb	a,#0
	mov	EppTimeout,a	;beim Einschalten von EPP auf Null, sonst 1

	mov	a,ECR_Bits
	anl	a,#00000101b	;Bei SPP oder SPP-FIFO auf Ausgabe schalten!
	jz	se3		;kein Schalten am Richtungsbit!
	clr	direction
	acall	DirChanged	;Ausgabetreiber stets aktiv - je nach OC-Simul.
se3:	mov	fifor,#Fifo
	mov	fifow,#Fifo
	ret

;===SPP-FIFO===========================
;Die Emulation der SPP-FIFO-Betriebsart
;Ein "echtes" Parallelport legt stets den FIFO-Kopf aufs Datenport...
SppXfer:
	jb	fifoe,exf	;wenn FIFO leer ist nichts zu tun
	LDX	GpifReadyStat
	jb	ACC.BSY,exf	;wenn beschäftigt dann geht's nicht
	mov	r0,fifor
	mov	OutB,@r0	;Datenbyte anlegen, Treiber muss aktiv sein
	LDX	GpifIdleCtl
	clr	ACC.STB
	STX			;Strobe aktivieren
	acall	IncFifoR	;Lesezeiger erhöhen (und Zeit verbrauchen)
	LDX	GpifIdleCtl
	setb	ACC.STB
	STX			;Strobe zurücknehmen
exf:	ret

;===EPP===========================

;A auf IdleCtl ausgeben und max. 10 µs auf WAIT=H warten
;PE: R0=LOW(GpifIdleCtl), A=GpifIdleCtl-Byte, R3=ASTB/DSTB-Maske
;PA: EppTimeOut.0, A=abgetastete Portpins
;VR: R0=LOW(GpifReadyStat),R1=A,R3
wait_epp:
	LDX	GpifIdleCtl
	mov	r1,a		;retten
	anl	a,r3		;ASTROBE/DSTROBE (ggf. WRITE)low
	STX
	mov	a,r3
	rrc	a		;Bit0 (Strobe = WRITE) extrahieren
	jc	sw0
	push	OEB		;bei Schreibzugriff: Zustand retten,...
	mov	OEB,#0FFh	;temporär alles Ausgabe
sw0:	;max. 10 µs warten bis WAIT=H
	mov	r3,#15		;15 Runden à 8 Takte (2/3 µs)
	mov	r0,#LOW(GpifReadyStat)
sw1:	LDX			;(2)
	jb	ACC.BSY,sw2	;(3) WAIT endlich HIGH
	djnz	r3,sw1		;(3)
	setb	EppTimeout.0	;TimeOut-Bit setzen
sw2:	jc	sw3		;noch einmal CY auswerten!
	pop	OEB		;bei Schreibzugriff: OEB restaurieren
sw3:	mov	r3,IOB		;abtasten (auch bei Schreibzugriff: egal!)
	mov	a,r1		;restaurieren
	STX	GpifIdleCtl	;ASTROBE/DSTROBE high
	mov	a,r3
	ret

out_epp:
;OUT-Befehl Byte R4 mit R3=Addr/DataStrobe, WRITE=0
;Steuerleitungen werden wie im Original nur bei Bedarf nach Low gezogen
;Datenrichtung wird (temporär) umgeschaltet. (Bei "richtigem" Port beobachtet.)
	mov	OutB,r4		;Daten ausgeben (immer - vorher!)
	LDX	GpifReadyStat	;EPP 1.9: WAIT muss LOW sein!
	jb	ACC.BSY,no_epp
	jmp	wait_epp

in_epp:
;IN-Befehl mit r3=Addr/DataStrobe
;PA: A=gelesenes Byte
	LDX	GpifReadyStat	;EPP 1.9: WAIT muss LOW sein!
	jb	ACC.BSY,no_epp_read_PINSB
	jmp	wait_epp

no_epp_read_PINSB:
	mov	a,IOB		;PortB lesen
no_epp:	setb	EppTimeOut.0	;TimeOut-Bit setzen
	ret			;nichts ausgeben!

;===ECP===========================

IncFifoPtr_Compare:
;FIFO-Zeiger erhöhen und mit anderem Zeiger vergleichen
;PE: R0=Zeiger auf einen der beiden FIFO-Zeiger
;PA: A=0 wenn nach Erhöhung beide Zeiger gleich, sonst <>0
	mov	a,@r0
	inc	a
	inc	a
	cjne	a,#Fifo+FIFOSIZE*2,ic1
	mov	a,#Fifo
ic1:	mov	@r0,a
	mov	a,fifor
	xrl	a,fifow
	ret

IncFifoR:
;FIFO-Lese-Zeiger erhöhen, "FIFO voll" löschen, "FIFO leer" ggf. setzen
;PE: -
;PA: A=0 wenn FIFO leer
;VR: R0,A
	mov	r0,#fifor
	call	IncFifoPtr_Compare
	jnz	ifr1
	setb	fifoe		;FIFO leer
ifr1:	clr	fifof		;FIFO ist keinesfalls voll
	ret

IncFifoW:
;FIFO-Schreib-Zeiger erhöhen, "FIFO leer" löschen, "FIFO voll" ggf. setzen
;PE: -
;PA: A=0 wenn FIFO voll
;VR: R0,A
	mov	r0,#fifow
	call	IncFifoPtr_Compare
	jnz	ifw1
	setb	fifof		;FIFO voll
ifw1:	clr	fifoe		;FIFO ist keinesfalls leer
	ret

EcpXfer:
;Auf Transfer von/in FIFO im Hintergrund prüfen
;Diese Routine wird, sofern ECP aktiv, zyklisch aufgerufen
;Keine Parameter, VR: A,R0,R3,R4
	jb	direction,EcpInXfer
	LDX	GpifIdleCtl
	jnb	ACC.STB,oxf2	;2. Phase der Byte-Übertragung zz. aktiv
	jb	fifoe,no_Xfer	;Kann kein Byte rausschicken
	LDX	GpifReadyStat
	jb	ACC.BSY,no_Xfer	;Gegenstelle ist beschäftigt
	mov	r0,fifor
	mov	AR4,@r0		;Datenbyte->R4
	inc	r0
	mov	AR3,@r0		;Command/Data->R3
	call	IncFifoR	;Lesezeiger erhöhen
	mov	OutB,r4		;Byte (R4) anlegen
	TRISB	0FFh		;Treiber aktivieren
	mov	a,r3
	rrc	a		;Command(0) oder Data(1) ausschieben
	LDX	GpifIdleCtl
	mov	ACC.AF,C	;HostAck(AF,1) setzen
	setb	ACC.INI		;nReverseRequest auf HIGH (Init) (unnötig?)
	STX
	clr	ACC.STB		;HostClk(STB) auf LOW
	STX
oxf2:	LDX	GpifReadyStat	;PeriphAck abfragen
	jnb	ACC.BSY,no_Xfer	;Gegenstelle ist beschäftigt
	LDX	GpifIdleCtl
	setb	ACC.STB		;HostClk(STB) auf HIGH
	STX
	ret
EcpInXfer:
	jb	fifof,no_Xfer	;Kann kein Byte einlesen
	LDX	GpifReadyStat
	xch	a,r3
	LDX	GpifIdleCtl
	jb	ACC.AF,ixf2	;HostAck=H, 2. Phase des Byte-Lesens...
	xch	a,r3
	jb	ACC.ACK,no_Xfer	;Gegenstelle meldet (noch) keinen Bedarf
	xch	a,r3
	setb	ACC.AF		;HostAck=H
	STX
ixf2:	LDX	GpifReadyStat
	jnb	ACC.ACK,no_Xfer	;PeriphClk=L = noch nicht bereit
	mov	c,ACC.BSY	;Command(0) / Data(1)
	mov	r0,fifow
	mov	@r0,IOB		;Datenbyte abholen und abspeichern
	inc	r0
	clr	a
	mov	ACC.0,c		;Bit einsetzen
	mov	@r0,a
	call	IncFifoW
	LDX	GpifIdleCtl
	clr	ACC.AF		;HostAck=L
	STX
no_Xfer:ret


DirChanged2:
;Aufzurufen, wenn sich die Steuerport-Steuerungbits ändern...
;PE: -
;Neues Control5V berechnen:
	mov	c,ECR_Bits.0	;SPP-Modus?
	anl	c,/TPControl
	orl	c,OCControl
	jc	dc_occontrol1	;C=Offene Kollektor-Simulation fürs Steuerport
	jbc	Control5V,dc_occontrol0
	ret
dc_occontrol0:	;OpenCollector-Status löschen!
	orl	OEA,#00000100b	;INI als Ausgang aktivieren
	STX	GpifCtlCfg,10000000b	;Push-Pull setzen
	ret
dc_occontrol1:	;OpenCollector-Status setzen?
	jb	Control5V,dc_oce	;Bereits aktiviert: nichts tun!
	setb	Control5V	;Aktivierung vermerken
	STX	GpifCtlCfg,10000111b	;OpenCollector setzen
DoOcControl:	;Ausgabetreiber für INI nachführen zur OC-Simulation
;PE: DCR
;VR: A
	mov	c,DCR.INI	;RAM-Kopie lesen, weil Flipflop nicht lesbar
	cpl	c
	mov	a,OEA
	mov	ACC.INI,c	;in beiden Revisionen gleiches Bit
	mov	OEA,a
dc_oce:	ret
	
DirChanged0:
;Aufzurufen, wenn sich die Datenportrichtung ändert...
;Neues Data5V berechnen:
	mov	c,OCData	;Feature-Register: aktiv?
	anl	c,/direction	;ECR-Register: Ausgabe?
	jc	dc_ocdata
	jbc	Data5V,DoSetOeb	;OpenCollector-Status löschen
	ret
DoSetOeb:
	clr	a
	jb	direction,dc_tb
	dec	a
dc_tb:	TRISB		;Treiberstatus setzen
	ret
dc_ocdata:
	jb	Data5V,dc_oce	;Ist schon an!
	setb	Data5V
	mov	a,IOB		;Problem: Flipflops nicht rücklesbar
;DoOcData:	;A=Datenbyte: Ausgabetreiber nachführen (zu simpel für UP!)
	cpl	a
	TRISB		;High-ausgebende Leitungen hochohming
	ret

;===Portzugriffe=========================

;OUT-Unterprogramme bekommen in R4 das Argument,
;IN-Unterprogramme müssen das Byte in A liefern

;=== OUT auf Adresse +0 (Datenport) ===
out0:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = FIFO-Betriebsarten?
	jz	out0ecp		;mit A=0 in die FIFO
	mov	OutB,r4
	jnb	Data5V,out0e	;Open-Collector-Simulation?
	mov	a,r4
	cpl	a
	TRISB
out0e:	ret

;=== OUT auf Adresse +1 (Statusport) ===
out1:	mov	a,r4
	jb	DirectIo,out1ni	;Nicht invertieren, Extra-Bits durchlassen
	anl	a,#11111000b
	xrl	a,#10000000b
out1ni:	mov	IOD,a		;bei einem Eingabeport ohne Wirkung
	jnb	ECR_Bits.4,out1nepp
	mov	a,r4
	jnb	ACC.0,out1nepp
	clr	EppTimeOut.0	;TimeOut-Bit löschen
out1nepp:
	jnb	Rev2,out1e
	mov	c,ACC.5		;evtl. Problem mit fehlender Rücklesbarkeit!
	mov	IOA.PE,c
	mov	c,ACC.4
	mov	IOA.ONL,c
	mov	c,ACC.3
	mov	IOA.ERR,c	
out1e:	ret
	
;=== OUT auf Adresse +2 (Steuerport) ===
;Bit4=IRQ-Freigabe (nicht unterstützt, aber gespeichert)
;Bit5=Ausgabetreiber-Freigabe
;Randproblem: Keine zeitgleiche Steuerung von INI und den drei anderen
;Steuerleitungen möglich, wenn man GPIF benutzt
out2:	mov	DCR,r4		;(2)
	mov	a,ECR_Bits	;(2)
	anl	a,#00000101b	;(2)
	jz	bidi1		;(3) Modus nicht SPP oder AutoStrobe
	clr	direction	;(2) DCR.5 klebt auf 0
bidi1:	mov	c,DCR.2		;(2)	;INIT
	mov	IOA.INI,c	;(2)
	mov	a,DCR		;(2)
	mov	c,ACC.3		;(2)	;SEL
	mov	ACC.SEL,c	;(2)	;zu Bit2 schaffen
	jb	DirectIo,out2ni	;(4)	;Nicht invertieren
	cpl	a		;(1)	;diese drei Bits sind invertiert
out2ni:	anl	a,#7		;(2)
	mov	r4,a		;(1)	;retten
	LDX	GpifIdleCtl	;(4)
	anl	a,#70h		;(2)	;Richtungsbits unverändert lassen
	orl	a,r4		;(1)
	STX			;(2)
	jnb	Control5V,DirChanged;(4)
	acall	DoOcControl	;OC für INI simulieren
DirChanged:
	jb	DirectIo,out2e	;(4) Nicht die Datenportrichtung beeinflussen!
	acall	DirChanged0	;Ggf. OC-Simulation umschalten
	jnb	Data5V,DoSetOeb	;Ohne OC-Simulation Tristate-Treiber schalten
out2e:	ret			;(4)

;=== OUT auf Adresse +3 (EPP-Adresse) ===
out3:	mov	r3,#NOT ((1 SHL SEL) OR (1 SHL STB))	;AddrStrobe (17) LOW
	jmp	out_epp

;=== OUT auf Adresse +4 (EPP-Daten) ===
out4:	mov	r3,#NOT ((1 SHL AF) OR (1 SHL STB))	;DataStrobe (14) LOW
	jmp	out_epp

;=== OUT auf Adresse +400 (ECP-Daten-FIFO) ===
out400:	mov	a,ECR_Bits
	anl	a,#01001100b	;Konfigurationen mit FIFO
	jz	out2e
	mov	a,#1		;Daten (=1)
out0ecp:
	jb	fifof,nix	;Nichts tun, wenn FIFO voll!
	mov	r0,fifow
	mov	@r0,AR4		;abspeichern
	inc	r0
	mov	@r0,a
	jmp	IncFifoW

;=== OUT auf Adresse +402 (ECP-Steuerport) ===
out402:	mov	a,r4
	jmp	SetECR

;=== OUT auf Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
out404:	;1 = Ausgabe, 0 = Eingabe
	mov	a,r4
	TRISB
	ret
;=== OUT auf Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
out405:	;1 = Ausgabe, 0 = Eingabe, Standard
	;Bei Rev.2 nicht für BSY und ACK realisierbar
	mov	a,r4
	mov	OED,a		; Rev.2: Falls jemand Brücken legt...
	jnb	Rev2,out405e
	mov	B,OEA		;OEA bitadressierbar machen
	mov	c,ACC.5
	mov	B.PE,c
	mov	c,ACC.4
	mov	B.ONL,c
	mov	c,ACC.3
	mov	B.ERR,c
	mov	OEA,B
out405e:ret
;=== OUT auf Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
out406:	;Eingabe mittels High-Nibble von GpifIdleCfg (070128)
	mov	a,r4		;(1)
	jnb	ACC.2,initi
	orl	OEA,#(1 SHL INI)
	sjmp	inito
initi:	anl	OEA,#not (1 SHL INI)
inito:	mov	c,ACC.3		;(2)	;SEL
	mov	ACC.SEL,c	;(2)
	swap	a
	anl	a,#070h		;High-Nibble
	mov	r4,a
	LDX	GpifIdleCtl
	anl	a,#7		;CTL0..CTL2 (56beiniges Gehäuse) behalten
	orl	a,r4
	STX
sf1:	ret
;=== OUT auf Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
out407:	mov	a,r4
SetFea:	anl	a,#11100111b	;Nur "bekannte" Bits durchlassen
	jnb	Rev2,sf2
	orl	a,#00000010b	;TPControl EIN bei Rev.2
	anl	a,#01111111b	;Pullups stets EIN (kein Schalttransistor)
sf2:	xch	a,Feature
	xrl	a,Feature	;Veränderungen?
	jz	sf1		;nein, gar nichts tun
	setb	FeatureChanged	;in EEPROM brennen (verzögert)
	jnb	ACC.6,setf6
	LDX	GpifIdleCtl
	xrl	a,#7		;sofort entsprechende Bits drehen!
	STX
	xrl	IOD,#10000000b	;... in der Annahme der Rücklesbarkeit:-)
setf6:	jb	Rev2,nopu
	mov	c,Pullups	;Feature.7: Pullups schalten
	mov	IOA.7,c		;"schleichend" verändertes Bit korrigieren
	cpl	c
	mov	a,OEA
	mov	ACC.7,c
	mov	OEA,a		;Z = Pullups AUS (H geht nicht!)
nopu:	acall	DirChanged0	;OC-Status Datenport nachführen
	ajmp	DirChanged2	;OC-Status Steuerport nachführen

;Der Teufel hat ECP erfunden! Wie soll festgestellt werden, ob in der
;FIFO ein Adress- oder ein Datenbyte liegt?
;Mein "echtes" EC-Port ignoriert beim Rücklesen einfach das neunte Bit.
;=== IN von Adresse +0 (Datenport) ===
in0:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = eine FIFO-Betriebsart?
	jz	infifo		;Datenport nicht rücklesbar!
	mov	a,IOB
nix:	ret

;=== IN von Adresse +1 (Statusport) ===
in1:			;(3-4) Aufruf
	jb	Rev2,in1r2	;(4)
	mov	a,IOD	;(2)
	jb	DirectIo,in1x
	xrl	a,#80h	;(2)	;BSY invertieren
	sjmp	in1e	;(3)
in1r2:	LDX	GpifReadyStat	;(4)
	rrc	a	;(1)	;BSY nach C, ACK nach ACC.0
	rr	a	;(1)	;ACK nach ACC.7
	jb	DirectIo,in1ni	;(4)
	cpl	c	;(1)	;BSY invertieren
in1ni:	rrc	a	;(1)	;BSY nach ACC.7, ACK nach ACC.6, OK
	mov	c,IOA.PE;(2)	;Die zu chaotische Zuordnung mit Bit-Transfer
	mov	ACC.5,c	;(2)
	mov	c,IOA.ONL;(2)
	mov	ACC.4,c	;(2)
	mov	c,IOA.ERR;(2)
	mov	ACC.3,c	;(2)
in1e:	orl	a,#7	;(2)	;restliche Bits = 1
	anl	a,EppTimeOut	;Bit 0 löschen, wenn kein EPP-TimeOut
in1x:	RETNC		;(5) = (26-27)
	
;=== IN von Adresse +2 (Steuerport) ===
;Überraschung: Bei einem "genügend neuen" echten Parallelport sind die
;Steuerleitungen nicht (mehr) rücklesbar!
;Hier wird diese Einschränkung nur in den 3 FIFO-Betriebsarten nachgeahmt
in2:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = FIFO-Betriebsart
	jz	in2o
	mov	a,DCR
	orl	a,#11001111b	;nur Interrupt- und Richtungs-Bit (4 & 5)
	mov	r3,a
	jb	Rev2,in2r2
	;Rev.3+: richtiges Lesen von Istzuständen
	mov	a,IOA	;(2)
	jb	DirectIo,in2e	;Nicht invertieren
	xrl	a,#1011b;(2)	;Alles außer INI invertieren
	sjmp	in2e	;(3)
in2r2:	;Rev.2: kein echtes Lesen von Leitungszuständen möglich
	LDX	GpifIdleCtl
	jb	DirectIo,in2ni	;Nicht invertieren
	cpl	a
in2ni:	mov	c,ACC.SEL
	mov	ACC.3,c
	mov	c,IOA.2		;INI-Bit lesen (Rev.2: als einziges echtes Bit)
	mov	ACC.2,c
in2e:	orl	a,#0F0h
	anl	a,r3		;Interrupt- und Richtungsbit aus DCR einsetzen
	ret
in2o:	;Rücklesen des Ausgaberegisters; erspart Kopfzerbrechen mit ECP
	mov	a,DCR
	orl	a,#11000000b	;obere Bits lesen immer 1
	ret

retff:	mov	a,#0FFh
retin:	ret

;=== IN von Adresse +3 (EPP-Adresse) ===
in3:	mov	r3,#NOT (1 SHL SEL)	;AddrStrobe LOW
	jmp	in_epp

;=== IN von Adresse +4 (EPP-Daten) ===
in4:	mov	r3,#NOT (1 SHL AF)	;DataStrobe LOW
	jmp	in_epp

;=== IN von Adresse +400 (ECP-FIFO) ===
in400:	mov	a,ECR_Bits
	anl	a,#11001100b	;FIFO-Betriebsarten + Konfiguration
	jz	retff		;ohne ECP gibts kein Port+400h
	mov	a,#00010000b	;Konfigurationsregister (Konstante) A
	jb	ECR_Bits.7,retin
infifo:	mov	r0,fifor
	mov	a,@r0		;FIFO lesen
	jb	fifoe,retin	;Letztes FIFO-Byte liefern wenn FIFO leer
	mov	r3,a		;Logische Schwäche bei BeyondLogic:
				;Wohin mit dem "gelesenen" PeriphAck?
	call	IncFifoR
	mov	a,r3
	ret

;=== IN von Adresse +401 (ECP-???) ===
in401:	jnb	ECR_Bits.7,retff
	clr	a		;Konfigurationsregister (Konstante) B
	ret

;=== IN von Adresse +402 (ECP-Steuerport) ===
in402:	mov	a,ECR
	ret
	
;=== IN von Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
in404:	mov	a,OEB
	ret
;=== IN von Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
in405:	mov	a,OED
	jnb	Rev2,in405e
	anl	a,#00111111b	;BSY und ACK sind immer Eingang
in405e:	ret
;=== IN von Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
in406:	LDX	GpifIdleCtl
	swap	a		;Richtungsbits für SEL, AF, STB
	mov	c,ACC.SEL
	mov	ACC.3,c
	mov	r3,a		;retten
	mov	a,OEA
	mov	c,ACC.2		;INI-Datenrichtungsbit
	mov	a,r3		;zurück
	mov	ACC.2,c
	anl	a,#0Fh
	ret
;=== IN von Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
in407:	mov	a,Feature
	ret

;=== Warte-Hilfsbefehl, Argument R4 = Wartezeit in 1 µs ===
wait:	inc	r4		;Wir brauchen 4x6=24 Takte pro Durchlauf
w1:	inc	dptr		;kurzer Befehl mit langer Dauer!
	inc	dptr
	inc	dptr
	djnz	r4,w1		;3 Takte
	ret

;=== Sprungtabelle für IN- und OUT-Befehle ===
;					Abgefangener|EP2 Out EP2 Out|EP2 In
;	=========> USB-Pipe-Belegung:	"ASM-Befehl"|1. Byte 2. Byte|Antwort
;					------------------------------------
upv:	ajmp	out0	;		OUT +0,al   |	00h	al  |	-
	ajmp	out1	;		OUT +1,al	01h	al	-
	ajmp	out2	;		OUT +2,al	02h	al	-
	ajmp	out3	;EPP-Adresse	OUT +3,al	03h	al	-
	ajmp	out4	;EPP-Daten	OUT +4,al	04h	al	-
	ajmp	out4	;		OUT +5,al	05h	al	-
	ajmp	out4	;		OUT +6,al	06h	al	-
	ajmp	out4	;		OUT +7,al	07h	al	-
	ajmp	out400	;nur ECP	OUT +400h,al	08h	al	-
	ajmp	nix	;		OUT +401h,al	09h	al	-
	ajmp	out402	;		OUT +402h,al	0Ah	al	-
	ajmp	nix	;		OUT +403h,al	0Bh	al	-
	ajmp	out404	;HINTERTÜR	OUT +404h,al	0Ch	al	-
	ajmp	out405	;HINTERTÜR	OUT +405h,al	0Dh	al	-
	ajmp	out406	;HINTERTÜR	OUT +406h,al	0Eh	al	-
	ajmp	out407	;Feature	OUT +407h,al	0Fh	al	-
	ajmp	in0	;		al = IN +0	10h	-	al
	ajmp	in1	;		al = IN +1	11h	-	al
	ajmp	in2	;		al = IN +2	12h	-	al
	ajmp	in3	;EPP-Adresse	al = IN +3	13h	-	al
	ajmp	in4	;EPP-Daten	al = IN +4	14h	-	al
	ajmp	in4	;		al = IN +5	15h	-	al
	ajmp	in4	;		al = IN +6	16h	-	al
	ajmp	in4	;		al = IN +7	17h	-	al
	ajmp	in400	;nur ECP	al = IN +400h	18h	-	al
	ajmp	in401	;		al = IN +401h	19h	-	al
	ajmp	in402	;		al = IN +402h	1Ah	-	al
	ajmp	retff	;		al = IN +403h	1Bh	-	al
	ajmp	in404	;HINTERTÜR	al = IN +404h	1Ch	-	al
	ajmp	in405	;HINTERTÜR	al = IN +405h	1Dh	-	al
	ajmp	in406	;HINTERTÜR	al = IN +406h	1Eh	-	al
	ajmp	in407	;Feature	al = IN +407h	1Fh	-	al
	ajmp	wait	;Code zum Warten (kein)		20h   x * 4 µs	-
;					------------------------------------
;Viele OUT-Befehle tun nichts, fast genauso viele IN-Befehle liefern nur FFh.
;Die Warte-Routine bietet die Möglichkeit, Port-Zugriffe zeitlich
;auseinanderzuziehen. (Hoffentlich braucht das niemand!)
;Die Bulk-Daten dürfen beliebig viele OUT- und IN-Befehle zusammenfassen.
;Alle nicht gelisteten 1. Bytes (also >20h) sowie fehlende Folge-Bytes
;im jeweiligen Bulk-Datenblock führen zum Abbruch der Bearbeitung des Blocks.
;Es sei denn, es ist "benutzerdefinierter Kode" heruntergeladen.

Aufruf:			;R3=UP-Nummer
	mov	a,r3
	JMPTBL	upv
	
;=== IEEE1284 ===
Request:
;Datenbyte liegt bereits an PortB, Steuerleitungen im Defaultzustand
	clr	c
	STX	GpifIdleCtl,01110101b	;SEL=high, AF=low
	mov	r2,#13		;max. 10 ms warten
	mov	r4,#0
rl1:	call	in1		;(26)
	anl	a,#01111000b	;(2)	ACK=LOW, PE=HIGH, SEL=HIGH, ERR=HIGH?
	xrl	a,#00111000b	;(2)
	jz	ry1		;(3)
	djnz	r4,rl1		;(3) * 256 = 9216T = 768µs
	djnz	r2,rl1
	RETC
ry1:	STX	GpifIdleCtl,01110100b	;STB=low
	mov	r4,#10		;1 µs
rl3:	djnz	r4,rl3		;(3)
	STX	,01110111b	;AF=high, STB=high
	mov	r3,#13		;max. 10 ms warten
	;mov	r4,#0
	mov	r0,#LOW(GpifReadyStat)
rl2:	call	in1		;(26)
	anl	a,#01111000b	;(2)	ACK=HIGH, PE=LOW, SEL=HIGH, ERR=LOW?
	xrl	a,#01010000b	;(2)
	jz	ry2		;(3)
	djnz	r4,rl2		;(3) * 256 = 9216T = 768µs
	djnz	r2,rl2
	setb	c
ry2:	ret	
	
Nibble:
;PA: Nibble in A (sowie R5)
;VR: R0,R3,R4,R5
	STX	GpifIdleCtl,01110101b	;AF low
	mov	r3,#6		;max. 1 ms warten
	mov	r2,#0
	mov	r0,#LOW(GpifReadyStat)
nl1:	LDX			;(2)
	jnb	ACC.ACK,ny1	;(4)
	djnz	r2,nl1		;(3) * 256 = 2304T = 192µs
	djnz	r3,nl1
	RETC
ny1:	mov	c,ACC.BSY
	clr	a
	rlc	a
	mov	c,IOA.PE
	rlc	a
	mov	c,IOA.ONL
	rlc	a
	mov	c,IOA.ERR
	rlc	a		;u.a. CY=0
	push	ACC
	 STX	GpifIdleCtl,01110111b	;AF high
	 mov	r3,#6		;max. 1 ms warten
	 mov	r2,#0
	 mov	r0,#LOW(GpifReadyStat)
nl2:	 LDX			;(2)
	 jb	ACC.ACK,ny2	;(4)
	 djnz	r2,nl2		;(3) * 256 = 2304T = 192µs
	 djnz	r3,nl2
	 setb	c		;TimeOut
ny2:	pop	ACC
	ret

TwoNibbles:
;VR: R0,R2,R3,R5
	call	Nibble	;Low-Nibble
	jc	tne
	mov	r5,a
	call	Nibble	;High-Nibble
	swap	a
	orl	a,r5
tne:	ret

GetDeviceId:
;Beschafft Drucker-ID im Nibble-Modus
;PA: CY=1: Fehler
;    CY=0: Daten über EP0 (64-Byte-weise) übertragen, kein STALL
;VR: R0..R7
	setb	c
	LDX	GpifIdleCtl	;muss x111xxxx sein! Sonst Fehler!
	cpl	a
	anl	a,#70h
	jnz	gdie
	STX	,01110011b	;SEL=low, (INI=high,) AF=high, STB=high
	mov	OutB,#4		;"Get Device ID using Nibble Mode"
	jnb	Data5V,gd1	;Open-Collector-Simulation? (Datenport)
	mov	OEB,#not 4
gd1:	call	Request
	jc	gdie
	mov	dptr,#GetDevId	;Kernfunktion für Universalroutine
	mov	r4,#11000000b	;"Vendor-Request", IN
	call	LongEP0		;Universalroutine arbeiten lassen
	STX	GpifIdleCtl,01110011b;Standard-Modus zurück
gdie:	ret
	
GetDevId:
;Callback-Routine mit R4=Transferlänge und R1=AutoDat
	jb	F0,GetDevIdCont
	call	TwoNibbles
	jc	gdie
	STX1
	djnz	r4,gdi1
	ret
gdi1:	mov	DPH0,a	;High-Teil retten
	call	TwoNibbles
	jc	gdie
	STX1
	;DPTR := 2 - Länge (also im Regelfall eine negative 16-bit-Zahl)
	mov	r2,a
	mov	a,#2
	subb	a,r2
	mov	DPL0,a	;Low-Teil
	clr	a
	subb	a,DPH0
	mov	DPH0,a	;High-Teil
	clr	c
	setb	F0	;Fortsetzungs-Bit
	sjmp	gdi3
GetDevIdCont:
	mov	a,DPH0
	jz	gdi4	;Rest mit Nullen füllen
	call	TwoNibbles
	jc	gdie
	inc	dptr	;solange negativ kommen Bytes vom Drucker
gdi4:	STX1
gdi3:	djnz	r4,GetDevIdCont
	ret

IF Gpif=0
;== Drucker-Klassen-Simulation ==
UniXfer:	;Aufruf wenn Daten in OUT4BUF vorhanden sind
	LDX	GpifReadyStat
	jb	ACC.BSY,ux1	;BUSY prüfen, nichts tun wenn beschäftigt
	mov	DPL0,UniIdxL	;Index in OUT4BC holen
	mov	a,UniIdxH
	add	a,#HIGH(EP4FifoBuf)
	mov	DPH0,a
	LOADX			;Datenbyte aus Puffer holen
	mov	OutB,a		;anlegen
	jnb	Data5V,ux3	;Open-Collector-Simulation?
	cpl	a
	TRISB
ux3:	setb	IOA.INI		;INIT inaktiv (INI=high)
	STX	GpifIdleCtl,01110011b;SEL=low für Standard-Parallelport-Betrieb
	STX	,01110010b	;Strobe-Signal erzeugen (STB=low)
	inc	UniIdxL		;Index erhöhen
	mov	a,UniIdxL
	jnz	ux2
	inc	UniIdxH
ux2:	mov	a,#66
	call	StartLed2
	STX	,01110011b	;einige Zyklen später (1 µs) STB=HIGH
	LDX	EP4BCH
	xrl	a,UniIdxH	;Puffer ausgelesen?
	jnz	ux1		;nein
	LDX	EP4BCL
	xrl	a,UniIdxL
	jnz	ux1
	mov	UniIdxL,a	;Index auf Null stellen
	mov	UniIdxH,a
	STX	OutPktEnd,84h	;ja, Puffer an USB übergeben, scharf machen
ux1:	ret
ENDIF
;=== LED-Spielerei ===

StartLed:
;Start des LED-Blinkens mit a=Blinkperiodendauer, 0=Maximum
;Blinkt die LED bereits, wird nur die künftige Blinkperiode gesetzt
;VR: A
	mov	Led_T,a		;Zeitzähler (Monoflop) neu starten
	mov	Led_F,a		;Blinkfrequenz setzen
	jb	Led_B,nost
	mov	Led_C,a
	setb	Led_B
p0Led:	setb	Led		;LED ausschalten (Zugriff beginnt)
nost:	ret

StartLed2:
	mov	Led2_T,a	;Zeitzähler (Monoflop) neu starten
	mov	Led2_F,a	;Blinkfrequenz setzen
	jb	Led2_B,nost2
	mov	Led2_C,a
	setb	Led2_B
	mov	c,HighSpeed
	mov	Led2State,c		;LED umschalten
nost2:	ret

On1ms:	;Aufruf alle 1 Millisekunde: LED-Blinken
	jnb	Led2_B,n2
	djnz	Led2_C,nbl2
	cpl	Led2State	;blaue LED umschalten
	mov	Led2_C,Led2_F
nbl2:	djnz	Led2_T,n2
	clr	c
	orl	c,/HighSpeed
	mov	Led2State,c	;LED in Normalzustand zurück
	clr	Led2_B
n2:
	jnb	Led_B,n1	;LED blinkt?
	djnz	Led_C,nbl
p1Led:	cpl	Led
	mov	Led_C,Led_F
nbl:	djnz	Led_T,n1
p2Led:	clr	Led		;Nach paar Millisekunden bleibt das Licht an
	clr	Led_B
n1:	;blaue LED so schalten wie Led2State
	mov	c,Led2State
p0Led2:	mov	Led2,c
	ret
IF 0
;Doppel-Pufferung via EP1 und Puffer @C0..FF
	mov	IntReq,EP01Stat
	jb	IntReq.1,nep1
	MOVW	AutoPtr1,EP1OutBuf
	LDX	EP1OutBC
	jz	ncop
	mov	r2,a
	mov	r0,#LOW(XAutoDat1)
	mov	r1,#0C0h
cop:	LDX			;(2)
	mov	@r1,a		;(1)
	inc	r1		;(1)
	djnz	r2,cop		;(3)
	
ncop:	jb	IntReq.2,nep1
nep1:
ENDIF

HandleUsbSleep:
;Alles auf Energiesparen schalten; kehrt mit dem Wakeup des Prozessors zurück
	clr	HighSpeed
	setb	Led2State
	LDX	GpifIdleCtl
	mov	r4,a		;retten
	clr	a
	push	OEA
	push	OEB
	push	OED
	 STX			;CTLx-Ausgänge hochohmig
	 mov	OEA,a		;alles hochohmig und Energie sparend
	 mov	OEB,a
	 mov	OED,a
	 mov	PCON,#31h	;Tiefschlaf hier (schon mal Strom gemessen?)
	pop	OED
	pop	OEB
	pop	OEA		;Tiefschlaf beendet
	mov	a,r4
	STX			;CTLx-Ausgänge auf alten Zustand
	ret

SaveFeature:
	clr	FeatureChanged
	mov	a,Feature
	STORX	Scratch
	call	EReqInitBoot
	MOVW	AutoPtr1,Scratch
	mov	r1,#LOW(XAutoDat1)
	mov	r4,#1
	mov	dptr,#0FFFBh	;Letztes Byte vor Seriennummer
	jmp	EWrite		;Daten schreiben (ohne Verify)

;===========================
;== Mikrocode-Interpreter ==
;===========================
;2-Byte-OUT- und 1-Byte-IN-Kommandos abarbeiten
;(auf verschiedenen Wegen, nämlich USB2LPT Bulk+Control, HID Control+Interrupt)
;PE: AutoPtr1=Kommandopuffer
;    ep2l=Kommandopuffer-Länge
;    PendingByte=unvollständiges Kommandobyte
;    AutoPtr2=Ergebnispuffer
;PA: AutoPtr2=Ende der Ergebnisse
;    PendingByte=unvollständiges Kommandobyte
;VR: alle, ep2h=ep2l=0
ProcessInOut:
	mov	a,#100		;kurz (Blinken mit 5 Hz)
	call	StartLed
	mov	a,ep2ll
	add	a,#0FFh
	mov	a,ep2lh
	addc	a,#0
	mov	ep2lh,a		;High-Teil für Doppelschleife gestalten
	jz	pioe
	clr	a
	xch	a,PendingByte
	jz	pio1
	clr	ACC.4
	sjmp	pio2
pio1:
	LDX	XAutoDat1	;"Befehls"-Byte vom OUT2-Puffer lesen
pio2:	CMP	a,#21h
	jc	pio3
	call	usr2f		;zur Extension springen
	jc	pioe		;Bulk-Block-Rest bei Fehler verwerfen
	sjmp	pio5
pio3:
	mov	r3,a
	jb	ACC.4,pioIn
	djnz	ep2ll,pio4	;len--
	djnz	ep2lh,pio4
	setb	ACC.4
	mov	PendingByte,a	;Fehler, wenn kein Folge-Byte folgt
	sjmp	pioe
pio4:	LDX			;weiteres AUTODATA-Byte lesen
	mov	r4,a
	call	Aufruf
	sjmp	pio5
pioIn:
	call	Aufruf
	STX	XAutoDat2	;IN-Byte in Ep6FifoBuf ablegen
pio5:	djnz	ep2ll,pio1
	djnz	ep2lh,pio1
pioe:	clr	c
	ret

;=====================
;== Initialisierung ==
;=====================
main:
	clr	a
	mov	IOD,a
	mov	OED,#00000111b	;Massepins (Rev.4) simulieren
	mov	CKCON,a		;Handbremse für "movx" lösen
	mov	MPAGE,#HIGH(CPUCS)	;die meisten XRAM-Register
	mov	r0,#7Fh
	mov	sp,r0
clr_ram:mov	@r0,a
	djnz	r0,clr_ram
	STX	CPUCS,10h	;kein CLK-Ausgang, 48 MHz
;Persistente Konfiguration lesen und übernehmen
	call	EReqInitBoot
	MOVW	AutoPtr1,Scratch
	mov	r1,#LOW(XAutoDat1)
	mov	r4,#1
	mov	dptr,#0FFFBh	;Letztes BYTE vor Seriennummer
	call	ERead		;persistente Daten lesen
	LOADX	Scratch
	inc	a
	jz	w3		;FFh als 00h annehmen
	dec	a
w3:	call	SetFea		;setzen
	clr	FeatureChanged
;Ports initialisieren
	mov	a,#20h		;PS/2-Modus (zweckmäßiger?)
	call	SetECR
;RevCtl erfordert im Gegensatz zur Dokumentation ein SyncDelay!
	STX	RevCtl,11b	;Bit0 und Bit1 setzen
	mov	IOB,#0		;0 gibt das BIOS standardmäßig aus (?)
	jb	DirectIo,w4	;bei DirectIo bleibt Datenport Eingang
	mov	OEB,#0FFh	;alles Ausgabe auf PortB
;Feststellung der Brücke zwischen IOA.6 und IOA.7 bei Revision 3
w4:	mov	OEA,#80h
	setb	IOA.7		;Pull-Up-Treiber aus
	jnb	IOA.6,isrev2
	clr	IOA.7		;Pull-Up-Treiber ein
	jb	IOA.6,isrev2
	mov	IOA,#00100100b	;Pull-Up ein, gelb ein, blau aus, SEL low
	mov	OEA,#DefOEA
	sjmp	isrev3
isrev2:
	call	PatchCode
	mov	IOA,#10000100b	;gelbe LED ein, blaue LED aus, INI high
	mov	OEA,#DefOEAr2	;Ausgabe für LEDs und INI auf PortA
isrev3:
	STX	GpifCtlCfg,10000000b	;Push-Pull-Betrieb
	STX	PREV,01110011b	;GpifIdleCtl: AF und STB auf High, SEL auf Low
	jnb	DirectIo,w5	;bei DirectIo auch Steuerleitungen Eingänge
	STX	,00000011b	;GpifIdleCtl: Eingänge
	anl	OEA,#11111011b	;INI als Eingang
w5:	setb	EICON.5		;Resume-Interrupt (auch bei EA=0)

	LDX	USBCS
IF Always_Renum=0
	jnb	ACC.7,nohs
	setb	HighSpeed
nohs:	jb	ACC.1,noren	;ReNum überspringen, falls vom EEPROM geladen
ENDIF
	setb	ACC.3		;DisCon
	STX
	mov	r5,#48		;1,5 Sekunden warten
	mov	r3,#0
w2:	call	w1		;verputzt R4=0
	djnz	r3,w2
	djnz	r5,w2
	xch	a,r4		;A=0
	dec	a		;A=0FFh
	STX	UsbIrq		;alle alten Interruptanforderungen löschen
	STX	EpIrq
	STX	UsbErrIrq
	xch	a,r4
	setb	ACC.1		;ReNum
	clr	ACC.3		;DisCon
;	clr	Led2
	STX	USBCS
noren:
	call	ResetFifos
IF Gpif
	STX	IFConfig,11000010b
	STX	EP2FifoCfg,0	;PortD frei
	STX	NEXT,10h		;EP4FifoCfg: AutoOut(?)
	STX	NEXT,0		;EP6FifoCfg
	STX	NEXT		;EP8FifoCfg
	MOVW	AutoPtr1,SPP_W
	MOVW	AutoPtr2,%(WaveData+32)
	mov	r4,#32
	call	memcpy
	STORX	WF3_Branch,077o
	STORX	WF3_Opcode,111b
	STORX	WF3_Output,011b
	STX	GpifIdleCS,1
	mov	GpifSglDatLX,#0
	STX	EP4GpifFlgSel,1
ENDIF
p3Led:	clr	Led		;gelbe LED einschalten
;	call	EReqInitBoot
;===================
;== Hauptschleife ==
;===================
mainloop:
	call	usr36		;Zyklischer User-Aufruf
	jnb	FeatureChanged,skipSF
	call	SaveFeature
;Software-FIFOs (SPP- und ECP-Simulation) abarbeiten
skipSF:	jnb	ECR_Bits.2,skip0;SPP-FIFO-Transfer prüfen
	call	SppXfer
skip0:	jnb	ECR_Bits.3,skip1;ECP-FIFO-Transfer prüfen
	call	EcpXfer
;USB-Interrupts abarbeiten
skip1:	LDX	UsbIrq		;USB-Interrupts prüfen
	jz	nu5		;Kurzschluss-Sprung
	STX			;alle Anforderungen löschen
	mov	IntReq,a	;bitadressierbar machen
	jnb	IntReq.0,nu0	;SUDAV-Anforderung?
;	STX	SudPtrCtl,1
	clr	a		;lang (Blinken mit 2 Hz)
	call	StartLed
	call	HandleSUD	;liefert CY=1 für Ep0Stall
u31ok:	mov	a,#40h		;HSNAK setzen
	rlc	a		;EP0STALL einsetzen
	STX	EP0CS
nu0:
	jnb	IntReq.1,nu1	;SOF-Anforderung?
	jnb	DarkBlue,nu1a
p2Led2:	setb	Led2		;bei jedem (Mikro)Frame blaue LED (erst mal) AUS
nu1a:	LDX	UsbFrameL
	CJE	a,FrameCnt,nu1	;Nur ganze Rahmen mitzählen!
	mov	FrameCnt,a
	call	On1ms
nu1:
	jnb	IntReq.3,nu3	;Einschlaf-Anforderung?
	;Irgendwie kommt es beim Busaufzählen zu 2 Einschlaf-Aufforderungen,
	;die zum "Defekt" des USB-Gerätes führen.
	;Das Abwarten von SET CONFIURATION reicht.
	jnb	Configuration.0,nu3	;Ignorieren solange unkonfiguriert
	call	HandleUsbSleep
nu3:
	jnb	IntReq.4,nu4	;USB-Reset?
	;ohne Behandlung folgt Absturz beim Booten mit angestecktem Gerät?
	mov	Configuration,#0
p4Led:	setb	Led		;gelbe LED aus
	setb	Led2State	;blaue LED aus
	call	ResTogAll
nu4:
	jnb	IntReq.5,nu5
	setb	HighSpeed
	clr	Led2State	;blaue LED ein
nu5:
;Endpoints abarbeiten
	mov	IntReq,Ep2468Stat	;bitadressierbar machen
;Auswertung des OUT-Bulk-Transfers
;AutoPtr1: Ep2FifoBuf-Zeiger, ep2lh:ep2ll=Länge (DJNZ-aufbereitet)
;AutoPtr2: Ep6FifoBuf-Zeiger
;R3=Befehlskode
;R4=Daten (bei IN-Befehl A)
	jb	IntReq.0,nep2	;EP2E: Wenn EP2 nicht leer...
	jb	IntReq.5,nep2	;EP6F: Wenn EP6 nicht voll...
	LDX	EP2BCH		;Anzahl der Bytes des Bulk-Transfers
	mov	ep2lh,a
	LDX	NEXT		;EP2BCL
	mov	ep2ll,a
	MOVW	AutoPtr1,Ep2FifoBuf
	MOVW	AutoPtr2,Ep6FifoBuf
	call	ProcessInOut
	mov	a,AutoPtrL2
	subb	a,#LOW(EP6FifoBuf)
	mov	r1,a
	mov	a,AutoPtrH2
	subb	a,#HIGH(EP6FifoBuf)
	mov	r0,a
	orl	a,r1
	jz	NoIn
	mov	a,r0
	STX	EP6BCH
	mov	a,r1
	SyncDelay
	STX	NEXT		;EP6BCL: Bulk-In "anschubsen"
	SyncDelay
NoIn:	STX	OutPktEnd,82h	;Bulk-Out-Puffer zurück zur SIE (nicht zum GPIF)
nep2:
IF Gpif=0
	jb	IntReq.2,nep4	;EP4E
	call	UniXfer		;(Nur) ein Byte zum Drucker übertragen
nep4:
ELSE
	mov	IntReq,EP24FifoFlgs
	jb	IntReq.5,nf4	;EP4EF
	mov	a,GpifTrig
	jnb	ACC.7,nf4	;kann nicht starten
	;STX	IFConfig,11000010b	;30 MHz reichen dicke
	call	StartLed2
	STX	GpifTcB0,1	;GPIF freigeben bei jeder Art Blockade
	mov	GpifTrig,#1
	;SyncDelay
f4:	;mov	a,GpifTrig
	;jnb	ACC.7,f4	;kann nicht stoppen
	;STX	IFConfig,11000000b
nf4:
ENDIF
	jmp	mainloop

PatchCode:
;Für Revision 2 Patches bei LED-Zugriffen vornehmen
	setb	Rev2
	STORX	p0Led+1,Ledr2	;Bitadresse
	STORX	p1Led+1
	STORX	p2Led+1
	STORX	p3Led+1
	STORX	p4Led+1
	STORX	p0Led2+1,Led2r2
	STORX	p2Led2+1
	ret
	
;USB-Deskriptoren
EVEN
;********************************************
;** Antwort auf Get Descriptor: Device (1) **
;********************************************
;Geräte-Beschreiber
DeviceDescriptor:
	db	18	;bLength
	db	1	;bDescriptorType, 1=DEVICE
	DWI	200h	;bcdUSB 2.0
	db	0	;bDeviceClass		Multifunktion
	db	0	;bDeviceSubClass
	db	0	;bDeviceProtocol
	db	64	;bMaxPacketSize0
IF MyID
	DWI	16C0h	;idVendor		Voti
	DWI	06B3h	;idProduct		h#s Sub-ID
 IF Interfaces > 1
	DWI	4621h	;bcdDevice		USB2LPT, USB2LPT2
 ELSE
	DWI	4620h	;bcdDevice
 ENDIF
ELSE
	DWI	04B4h
	DWI	8613h	;idProduct		FX2
	DWI	4621h	;bcdDevice
ENDIF
	db	1	;iManufacturer (1)
	db	2	;iProduct (2)
	db	0	;iSerialNumber		keine Seriennummer!
	db	1	;bNumConfigurations	keine Wahl von Stromaufnahmen
;Geräte-Qualifizierer (gleich für beide Geschwindigkeiten)
EVEN
DeviceQualifier:
	db	10	;bLength
	db	6	;bDescriptorType	Qualifizierer
	DWI	200h	;bcdUSB			USB 2.0
	db	0	;bDeviceClass		Multifunktion
	db	0	;bDeviceSubClass
	db	0	;bDeviceProtocol
	db	64	;bMaxPacketSize0
	db	1	;bNumConfigurations	keine Wahl von Stromaufnahmen
	db	0	;bReserved

IF Interfaces > 2
EVEN
HidReportDesc:
;Der erste Teil ist kompatibel zu Low-Speed-USB2LPT
	db	6,0,0FFh	;G Usage Page (Vendor defined)
	db	9,1		;L Usage (Vendor Usage 1)
	db	0A1h,1		;M Collection (Application)
	db	15h,0		;G  Logical Minimum (0)
	db	26h,0FFh,0	;G  Logical Maximum (255)
	db	75h,8		;G  Report Size (8 bits)
 _nb_ SET 0
 REPT 7
 _nb_ SET _nb_+1
	db	85h,_nb_	;G  Report ID (1..7)
	db	95h,_nb_	;G  Report Count (1..7 Byte)
	db	9,_nb_		;L  Usage (1..7) = Microcode or Result
	db	0b2h,2,1	;M  Feature (Var,Buf)
 ENDM
;Diesen Teil mit den 64-Byte-Reports gibt es nur beim Full/High-Speed-USB2LPT
	db	85h,8		;G  Report ID (8)
	db	5,1		;G  Usage Page (Generic Desktop)
	db	9,3Ah		;L  Usage (Counted Buffer)
	db	0A1h,2		;M  Collection (Logical)
	db	95h,1		;G   Report Count (1 Byte)
	db	9,3Bh		;L   Usage (Byte Count)
	db	91h,2		;M   Output (Var)
	db	9,3Bh		;L   Usage (Byte Count)
	db	81h,2		;M   Input (Var)
	db	95h,62		;G   Report Count (62 Byte)
	db	6,0,0FFh	;G   Usage Page (Vendor defined)
	db	9,8		;L   Usage (8) = Microcode
	db	92h,2,1		;M   Output (Var,Buf)
	db	9,9		;L   Usage (9) = Result
	db	82h,2,1		;M   Input (Var,Buf)
	db	0C0h		;M  End Collection
	db	0C0h		;M End Collection
HRDE:
ENDIF

EVEN
;********************************************
;** Antwort auf Get Descriptor: Config (2) **
;********************************************
ConfigDescFS:	;12-Mbit/s-Version
;Konfigurations-Beschreiber 0
	db	9	;bLength
	db	2	;bDescriptorType	2=CONFIG
	DWI	CfgEFS-ConfigDescFS	;wTotalLength
	db	Interfaces	;bNumInterfaces
	db	1	;bConfigurationValue (willkürliche Nummer dieser K.)
	db	0	;iConfiguration (3)
	db	80h	;bmAttributes (Busversorgt, kein Aufwecken)
	db	100/2	;MaxPower (in 2 Milliampere)	100 mA
;Interface-Beschreiber 0, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	0	;bInterfaceNumber
	db	0	;bAlternateSetting
IF Interfaces > 1
	db	2	;bNumEndpoints
ELSE
	db	3	;bNumEndpoints
ENDIF
	db	-1	;bInterfaceClass	hersteller-spezifisch
	db	0	;bInterfaceSubClass	(passt in keine Klasse)
	db	0	;bInterfaceProtocol
	db	0	;iInterface (4)
;Enden-Beschreiber C0I0A0:Bulk EP2OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	2	;bEndpointAddress	EP2OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
;Enden-Beschreiber C0I0A0:Bulk EP6IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	86h	;bEndpointAddress	EP6IN
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
IF Interfaces = 1
;Enden-Beschreiber C0I0A0:Bulk EP4OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
ENDIF
IF Interfaces > 1
 _as_ SET 0	;Drei Alternativen erzeugen lassen: Uni, BiDi, Dot4
 _ne_ SET 1	;Erste Alternative (Uni) mit einem Ende, BiDi und Dot4 mit zwei
 REPT 3
;Interface-Beschreiber 1, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	1	;bInterfaceNumber
	db	_as_	;bAlternateSetting
	db	_ne_	;bNumEndpoints
	db	7	;bInterfaceClass	Drucker
	db	1	;bInterfaceSubClass	Drucker
	db	_as_+1	;bInterfaceProtocol	Uni/BiDi/Dot4
	db	_as_+5	;iInterface		(5), (6), (7)
;Enden-Beschreiber C0I1A?:Bulk EP4OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
  IF _ne_ = 2
;Enden-Beschreiber C0I1A?:Bulk EP8IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	88h	;bEndpointAddress	EP8IN
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
  ENDIF
  _as_ SET _as_+1
  _ne_ SET 2
 ENDM
ENDIF
IF Interfaces > 2
;Interface-Beschreiber 2, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	2	;bInterfaceNumber
	db	0	;bAlternateSetting
	db	2	;bNumEndpoints
	db	3	;bInterfaceClass	HID
	db	0	;bInterfaceSubClass
	db	0	;bInterfaceProtocol
	db	8	;iInterface
;HID-Beschreiber
HidDesc:
	db	9	;bLength
	db	21h	;HID
	DWI	101h	;BCD representation of HID version
	db	0	;target country code
	db	1	;number of HID Report Descriptor infos to follow
	db	22h	;descriptor type: report
	DWI	HRDE-HidReportDesc	;total length of report descriptor 
;Enden-Beschreiber C0I2A0:Interrupt EP1IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	81h	;bEndpointAddress	EP1IN
	db	3	;bmAttributes		3=Interrupt
	DWI	64	;wMaxPacketSize
	db	2	;bInterval		2 ms
;Enden-Beschreiber C0I2A0:Interrupt EP1OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	01h	;bEndpointAddress	EP1OUT
	db	3	;bmAttributes		3=Interrupt
	DWI	64	;wMaxPacketSize
	db	2	;bInterval		2 ms
ENDIF
CfgEFS:

EVEN
ConfigDescHS:	;480-Mbit/s-Version
;Konfigurations-Beschreiber 0
	db	9	;bLength
	db	2	;bDescriptorType	2=CONFIG
	DWI	CfgEHS-ConfigDescHS	;wTotalLength
	db	Interfaces	;bNumInterfaces
	db	1	;bConfigurationValue (willkürliche Nummer dieser K.)
	db	3	;iConfiguration (3)
	db	80h	;bmAttributes (Busversorgt, kein Aufwecken)
	db	100/2	;MaxPower (in 2 Milliampere)
;Interface-Beschreiber 0, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	0	;bInterfaceNumber
	db	0	;bAlternateSetting
IF Interfaces > 1
	db	2	;bNumEndpoints
ELSE
	db	3	;bNumEndpoints
ENDIF
	db	-1	;bInterfaceClass	hersteller-spezifisch
	db	0	;bInterfaceSubClass
	db	0	;bInterfaceProtocol
	db	4	;iInterface (4)
;Enden-Beschreiber C0I0A0:Bulk EP2OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	2	;bEndpointAddress	EP2OUT
	db	2	;bmAttributes		2=BULK
	DWI	512	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
;Enden-Beschreiber C0I0A0:Bulk EP6IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	86h	;bEndpointAddress	EP6IN
	db	2	;bmAttributes		2=BULK
	DWI	512	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
IF Interfaces = 1
;Enden-Beschreiber C0I0A0:Bulk EP4OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	512	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
ENDIF
IF Interfaces > 1
 _as_ SET 0	;Drei Alternativen erzeugen lassen: Uni, BiDi, Dot4
 _ne_ SET 1	;Erste Alternative (Uni) mit einem Ende, BiDi und Dot4 mit zwei
 REPT 3
;Interface-Beschreiber 1, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	1	;bInterfaceNumber
	db	_as_	;bAlternateSetting
	db	_ne_	;bNumEndpoints
	db	7	;bInterfaceClass	Drucker
	db	1	;bInterfaceSubClass	Drucker
	db	_as_+1	;bInterfaceProtocol	Uni/BiDi/Dot4
	db	_as_+5	;iInterface		(5), (6), (7)
;Enden-Beschreiber C0I1A?:Bulk EP4OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	512	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
  IF _ne_ = 2
;Enden-Beschreiber C0I1A?:Bulk EP8IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	88h	;bEndpointAddress	EP8IN
	db	2	;bmAttributes		2=BULK
	DWI	512	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
  ENDIF
  _as_ SET _as_+1
  _ne_ SET 2
 ENDM
ENDIF
IF Interfaces > 2
;Interface-Beschreiber 2, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	2	;bInterfaceNumber
	db	0	;bAlternateSetting
	db	2	;bNumEndpoints
	db	3	;bInterfaceClass	HID
	db	0	;bInterfaceSubClass
	db	0	;bInterfaceProtocol
	db	8	;iInterface
;HID-Beschreiber
	db	9	;bLength
	db	21h	;HID
	DWI	101h	;BCD representation of HID version
	db	0	;target country code
	db	1	;number of HID Report Descriptor infos to follow
	db	22h	;descriptor type: report
	DWI	HRDE-HidReportDesc	;total length of report descriptor 
;Enden-Beschreiber C0I2A0:Interrupt EP1IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	81h	;bEndpointAddress	EP1IN
	db	3	;bmAttributes		3=Interrupt
	DWI	64	;wMaxPacketSize
	db	1	;bInterval		1 ms
;Enden-Beschreiber C0I2A0:Interrupt EP1OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	01h	;bEndpointAddress	EP1OUT
	db	3	;bmAttributes		3=Interrupt
	DWI	64	;wMaxPacketSize
	db	1	;bInterval		1 ms
ENDIF
CfgEHS:

EVEN
;********************************************
;** Antwort auf Get Descriptor: String (3) **
;********************************************
StringDesc:
Str0:	db	Str1-$,3
	DWI	0407h		;Deutsch (Deutschland)
Str1:	db	Str2-$,3
	DWI	'h','a','f','t','m','a','n','n','#'
	DWI	's','o','f','t','w','a','r','e'
Str2:	db	Str3-$,3
	DWI	'h','#','s',' ','U','S','B','2','L','P','T','2',' ','-',' '
	DWI	'd','e','r',' ','U','m','s','e','t','z','e','r',',',' '
	DWI	'd','e','r',' ','w','i','r','k','l','i','c','h',' '
	DWI	'f','u','n','k','t','i','o','n','i','e','r','t','!'
Str3:	db	Str4-$,3
	DWI	'B','u','s','v','e','r','s','o','r','g','t',',',' '
	DWI	'k','e','i','n',' ','A','u','f','w','e','c','k','e','n'
Str4:	db	Str5-$,3
	DWI	'D','i','r','e','k','t','z','u','g','r','i','f','f'
Str5:
IF Interfaces > 1
	db	Str6-$,3
	DWI	'U','n','i','d','i','r','e','k','t','i','o','n','a','l'
Str6:	db	Str7-$,3
	DWI	'B','i','d','i','r','e','k','t','i','o','n','a','l'
Str7:	db	Str8-$,3
	DWI	'I','E','E','E','1','2','8','4','.','4',' '
	DWI	'(','D','o','t','4',')'
ENDIF
Str8:
IF Interfaces > 2
	db	Str9-$,3
	DWI	'T','r','e','i','b','e','r','l','o','s','e','r',' '
	DWI	'U','s','e','r','-','M','o','d','e','-'
	DWI	'Z','u','g','a','n','g',' '
	DWI	'f',252,'r',' ','W','i','n','6','4',' '
	DWI	'(','u','m','g','e','h','t',' '
	DWI	'T','r','e','i','b','e','r'
	DWI	'z','e','r','t','i','f','i','z','i','e','r','u','n','g','s'
	DWI	'z','w','a','n','g',')'
ENDIF
Str9:	db	0	;Endekennung

AnswerBuf:		;Platz für EP0InBuf (In-Bytes)
	ds	8

IF Gpif

SetWaveform:
;PE: AutoPtr1 = Quelle, AutoPtr2 = Ziel (durch 32 teilbar)
	mov	r0,#LOW(XAutoDat1)
	mov	r1,#LOW(XAutoDat2)
swf:	movx	a,@r0
	movx	@r1,a
	movx	a,@r0
	movx	@r1,a
	movx	a,@r0
	movx	@r1,a
	movx	a,@r0
	movx	@r1,a
	mov	a,AutoPtrL2
	add	a,#4	;die anderen Bytes ungenutzt lassen
	mov	AutoPtrL2,a
	anl	a,#1Fh
	jnz	swf	;Vier Runden
	
memcpy:
	mov	r0,#LOW(XAutoDat1)
	mov	r1,#LOW(XAutoDat2)
cpy:	LDX
	STX1
	djnz	r4,cpy
	ret

;GPIF-Wellenformen (max. 4 Zustände erforderlich)

STB_HI	equ	1 shl STB
AF_HI	equ	1 shl AF
SEL_HI	equ	1 shl SEL
ACK_HI	equ	11o shl ACK
BSY_HI	equ	11o shl BSY

SPP_W:	;Normales Schreiben
	db	071o	;THEN Idle ELSE Next
	db	4*48
	db	4*48
	db	4*48
	db	070o	;THEN 0 ELSE 0
	db	0,0,0
	
	db	001b	;IF
	db	010b	;4 µs Daten anlegen
	db	010b	;4 µs Strobe lo
	db	010b	;4 µs Daten nachhalten
	db	101b	;NEXT, JUMP
	db	0,0,0
	
	db	AF_HI+STB_HI
	db	AF_HI+STB_HI
	db	AF_HI
	db	AF_HI+STB_HI
	db	AF_HI+STB_HI
	db	0,0,0
	
	db	060o+BSY;FIFO EMPTY or PRINTER BUSY
	db	0,0,0
	db	060o+BSY;FIFO EMPTY or PRINTER BUSY
	db	0,0,0

BM_R:	;Byte Mode (Rücklesen)
	db	001o
	db	021o
	db	1*30
	db	077o
	
	db	001b	;AF lo, warten bis ACK lo
	db	011b	;Daten, AF hi, warten bis ACK hi
	db	000b	;1 µs STB lo
	db	001b	;STB hi, JUMP
	
	db	SEL_HI+STB_HI
	db	SEL_HI+AF_HI+STB_HI
	db	SEL_HI+AF_HI
	db	SEL_HI+AF_HI+STB_HI
	
	db	ACK_HI
	db	ACK_HI
	db	0,0

EPP_WA:	;EPP-Modus: Schreiben: Adresse
	db	010o
	db	012o
	db	077o
	db	0FFh
	
	db	011b	;SEL lo, warten bis BSY hi
	db	011b	;SEL hi, warten bis BSY lo
	db	101b	;NEXT, JUMP
	db	0FFh
	
	db	AF_HI	
	db	SEL_HI+AF_HI
	db	SEL_HI+AF_HI
	db	0FFh
	
	db	BSY_HI
	db	BSY_HI
	db	0
	db	0FFh
	
EPP_WD:	;EPP-Modus: Schreiben: Daten
	db	010o
	db	012o
	db	077o
	db	0FFh

	db	011b	;AF lo, warten bis BSY hi
	db	011b	;AF hi, warten bis BSY lo
	db	101b	;NEXT, JUMP
	db	0FFh

	db	SEL_HI
	db	SEL_HI+AF_HI
	db	SEL_HI+AF_HI
	db	0FFh

	db	BSY_HI
	db	BSY_HI
	db	0
	db	0FFh

EPP_RA:	;EPP-Modus: Lesen: Adresse
	db	010o
	db	017o
	db	0FFh
	db	0FFh

	db	001b	;SEL lo, warten bis BSY hi
	db	011b	;SEL hi, warten bis BSY lo
	db	0FFh
	db	0FFh

	db	AF_HI+STB_HI
	db	SEL_HI+AF_HI+STB_HI
	db	0FFh
	db	0FFh

	db	BSY_HI
	db	BSY_HI
	db	0FFh
	db	0FFh

EPP_RD:	;EPP-Modus: Lesen: Daten
	db	010o
	db	017o
	db	0FFh
	db	0FFh

	db	001b	;AF lo, warten bis BSY hi
	db	011b	;AF hi, warten bis BSY lo
	db	0FFh
	db	0FFh

	db	SEL_HI+STB_HI
	db	SEL_HI+AF_HI+STB_HI
	db	0FFh
	db	0FFh

	db	BSY_HI
	db	BSY_HI
	db	0FFh
	db	0FFh

ECP_WA:	;ECP-Modus: Schreiben: Adresse ("Kommando")
	db	010o
	db	012o
	db	077o
	db	0FFh

	db	011b	;STB lo, warten bis BSY hi
	db	011b	;STB hi, warten bis BSY lo
	db	101b	;NEXT, JUMP
	db	0FFh

	db	SEL_HI
	db	SEL_HI+STB_HI
	db	SEL_HI+STB_HI
	db	0FFh

	db	BSY_HI
	db	BSY_HI
	db	0
	db	0FFh

ECP_WD:	;ECP-Modus: Schreiben: Daten
	db	010o
	db	012o
	db	077o
	db	0FFh

	db	011b	;STB lo, warten bis BSY hi
	db	011b	;STB hi, warten bis BSY lo
	db	101b	;NEXT, JUMP
	db	0FFh

	db	SEL_HI+AF_HI
	db	SEL_HI+AF_HI+STB_HI
	db	SEL_HI+AF_HI+STB_HI
	db	0FFh

	db	BSY_HI
	db	BSY_HI
	db	0
	db	0FFh

ECP_R:	;ECP-Modus: Lesen: Adresse oder Daten (je nach BSY)
	db	001o
	db	071o
	db	0FFh
	db	0FFh

	db	001b	;AF hi, warten bis ACK lo
	db	011b	;AF lo, warten bis ACK hi
	db	0FFh
	db	0FFh

	db	SEL_HI+AF_HI+STB_HI
	db	SEL_HI+STB_HI
	db	0FFh
	db	0FFh

	db	ACK_HI
	db	ACK_HI
	db	0FFh
	db	0FFh
ENDIF
END

;"Richtige" Interrupt-Simulation (ACK) erfordert Schaltungsänderung!
;Und ist schwierig am PC zu simulieren (will sagen, ich weiß nicht, wie)
;Ganz zu schweigen von ECP-DMA!
;USB-mäßig wäre es natürlich ein simpler 1-Byte-USB-INT-Transfer...

Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded