;   usbdux_firmware.asm
;   Copyright (C) 2010,2015 Bernd Porr, mail@berndporr.me.uk
;   For usbduxsigma.c 0.5+
;
;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation; either version 2 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;
;
; Firmware: usbduxsigma_firmware.asm for usbduxsigma.c
; Description: University of Stirling USB DAQ & INCITE Technology Limited
; Devices: [ITL] USB-DUX-SIGMA (usbduxsigma.ko)
; Author: Bernd Porr <mail@berndporr.me.uk>
; Updated: 20 Jul 2015
; Status: testing
;
;;;
;;;
;;;
	
	.inc	fx2-include.asm

;;; a couple of flags in high memory
	.equ	CMD_FLAG,80h	; flag for the next in transfer
	.equ	PWMFLAG,81h	; PWM on or off?
	.equ	MAXSMPL,82H	; maximum number of samples, n channellist
	.equ	MUXSG0,83H	; content of the MUXSG0 register
	.equ	INTERVAL,88h	; uframe/frame interval
	.equ	INTCTR,89h	; interval counter
	.equ	DABUFFER,0F0h	; buffer with DA values

;;; in precious low memory but accessible within one clock cycle
	.equ	DPTRL,70H
	.equ	DPTRH,71h
	.equ	ASYNC_ON,72h
	.equ	SMPLCTR,73h

;;; actual code
	.org	0000h		; after reset the processor starts here
	ljmp	main		; jump to the main loop

	.org	0003h
	ljmp	isr0		; external interrupt 0: /DRY

	.org	0043h		; the IRQ2-vector
	ljmp	jmptbl		; irq service-routine

	.org	0100h		; start of the jump table

jmptbl:	ljmp	sudav_isr
	nop
	ljmp	sof_isr
	nop
	ljmp	sutok_isr
	nop
	ljmp	suspend_isr
	nop
	ljmp	usbreset_isr
	nop
	ljmp	hispeed_isr
	nop
	ljmp	ep0ack_isr
	nop
	ljmp	spare_isr
	nop
	ljmp	ep0in_isr
	nop
	ljmp	ep0out_isr
	nop
	ljmp	ep1in_isr
	nop
	ljmp	ep1out_isr
	nop
	ljmp	ep2_isr
	nop
	ljmp	ep4_isr
	nop
	ljmp	ep6_isr
	nop
	ljmp	ep8_isr
	nop
	ljmp	ibn_isr
	nop
	ljmp	spare_isr
	nop
	ljmp	ep0ping_isr
	nop
	ljmp	ep1ping_isr
	nop
	ljmp	ep2ping_isr
	nop
	ljmp	ep4ping_isr
	nop
	ljmp	ep6ping_isr
	nop
	ljmp	ep8ping_isr
	nop
	ljmp	errlimit_isr
	nop
	ljmp	spare_isr
	nop
	ljmp	spare_isr
	nop
	ljmp	spare_isr
	nop
	ljmp	ep2isoerr_isr
	nop
	ljmp	ep4isoerr_isr
	nop
	ljmp	ep6isoerr_isr
	nop
	ljmp	ep8isoerr_isr

	
	;; dummy isr
sudav_isr:	
sutok_isr:	
suspend_isr:	
usbreset_isr:	
hispeed_isr:	
ep0ack_isr:	
spare_isr:	
ep0in_isr:	
ep0out_isr:	
ibn_isr:	
ep0ping_isr:	
ep1ping_isr:	
ep2ping_isr:	
ep4ping_isr:	
ep6ping_isr:	
ep8ping_isr:	
errlimit_isr:	
ep2isoerr_isr:	
ep4isoerr_isr:	
ep6isoerr_isr:	
ep8isoerr_isr:
ep6_isr:
ep2_isr:
ep4_isr:	

	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw

	;; clear the USB2 irq bit and return
	mov	a,EXIF
	clr	acc.4
	mov	EXIF,a

	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps
	
	reti


ep1in_isr:	
	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw
		
	mov	dptr,#0E7C0h	; EP1in
	mov	a,IOB		; get DIO D
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,IOC		; get DIO C
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,IOD		; get DIO B
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,#0		; just zero
	movx	@dptr,a		; pad it up

	;; clear INT2
	mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request
	clr	acc.4
	mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable

	mov	DPTR,#EPIRQ	; 
	mov	a,#00000100b	; clear the ep1in
	movx	@DPTR,a

	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps
	reti



;;; this is triggered when DRY goes low
isr0:	
	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw
	push	00h		; R0
	push	01h		; R1
	push	02h		; R2
	push	03h		; R3
	push	04h		; R4
	push	05h		; R5
	push	06h		; R6
	push	07h		; R7

	mov	a,ASYNC_ON
	jz	noepsubmit

	mov	DPS,#0
	mov	dpl,DPTRL
	mov	dph,DPTRH

	lcall	readADCch	; read one channel

	mov	DPTRL,dpl
	mov	DPTRH,dph

	mov	a,SMPLCTR
	dec	a
	mov	SMPLCTR,a
	jnz	noepsubmit

	mov	ASYNC_ON,#0

	clr	IOA.7		; START = 0
	
	;; arm the endpoint and send off the data
	mov	DPTR,#EP6BCH	; byte count H
	mov	a,#0		; is zero
	lcall	syncdelaywr	; wait until we can write again
	
	mov	r0,#MAXSMPL	; number of samples to transmit
	mov	a,@r0		; get them
	rl	a		; a=a*2
	rl	a		; a=a*2
	add	a,#4		; four bytes for DIO
	mov	DPTR,#EP6BCL	; byte count L
	lcall	syncdelaywr	; wait until we can write again

noepsubmit:
	pop	07h
	pop	06h
	pop	05h
	pop	04h		; R4
	pop	03h		; R3
	pop	02h		; R2
	pop	01h		; R1
	pop	00h		; R0
	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps

	reti

	
		
;;; main program
;;; basically only initialises the processor and
;;; then engages in an endless loop
main:
	mov	DPTR,#CPUCS	; CPU control register
	mov	a,#00010000b	; 48Mhz
	lcall	syncdelaywr

        mov     dptr,#REVCTL
        mov     a,#00000011b    ; allows skip
        lcall   syncdelaywr

	mov	dptr,#INTSETUP	; IRQ setup register
	mov	a,#08h		; enable autovector
	lcall	syncdelaywr

	mov	dptr,#PORTCCFG
	mov	a,#0
	lcall	syncdelaywr

	mov	IP,#01H		; int0 has highest interrupt priority
	mov	EIP,#0		; all USB interrupts have low priority

	lcall	initAD		; init the ports to the converters

	lcall	initeps		; init the isochronous data-transfer

;;; main loop, rest is done as interrupts
mloop2:	nop

;;; pwm
	mov	r0,#PWMFLAG	; pwm on?
	mov	a,@r0		; get info
	jz	mloop2		; it's off

	mov	a,GPIFTRIG	; GPIF status
	anl	a,#80h		; done bit
	jz	mloop2		; GPIF still busy

        mov     a,#01h		; WR,EP4, 01 = EP4
        mov     GPIFTRIG,a	; restart it

	sjmp	mloop2		; loop for ever


;;; initialise the ports for the AD-converter
initAD:
	mov	r0,#MAXSMPL	; length of channellist
	mov	@r0,#0		; we don't want to accumlate samples

	mov	ASYNC_ON,#0	; async enable

	mov	r0,#DABUFFER
	mov	@r0,#0

	mov	OEA,#11100000b	; PortA7,A6,A5 Outputs
	mov	IOA,#01100000b	; /CS = 1 and START = 0
	mov	dptr,#IFCONFIG	; switch on clock on IFCLK pin
	mov	a,#10100000b	; gpif, 30MHz, internal IFCLK -> 15MHz for AD
	lcall	syncdelaywr

	mov	SCON0,#013H	; ser rec en, TX/RX: stop, 48/12MHz=4MHz clock
	
	mov	dptr,#PORTECFG
	mov	a,#00001000b	; special function for port E: RXD0OUT
	lcall	syncdelaywr

	ret


;;; send a byte via SPI
;;; content in a, dptr1 is changed
;;; the lookup is done in dptr1 so that the normal dptr is not affected
;;; important: /cs needs to be reset to 1 by the caller: IOA.5
sendSPI:
	inc	DPS
	
	;; bit reverse
	mov	dptr,#swap_lut	; lookup table
	movc 	a,@a+dptr	; reverse bits

	;; clear interrupt flag, is used to detect
	;; successful transmission
	clr	SCON0.1		; clear interrupt flag

	;; start transmission by writing the byte
	;; in the transmit buffer
	mov	SBUF0,a		; start transmission

	;; wait for the end of the transmission
sendSPIwait:
	mov	a,SCON0		; get transmission status
	jnb     ACC.1,sendSPIwait	; loop until transmitted

	inc	DPS
	
	ret



	
;;; receive a byte via SPI
;;; content in a, dptr is changed
;;; the lookup is done in dptr1 so that the normal dptr is not affected
;;; important: the /CS needs to be set to 1 by the caller via "setb IOA.5"
recSPI:
	inc	DPS
	
	clr	IOA.5		; /cs to 0	

	;; clearning the RI bit starts reception of data
	clr	SCON0.0

recSPIwait:
	;; RI goes back to 1 after the reception of the 8 bits
	mov	a,SCON0		; get receive status
	jnb	ACC.0,recSPIwait; loop until all bits received

	;; read the byte from the buffer
	mov	a,SBUF0		; get byte
	
	;; lookup: reverse the bits
	mov	dptr,#swap_lut	; lookup table
	movc 	a,@a+dptr	; reverse the bits

	inc	DPS
	
	ret



	
;;; reads a register
;;; register address in a
;;; returns value in a
registerRead:
	anl	a,#00001111b	; mask out the index to the register
	orl	a,#01000000b	; 010xxxxx indicates register read
	clr	IOA.5		; ADC /cs to 0
	lcall	sendSPI		; send the command over
	lcall	recSPI		; read the contents back
	setb	IOA.5		; ADC /cs to 1
	ret



;;; writes to a register
;;; register address in a
;;; value in r0
registerWrite:
	push	acc
	anl	a,#00001111b	; mask out the index to the register
	orl	a,#01100000b	; 011xxxxx indicates register write

	clr	IOA.5		; ADC /cs to 0	

	lcall	sendSPI		;
	mov	a,r0
	lcall	sendSPI

	setb	IOA.5		; ADC /cs to 1
	pop	acc

	lcall	registerRead	; check if the data has arrived in the ADC
	mov	0f0h,r0		; register B
	cjne	a,0f0h,registerWrite ; something went wrong, try again
	
	ret



;;; initilise the endpoints
initeps:
	mov	dptr,#FIFORESET
	mov	a,#80H		
	movx	@dptr,a		; reset all fifos
	mov	a,#2	
	movx	@dptr,a		; 
	mov	a,#4		
	movx	@dptr,a		; 
	mov	a,#6		
	movx	@dptr,a		; 
	mov	a,#8		
	movx	@dptr,a		; 
	mov	a,#0		
	movx	@dptr,a		; normal operat
	
	mov	DPTR,#EP2CFG
	mov	a,#10010010b	; valid, out, double buff, iso
	movx	@DPTR,a

	mov	dptr,#EP2FIFOCFG
	mov	a,#00000000b	; manual
	movx	@dptr,a

	mov	dptr,#EP2BCL	; "arm" it
	mov	a,#00h
	movx	@DPTR,a		; can receive data
	lcall	syncdelay	; wait to sync
	movx	@DPTR,a		; can receive data
	lcall	syncdelay	; wait to sync
	movx	@DPTR,a		; can receive data
	lcall	syncdelay	; wait to sync
	
	mov	DPTR,#EP1OUTCFG
	mov	a,#10100000b	; valid
	movx	@dptr,a

	mov	dptr,#EP1OUTBC	; "arm" it
	mov	a,#00h
	movx	@DPTR,a		; can receive data
	lcall	syncdelay	; wait until we can write again
	movx	@dptr,a		; make shure its really empty
	lcall	syncdelay	; wait

	mov	DPTR,#EP6CFG	; ISO data from here to the host
	mov	a,#11010010b	; Valid
	movx	@DPTR,a		; ISO transfer, double buffering

	mov	DPTR,#EP8CFG	; EP8
	mov	a,#11100000b	; BULK data from here to the host
	movx	@DPTR,a		;

	mov	dptr,#PORTACFG
	mov	a,#1		; interrupt on pin A0
	lcall	syncdelaywr

	;; enable interrupts
	mov	dptr,#EPIE	; interrupt enable
	mov	a,#10001100b	; enable irq for ep1out,8,ep1in
	movx	@dptr,a		; do it

	mov	dptr,#EPIRQ	; clear IRQs
	mov	a,#10001100b
	movx	@dptr,a
	
        mov     DPTR,#USBIE	; USB int enables register
        mov     a,#2            ; enables SOF (1ms/125us interrupt)
        movx    @DPTR,a         ; 

	setb	TCON.0		; make INT0 edge triggered, falling edge

	mov	EIE,#00000001b	; enable INT2/USBINT in the 8051's SFR
	mov	IE,#81h		; IE, enable all interrupts and INT0

	ret


;;; Reads one ADC channel from the converter and stores
;;; the result at dptr
readADCch:
	;; reading data is done by just dropping /CS and start reading and
	;; while keeping the IN signal to the ADC inactive
	clr	IOA.5		; /cs to 0
	
	;; 1st byte: STATUS
	lcall	recSPI		; index
	movx	@dptr,a		; store the byte
	inc	dptr		; increment pointer

	;; 2nd byte: MSB
	lcall	recSPI		; data
	movx	@dptr,a
	inc	dptr

	;; 3rd byte: MSB-1
	lcall	recSPI		; data
	movx	@dptr,a
	inc	dptr

	;; 4th byte: LSB
	lcall	recSPI		; data
	movx	@dptr,a
	inc	dptr
	
	;; got all bytes
	setb	IOA.5		; /cs to 1
	
	ret

	

;;; interrupt-routine for SOF
sof_isr:
	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw
	push	00h		; R0
	push	01h		; R1
	push	02h		; R2
	push	03h		; R3
	push	04h		; R4
	push	05h		; R5
	push	06h		; R6
	push	07h		; R7

	mov	r0,#INTCTR	; interval counter
	mov	a,@r0		; get the value
	dec	a		; decrement
	mov	@r0,a		; save it again
	jz	sof_adc		; we do ADC functions
	ljmp	epfull		; we skip all adc functions
	
sof_adc:
	mov	r1,#INTERVAL	; get the interval
	mov	a,@r1		; get it
	mov	@r0,a		; save it in the counter
	mov	a,EP2468STAT
	anl	a,#20H		; full?
	jnz	epfull		; EP6-buffer is full

	mov	a,IOA		; conversion running?
	jb	ACC.7,epfull

	;; make sure that we are starting with the first channel
	mov	r0,#MUXSG0	;
	mov	a,@r0		; get config of MUXSG0
	mov	r0,a
	mov	a,#04H		; MUXSG0
	lcall	registerWrite	; this resets the channel sequence

	setb	IOA.7		; start converter, START = 1
	
	mov	dptr,#0f800h	; EP6 buffer
	mov	a,IOD		; get DIO D
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,IOC		; get DIO C
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,IOB		; get DIO B
	movx	@dptr,a		; store it
	inc	dptr		; next byte
	mov	a,#0		; just zero
	movx	@dptr,a		; pad it up
	inc	dptr		; algin along a 32 bit word
	mov	DPTRL,dpl
	mov	DPTRH,dph

	mov	r0,#MAXSMPL
	mov	a,@r0
	mov	SMPLCTR,a

	mov	ASYNC_ON,#1

epfull:
	;; do the D/A conversion
	mov	a,EP2468STAT
	anl	a,#01H		; empty
	jnz	epempty		; nothing to get

	mov	dptr,#0F000H	; EP2 fifo buffer
	lcall	dalo		; conversion

	mov	dptr,#EP2BCL	; "arm" it
	mov	a,#00h
	lcall	syncdelaywr	; wait for the rec to sync
	lcall	syncdelaywr	; wait for the rec to sync

epempty:
	mov	a,IOA		; conversion running?
	jb	ACC.7,sofend

	lcall	DAsend

sofend:	
	;; clear INT2
	mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request
	clr	acc.4
	mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable
	
	mov	DPTR,#USBIRQ	; points to the SOF
	mov	a,#2		; clear the SOF
	movx	@DPTR,a

nosof:
	pop	07h
	pop	06h
	pop	05h
	pop	04h		; R4
	pop	03h		; R3
	pop	02h		; R2
	pop	01h		; R1
	pop	00h		; R0
	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps
	reti


reset_ep8:
	;; erase all data in ep8
	mov	dptr,#FIFORESET
	mov	a,#80H		; NAK
	lcall	syncdelaywr
	mov	dptr,#FIFORESET
	mov	a,#8		; reset EP8
	lcall	syncdelaywr
	mov	dptr,#FIFORESET
	mov	a,#0		; normal operation
	lcall	syncdelaywr
	ret


reset_ep6:
	;; throw out old data
	mov	dptr,#FIFORESET
	mov	a,#80H		; NAK
	lcall	syncdelaywr
	mov	dptr,#FIFORESET
	mov	a,#6		; reset EP6
	lcall	syncdelaywr
	mov	dptr,#FIFORESET
	mov	a,#0		; normal operation
	lcall	syncdelaywr
	ret


;;; configure the ADC converter
;;; the dptr points to the init data:
;;; CONFIG 0,1,3,4,5,6
;;; note that CONFIG2 is omitted
configADC:	
	clr	IOA.7		; stops ADC: START line of ADC = L
	setb	IOA.5		; ADC /cs to 1

	;; just in case something has gone wrong
	nop
	nop
	nop

	mov	a,#11000000b	; reset	the ADC
	clr	IOA.5		; ADC /cs to 0	
	lcall	sendSPI
	setb	IOA.5		; ADC /cs to 1	

	movx	a,@dptr		;
	inc	dptr
	mov	r0,a
	mov	a,#00H		; CONFIG0
	lcall	registerWrite

	movx	a,@dptr		;
	inc	dptr
	mov	r0,a
	mov	a,#01H		; CONFIG1
	lcall	registerWrite

	movx	a,@dptr		;
	inc	dptr
	mov	r0,a
	mov	a,#03H		; MUXDIF
	lcall	registerWrite

	movx	a,@dptr		;
	inc	dptr
	mov	r0,#MUXSG0
	mov	@r0,a		; store it for reset purposes
	mov	r0,a
	mov	a,#04H		; MUXSG0
	lcall	registerWrite
	
	movx	a,@dptr		;
	inc	dptr
	mov	r0,a
	mov	a,#05H		; MUXSG1
	lcall	registerWrite
	
	movx	a,@dptr		;
	inc	dptr
	mov	r0,a
	mov	a,#06H		; SYSRED
	lcall	registerWrite

	ret

	
;;; interrupt-routine for ep1out
;;; receives the channel list and other commands
ep1out_isr:
	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw
	push	00h		; R0
	push	01h		; R1
	push	02h		; R2
	push	03h		; R3
	push	04h		; R4
	push	05h		; R5
	push	06h		; R6
	push	07h		; R7

	mov	dptr,#0E780h	; FIFO buffer of EP1OUT
	movx	a,@dptr		; get the first byte
	mov	r0,#CMD_FLAG	; pointer to the command byte
	mov 	@r0,a		; store the command byte for ep8

	mov	dptr,#ep1out_jmp; jump table for the different functions
	rl	a		; multiply by 2: sizeof sjmp
	jmp	@a+dptr		; jump to the jump table
	;; jump table, corresponds to the command bytes defined
	;; in usbdux.c
ep1out_jmp:
	sjmp	startadc	; a=0
	sjmp	single_da	; a=1
	sjmp	config_digital_b; a=2
	sjmp	write_digital_b	; a=3
	sjmp	initsgADchannel	; a=4
	sjmp	nothing		; a=5
	sjmp	nothing		; a=6
	sjmp	pwm_on		; a=7
	sjmp	pwm_off		; a=8
	sjmp	startadcint	; a=9

nothing:
	ljmp	over_da

pwm_on:
	lcall	startPWM
	sjmp	over_da

pwm_off:
	lcall	stopPWM
	sjmp	over_da

initsgADchannel:
	mov	ASYNC_ON,#0
	
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT
	lcall	configADC	; configures the ADC esp sel the channel

	lcall	reset_ep8	; reset FIFO: get rid of old bytes
	;; Save new A/D data in EP8. This is the first byte
	;; the host will read during an INSN. If there are
	;; more to come they will be handled by the ISR of
	;; ep8.
	lcall	ep8_ops		; get A/D data
		
	sjmp	over_da

startadcint:
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT from 2nd byte

	movx	a,@dptr		; interval is the 1st byte
	inc	dptr		; data pointer
	sjmp	startadc2	; the other paramters as with startadc
	
;;; config AD:
;;; we write to the registers of the A/D converter
startadc:
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT from 2nd byte

	mov	a,#1		; interval is 1 here all the time
startadc2:	
	mov	r0,#INTERVAL	; set it
	mov	@r0,a
	mov	r0,#INTCTR	; the counter is also just one
	mov	@r0,a

	movx	a,@dptr		; get length of channel list
	inc	dptr
	mov	r0,#MAXSMPL
	mov	@r0,a 		; length of the channel list
	mov	SMPLCTR,a

	lcall	configADC	; configures all registers

	mov	ASYNC_ON,#1	; async enable

	lcall	reset_ep6	; reset FIFO
	
	;; load new A/D data into EP6
	;; This must be done. Otherwise the ISR is never called.
	;; The ISR is only called when data has _left_ the
	;; ep buffer here it has to be refilled.
	lcall	ep6_arm		; fill with dummy data
	
	sjmp	over_da

;;; Single DA conversion. The 2 bytes are in the FIFO buffer
single_da:
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT
	lcall	dalo		; conversion
	sjmp	over_da

;;; configure the port B as input or output (bitwise)
config_digital_b:
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT
	movx	a,@dptr		; get the second byte
	inc	dptr
	mov	OEB,a		; set the output enable bits
	movx	a,@dptr		; get the second byte
	inc	dptr
	mov	OEC,a
	movx	a,@dptr		; get the second byte
	inc	dptr
	mov	OED,a
	sjmp	over_da
	
;;; Write one byte to the external digital port B
;;; and prepare for digital read
write_digital_b:
	mov	dptr,#0e781h	; FIFO buffer of EP1OUT
	movx	a,@dptr		; command[1]
	inc	dptr
	mov	OEB,a		; output enable
	movx	a,@dptr		; command[2]
	inc	dptr
	mov	OEC,a
	movx	a,@dptr		; command[3]
	inc	dptr
	mov	OED,a 
	movx	a,@dptr		; command[4]
	inc	dptr
	mov	IOB,a		;
	movx	a,@dptr		; command[5]
	inc	dptr
	mov	IOC,a
	movx	a,@dptr		; command[6]
	inc	dptr
	mov	IOD,a

	lcall	reset_ep8	; reset FIFO of ep 8

	;; fill ep8 with new data from port B
	;; When the host requests the data it's already there.
	;; This must be so. Otherwise the ISR is not called.
	;; The ISR is only called when a packet has been delivered
	;; to the host. Thus, we need a packet here in the
	;; first instance.
	lcall	ep8_ops		; get digital data

	;; 
	;; for all commands the same
over_da:	
	mov	dptr,#EP1OUTBC
	mov	a,#00h
	lcall	syncdelaywr	; arm
	lcall	syncdelaywr	; arm
	lcall	syncdelaywr	; arm

	;; clear INT2
	mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request
	clr	acc.4
	mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable

	mov	DPTR,#EPIRQ	; 
	mov	a,#00001000b	; clear the ep1outirq
	movx	@DPTR,a

	pop	07h
	pop	06h
	pop	05h
	pop	04h		; R4
	pop	03h		; R3
	pop	02h		; R2
	pop	01h		; R1
	pop	00h		; R0
	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps
	reti


	
;;; save all DA channels from the endpoint buffer in a local buffer
dalo:
	movx	a,@dptr		; number of bytes to send out
	inc	dptr		; pointer to the first byte
	mov	r1,#DABUFFER	; buffer for DA values
	mov	@r1,a		; save it
	inc	r1		; inc pointer to local buffer
	mov	r0,a		; counter
nextDAlo:	
	movx	a,@dptr		; get the byte
	inc	dptr		; point to the high byte
	mov	@r1,a		; save it in the buffer
	inc	r1
	movx	a,@dptr		; get the channel number
	inc	dptr		; get ready for the next channel
	mov	@r1,a		; save it
	inc	r1
	djnz	r0,nextDAlo	; next channel
	ret


;;; write to the DA converter
DAsend:
	mov	r1,#DABUFFER	; buffer of the DA values
	mov	a,@r1		; get the channel count
	jz	DAret		; nothing to do
	inc	r1		; pointer to the first byte
	mov	r0,a		; counter
nextDA:	
	mov	a,@r1		; get the byte
	inc	r1		; point to the high byte
	mov	r3,a		; store in r3 for writeDA
	mov	a,@r1		; get the channel number
	inc	r1		; get ready for the next channel
	push	1		; is modified in the subroutine
	lcall	writeDA		; write value to the DAC
	pop	1		; get the pointer back
	djnz	r0,nextDA	; next channel
DAret:	
	ret



;;; D/A-conversion:
;;; channel number in a
;;; value in r3
writeDA:
	anl	a,#00000011b	; 4 channels
	mov	r1,#6		; the channel number needs to be shifted up
writeDA2:
	rl	a		; bit shift to the left
	djnz	r1,writeDA2	; do it 6 times
	orl	a,#00010000b	; update outputs after write
	mov	r2,a		; backup
	mov	a,r3		; get byte
	anl	a,#11110000b	; get the upper nibble
	mov	r1,#4		; shift it up to the upper nibble
writeDA3:
	rr	a		; shift to the upper to the lower
	djnz	r1,writeDA3
	orl	a,r2		; merge with the channel info
	clr	IOA.6		; /SYNC (/CS) of the DA to 0
	lcall	sendSPI		; send it out to the SPI
	mov	a,r3		; get data again
	anl	a,#00001111b	; get the lower nibble
	mov	r1,#4		; shift that to the upper
writeDA4:
	rl	a
	djnz	r1,writeDA4
	anl	a,#11110000b	; make sure that's empty
	lcall	sendSPI
	setb	IOA.6		; /SYNC (/CS) of the DA to 1
noDA:	ret
	


;;; arm ep6: this is just a dummy arm to get things going
ep6_arm:
	mov	DPTR,#EP6BCH	; byte count H
	mov	a,#0		; is zero
	lcall	syncdelaywr	; wait until the length has arrived
	
	mov	DPTR,#EP6BCL	; byte count L
	mov	a,#1		; is one
	lcall	syncdelaywr	; wait until the length has been proc
	ret
	


;;; converts one analog/digital channel and stores it in EP8
;;; also gets the content of the digital ports B,C and D depending on
;;; the COMMAND flag
ep8_ops:
	mov	dptr,#0fc01h	; ep8 fifo buffer
	clr	a		; high byte
	movx	@dptr,a		; set H=0
	mov	dptr,#0fc00h	; low byte
	mov	r0,#CMD_FLAG
	mov	a,@r0
	movx	@dptr,a		; save command byte

	mov	dptr,#ep8_jmp	; jump table for the different functions
	rl	a		; multiply by 2: sizeof sjmp
	jmp	@a+dptr		; jump to the jump table
	;; jump table, corresponds to the command bytes defined
	;; in usbdux.c
ep8_jmp:
	sjmp	ep8_err		; a=0, err
	sjmp	ep8_err		; a=1, err
	sjmp	ep8_err		; a=2, err
	sjmp	ep8_dio		; a=3, digital read
	sjmp	ep8_sglchannel	; a=4, analog A/D
	sjmp	ep8_err		; a=5, err
	sjmp	ep8_err		; a=6, err

	;; read one A/D channel
ep8_sglchannel:
	setb	IOA.7		; start converter, START = 1
	;; we do polling: we wait until DATA READY is zero
sglchwait:	
	mov	a,IOA		; get /DRDY
	jb	ACC.0,sglchwait	; wait until data ready (DRDY=0)
	mov 	DPTR,#0fc01h	; EP8 FIFO
	lcall	readADCch	; get one reading
	clr	IOA.7		; stop the converter, START = 0

	sjmp	ep8_send	; send the data

	;; read the digital lines
ep8_dio:	
	mov 	DPTR,#0fc01h	; store the contents of port B
	mov	a,IOB		; in the next
	movx	@dptr,a		; entry of the buffer
	inc	dptr
	mov	a,IOC		; port C
	movx	@dptr,a		; next byte of the EP
	inc	dptr
	mov	a,IOD
	movx	@dptr,a		; port D
	
ep8_send:	
	mov	DPTR,#EP8BCH	; byte count H
	mov	a,#0		; is zero
	lcall	syncdelaywr
	
	mov	DPTR,#EP8BCL	; byte count L
	mov	a,#10H		; 16 bytes, bec it's such a great number...
	lcall	syncdelaywr	; send the data over to the host

ep8_err:	
	ret



;;; EP8 interrupt is the endpoint which sends data back after a command
;;; The actual command fills the EP buffer already
;;; but for INSNs we need to deliver more data if the count > 1
ep8_isr:	
	push	dps
	push	dpl
	push	dph
	push	dpl1
	push	dph1
	push	acc
	push	psw
	push	00h		; R0
	push	01h		; R1
	push	02h		; R2
	push	03h		; R3
	push	04h		; R4
	push	05h		; R5
	push	06h		; R6
	push	07h		; R7
		
	lcall	ep8_ops
	
	;; clear INT2
	mov	a,EXIF		; FIRST clear the USB (INT2) interrupt request
	clr	acc.4
	mov	EXIF,a		; Note: EXIF reg is not 8051 bit-addressable

	mov	DPTR,#EPIRQ	; 
	mov	a,#10000000b	; clear the ep8irq
	movx	@DPTR,a

	pop	07h
	pop	06h
	pop	05h
	pop	04h		; R4
	pop	03h		; R3
	pop	02h		; R2
	pop	01h		; R1
	pop	00h		; R0
	pop	psw
	pop	acc 
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl 
	pop	dps
	reti



;;; GPIF waveform for PWM
waveform:
	;;      0     1     2     3     4     5     6     7(not used)
	;; len (gives 50.007Hz)
	.db	195,  195,  195,  195,  195,  195,  1,    1

	;; opcode
	.db	002H, 006H, 002H, 002H, 002H, 002H, 002H, 002H
	
	;; out
	.db	0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH, 0ffH

	;; log
	.db	000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H


stopPWM:
	mov	r0,#PWMFLAG	; flag for PWM
	mov	a,#0		; PWM (for the main loop)
	mov	@r0,a		; set it

	mov	dptr,#IFCONFIG	; switch off GPIF
	mov	a,#10100000b	; gpif, 30MHz, internal IFCLK
	lcall	syncdelaywr
	ret
	

;;; init PWM
startPWM:
	mov	dptr,#IFCONFIG	; switch on IFCLK signal
	mov	a,#10100010b	; gpif, 30MHz, internal IFCLK
	lcall	syncdelaywr

	mov	OEB,0FFH	; output to port B

	mov	DPTR,#EP4CFG
	mov	a,#10100000b	; valid, out, bulk
	movx	@DPTR,a

	;; reset the endpoint
	mov	dptr,#FIFORESET
	mov	a,#80h		; NAK
	lcall	syncdelaywr
	mov	a,#84h		; reset EP4 + NAK
	lcall	syncdelaywr
	mov	a,#0		; normal op
	lcall	syncdelaywr

	mov	dptr,#EP4BCL
	mov	a,#0H		; discard packets
	lcall	syncdelaywr	; empty FIFO buffer
	lcall	syncdelaywr	; empty FIFO buffer

	;; aborts all transfers by the GPIF
	mov	dptr,#GPIFABORT
	mov	a,#0ffh		; abort all transfers
	lcall	syncdelaywr

	;; wait for GPIF to finish
wait_f_abort:
	mov	a,GPIFTRIG	; GPIF status
	anl	a,#80h		; done bit
	jz	wait_f_abort	; GPIF busy

        mov     dptr,#GPIFCTLCFG
        mov     a,#10000000b    ; tri state for CTRL
        lcall   syncdelaywr

        mov     dptr,#GPIFIDLECTL
        mov     a,#11110000b    ; all CTL outputs low
        lcall   syncdelaywr

	;; abort if FIFO is empty
        mov     a,#00000001b    ; abort if empty
        mov     dptr,#EP4GPIFFLGSEL
        lcall   syncdelaywr

	;; 
        mov     a,#00000001b    ; stop if GPIF flg
        mov     dptr,#EP4GPIFPFSTOP
        lcall   syncdelaywr

	;; transaction counter
	mov	a,#0ffH
	mov	dptr,#GPIFTCB3
	lcall	syncdelaywr

	;; transaction counter
	mov	a,#0ffH
	mov	dptr,#GPIFTCB2
	lcall	syncdelaywr

	;; transaction counter
	mov	a,#0ffH		; 512 bytes
	mov	dptr,#GPIFTCB1
	lcall	syncdelaywr

	;; transaction counter
	mov	a,#0ffH
	mov	dptr,#GPIFTCB0
	lcall	syncdelaywr

	;; RDY pins. Not used here.
        mov     a,#0
        mov     dptr,#GPIFREADYCFG
        lcall   syncdelaywr

	;; drives the output in the IDLE state
        mov     a,#1
        mov     dptr,#GPIFIDLECS
        lcall   syncdelaywr

	;; direct data transfer from the EP to the GPIF
	mov	dptr,#EP4FIFOCFG
	mov	a,#00010000b	; autoout=1, byte-wide
	lcall	syncdelaywr

	;; waveform 0 is used for FIFO out
	mov	dptr,#GPIFWFSELECT
	mov	a,#00000000b
	movx	@dptr,a
	lcall	syncdelay

	;; transfer the delay byte from the EP to the waveform
	mov	dptr,#0e781h	; EP1 buffer
	movx	a,@dptr		; get the delay
	mov	dptr,#waveform	; points to the waveform
	mov	r2,#6		; fill 6 bytes
timloop:
	movx	@dptr,a		; save timing in a xxx
	inc	dptr
	djnz	r2,timloop	; fill the 6 delay bytes

	;; load waveform
        mov     AUTOPTRH2,#0E4H ; XDATA0H
        lcall   syncdelay
        mov     AUTOPTRL2,#00H  ; XDATA0L
        lcall   syncdelay

	mov	dptr,#waveform	; points to the waveform
	
        mov     AUTOPTRSETUP,#7 ; autoinc and enable
        lcall   syncdelay

        mov     r2,#20H         ; 32 bytes to transfer

wavetr:
        movx    a,@dptr
	inc	dptr
	push	dpl
	push	dph
	push	dpl1
	push	dph1
        mov     dptr,#XAUTODAT2
        movx    @dptr,a
        lcall   syncdelay
	pop	dph1 
	pop	dpl1
	pop	dph 
	pop	dpl
        djnz    r2,wavetr

	mov	dptr,#OUTPKTEND
	mov	a,#084H
	lcall	syncdelaywr
	lcall	syncdelaywr

	mov	r0,#PWMFLAG	; flag for PWM
	mov	a,#1		; PWM (for the main loop)
	mov	@r0,a		; set it

	ret

	

;; need to delay every time the byte counters
;; for the EPs have been changed.

syncdelay:
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	ret

syncdelaywr:
	movx	@dptr,a
	lcall	syncdelay
	ret



	.org	1F00h		; lookup table at the end of memory

swap_lut:
.db 0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136
.db 72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100
.db 228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220
.db 60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10
.db 138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166
.db 102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94
.db 222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9
.db 137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165
.db 101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93
.db 221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11
.db 139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167
.db 103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95
.db 223,63,191,127,255



	
.End


