; ======================================================================
; USB interrupt handler
;
; This is the handler for the interrupt caused by the initial rising edge
; on the D+ USB signal. The NRZI encoding and bit stuffing are removed,
; and the packet is saved in one of the two input buffers. In some cases,
; a reply packet is sent right away.
;
; When a DATA0/DATA1 packet directly follows a SETUP or OUT packet, while
; this interrupt handler is not yet finished, there would be no time to
; return and take another interrupt. In that case, the second packet is
; decoded directly in the same invocation. A packet immediately following
; an ignored packet is also decoded directly.
;
; This code is *extremely* time critical. For instance, there is not a
; single spare cycle in the receiver loop, and only two in the transmitter
; loop. In addition, the various code paths are laid out in such a way that
; the various USB timeouts are not violated, in particular the maximum time
; between the reception of a packet and the reply, which is 7.5 bit times
; (TRSPIPD2) for a low-speed USB captive cable. The worst-case delay here
; is 51 cycles, which is well below the 60 cycles limit, and even below the
; 6.5 bit times limit for a detachable cable (TRSPIPD1).
;
; The interrupt handler must be reached within 34 cycles after D+ goes high
; for the first time. The interrupt response time is 4 cycles, and the RJMP
; in the vector table takes 2 cycles. Therefore, the interrupts should not
; be disabled for longer than: 34 - 4 - 2 = 28 cycles. When the I-bit is
; reenabled, a single instruction is always executed before a pending
; interrupt is served, so this instruction should be included in the
; calculation. For RETI, the next instruction can be anything, so we
; should assume the worst-case of 4 cycles.
;
; The end-of-packet (EOP) is sampled in the second bit, because the USB
; standard allows the EOP to be delayed by up to one bit. As the EOP
; duration is two bits, this is not a problem.
;
; Stack usage including the return address: 11 bytes.
;
; Copyright 2006-2010 Dick Streefland
;
; This is free software, licensed under the terms of the GNU General
; Public License as published by the Free Software Foundation.
; ======================================================================

#include "def.h"

; ----------------------------------------------------------------------
; local data
; ----------------------------------------------------------------------
	.data
tx_ack:	.byte	USB_PID_ACK		; ACK packet
tx_nak:	.byte	USB_PID_NAK		; NAK packet
	.lcomm	token_pid, 1		; PID of most recent token packet
	.global	__do_copy_data

; ----------------------------------------------------------------------
; register definitions
; ----------------------------------------------------------------------
// receiver:
#define	count		r16
#define	usbmask		r17
#define	odd		r18
#define	byte		r19
#define	fixup		r20
#define	even		r22

// transmitter:
#define	output		odd
#define	done		fixup
#define	next		even

// control:
#define	pid		odd
#define	addr		usbmask
#define	tmp		fixup

#define	nop2		rjmp	.+0	// not .+2 for some strange reason

; ----------------------------------------------------------------------
; interrupt handler
; ----------------------------------------------------------------------
	.text
	.global	USB_INT_VECTOR
	.type	USB_INT_VECTOR, @function
; ----------------------------------------------------------------------
; This handler must be reached no later than 34 cycles after D+ goes high
; for the first time.
; ----------------------------------------------------------------------
USB_INT_VECTOR:
	; save registers
	push	count
	push	usbmask
	push	odd
	push	YH
	push	YL
	in	count, SREG
	push	count

; ----------------------------------------------------------------------
; Synchronize to the pattern 10101011 on D+. This code must be reached
; no later than 47 cycles after D+ goes high for the first time.
; ----------------------------------------------------------------------
sync:
	; wait until D+ == 0
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync			; jump if D+ == 1
resync:
	; sync on 0-->1 transition on D+ with a 2 cycle resolution
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync6			; jump if D+ == 1
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync6			; jump if D+ == 1
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync6			; jump if D+ == 1
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync6			; jump if D+ == 1
	sbic	USB_IN, USBTINY_DPLUS
	rjmp	sync6			; jump if D+ == 1
	ldi	count, 1<<USB_INT_PENDING_BIT
	out	USB_INT_PENDING, count
	rjmp	return			; ==> false start, bail out

sync6:
	; we are now between -1 and +1 cycle from the center of the bit
	; following the 0-->1 transition
	lds	YL, usb_rx_off
	clr	YH
	subi	YL, lo8(-(usb_rx_buf))	; Y = & usb_rx_buf[usb_rx_off]
	sbci	YH, hi8(-(usb_rx_buf))
	ldi	count, USB_BUFSIZE	; limit on number of bytes to receive
	ldi	usbmask, USB_MASK	; why is there no eori instruction?
	ldi	odd, USB_MASK_DPLUS

sync7:
	; the last sync bit should also be 1
	sbis	USB_IN, USBTINY_DPLUS	; bit 7 of sync byte?
	rjmp	resync			; no, wait for next transition
	push	byte
	push	fixup
	push	even

; ----------------------------------------------------------------------
; receiver loop
; ----------------------------------------------------------------------
	in	even, USB_IN		; sample bit 0
	ldi	byte, 0x80		; load sync byte for correct unstuffing
	rjmp	rxentry			; 2 cycles

rxloop:
	in	even, USB_IN		; sample bit 0
	or	fixup, byte
	st	Y+, fixup		; 2 cycles
rxentry:
	clr	fixup
	andi	even, USB_MASK
	eor	odd, even
	subi	odd, 1
	in	odd, USB_IN		; sample bit 1
	andi	odd, USB_MASK
	breq	eop			; ==> EOP detected
	ror	byte
	cpi	byte, 0xfc
	brcc	skip0
skipped0:
	eor	even, odd
	subi	even, 1
	in	even, USB_IN		; sample bit 2
	andi	even, USB_MASK
	ror	byte
	cpi	byte, 0xfc
	brcc	skip1
skipped1:
	eor	odd, even
	subi	odd, 1
	ror	byte
	in	odd, USB_IN		; sample bit 3
	andi	odd, USB_MASK
	cpi	byte, 0xfc
	brcc	skip2
	eor	even, odd
	subi	even, 1
	ror	byte
skipped2:
	cpi	byte, 0xfc
	in	even, USB_IN		; sample bit 4
	andi	even, USB_MASK
	brcc	skip3
	eor	odd, even
	subi	odd, 1
	ror	byte
skipped4:
	cpi	byte, 0xfc
skipped3:
	brcc	skip4
	in	odd, USB_IN		; sample bit 5
	andi	odd, USB_MASK
	eor	even, odd
	subi	even, 1
	ror	byte
skipped5:
	cpi	byte, 0xfc
	brcc	skip5
	dec	count
	in	even, USB_IN		; sample bit 6
	brmi	overflow		; ==> overflow
	andi	even, USB_MASK
	eor	odd, even
	subi	odd, 1
	ror	byte
skipped6:
	cpi	byte, 0xfc
	brcc	skip6
	in	odd, USB_IN		; sample bit 7
	andi	odd, USB_MASK
	eor	even, odd
	subi	even, 1
	ror	byte
	cpi	byte, 0xfc
	brcs	rxloop			; 2 cycles
	rjmp	skip7

eop:
	rjmp	eop2
overflow:
	rjmp	ignore

; ----------------------------------------------------------------------
; out-of-line code to skip stuffing bits
; ----------------------------------------------------------------------
skip0:					; 1+6 cycles
	eor	even, usbmask
	in	odd, USB_IN		; resample bit 1
	andi	odd, USB_MASK
	cbr	byte, (1<<7)
	sbr	fixup, (1<<0)
	rjmp	skipped0

skip1:					; 2+5 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<1)
	in	even, USB_IN		; resample bit 2
	andi	even, USB_MASK
	eor	odd, usbmask
	rjmp	skipped1

skip2:					; 3+7 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<2)
	eor	even, usbmask
	in	odd, USB_IN		; resample bit 3
	andi	odd, USB_MASK
	eor	even, odd
	subi	even, 1
	ror	byte
	rjmp	skipped2

skip3:					; 4+7 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<3)
	eor	odd, usbmask
	ori	byte, 1
	in	even, USB_IN		; resample bit 4
	andi	even, USB_MASK
	eor	odd, even
	subi	odd, 1
	ror	byte
	rjmp	skipped3

skip4:					; 5 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<4)
	eor	even, usbmask
	rjmp	skipped4

skip5:					; 5 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<5)
	eor	odd, usbmask
	rjmp	skipped5

skip6:					; 5 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<6)
	eor	even, usbmask
	rjmp	skipped6

skip7:					; 7 cycles
	cbr	byte, (1<<7)
	sbr	fixup, (1<<7)
	eor	odd, usbmask
	nop2
	rjmp	rxloop

; ----------------------------------------------------------------------
; end-of-packet detected (worst-case: 3 cycles after end of SE0)
; ----------------------------------------------------------------------
eop2:
	; clear pending interrupt (SE0+3)
	ldi	byte, 1<<USB_INT_PENDING_BIT
	out	USB_INT_PENDING, byte	; clear pending bit at end of packet
	; calculate packet length
	subi	count, USB_BUFSIZE
	neg	count			; count = packet length
	; get PID
	sub	YL, count
	sbci	YH, 0
	ld	pid, Y
	; separate out the non-Token packets (SE0+11)
	sbrc	pid, 1
	rjmp	is_data_handshake	; jump for Data or Handshake packet
	; check ADDR of Token packet (SE0+13)
	ldd	addr, Y+1
	andi	addr, 0x7f
	lds	tmp, usb_address
	cp	addr, tmp		; is this packet for me?
	brne	ignore			; no, ignore
	; dispatch Token packets (SE0+20)
	cpi	pid, USB_PID_IN
	brne	is_setup_out		; handle SETUP and OUT packets

; ----------------------------------------------------------------------
; Handle IN (SE0+22)
; ----------------------------------------------------------------------
	lds	count, usb_tx_len
	tst	count			; data ready?
	breq	nak			; no, reply with NAK
	lds	tmp, usb_rx_len
	tst	tmp			; unprocessed input packet?
	brne	nak			; yes, don't send old data for new packet
	sts	usb_tx_len, tmp		; buffer is available again (after reti)
	lds	tmp, usb_new_address
	sts	usb_address, tmp	; assign new address at end of transfer
	ldi	YL, lo8(usb_tx_buf)
	ldi	YH, hi8(usb_tx_buf)
	rjmp	send_packet		; SE0+40, SE0 --> SOP <= 51

; ----------------------------------------------------------------------
; exit point for ignored packets (SE0+21)
; ----------------------------------------------------------------------
ignore:
	clr	pid
ignore0:

; ----------------------------------------------------------------------
; Handle SETUP/OUT (SE0+23)
; ----------------------------------------------------------------------
is_setup_out:
	sts	token_pid, pid		; save PID of token packet
	pop	even
	pop	fixup
	pop	byte
	in	count, USB_INT_PENDING	; next packet already started?
	sbrc	count, USB_INT_PENDING_BIT
	rjmp	sync			; yes, get it right away (SE0+35)

; ----------------------------------------------------------------------
; restore registers and return from interrupt (SE0+34)
; ----------------------------------------------------------------------
return:
	pop	count
	out	SREG, count
	pop	YL
	pop	YH
	pop	odd
	pop	usbmask
	pop	count
	reti

; ----------------------------------------------------------------------
; send NAK packet (SE0+31)
; ----------------------------------------------------------------------
nak:
	ldi	YL, lo8(tx_nak)
	ldi	YH, hi8(tx_nak)
	rjmp	send_token

; ----------------------------------------------------------------------
; Handle Data and Handshake packets (SE0+14)
; ----------------------------------------------------------------------
is_data_handshake:
	andi	pid, 0x01
	breq	ignore0			; ignore ACK/NAK/STALL

; ----------------------------------------------------------------------
; Handle DATA0/DATA1 (SE0+16)
; ----------------------------------------------------------------------
	lds	pid, token_pid
	tst	pid			; data following our SETUP/OUT
	breq	ignore0			; no, ignore
	lds	tmp, usb_rx_len
	tst	tmp			; buffer free?
	brne	nak			; no, reply with NAK
	sts	usb_rx_len, count	; pass buffer length
	sts	usb_rx_token, pid	; pass PID of token (SETUP or OUT)
	lds	count, usb_rx_off	; switch to other input buffer
	ldi	tmp, USB_BUFSIZE
	sub	tmp, count
	sts	usb_rx_off, tmp

; ----------------------------------------------------------------------
; send ACK packet (SE0+34)
; ----------------------------------------------------------------------
	ldi	YL, lo8(tx_ack)
	ldi	YH, hi8(tx_ack)
send_token:
	ldi	count, 1		; SE0+37, SE0 --> SOP <= 48

; ----------------------------------------------------------------------
; acquire the bus and send a packet (11 cycles to SOP)
; ----------------------------------------------------------------------
send_packet:
	in	output, USB_OUT
	cbr	output, USB_MASK
	ori	output, USB_MASK_DMINUS
	in	usbmask, USB_DDR
	ori	usbmask, USB_MASK
	out	USB_OUT, output		; idle state
	out	USB_DDR, usbmask	; acquire bus
	ldi	usbmask, USB_MASK
	ldi	byte, 0x80		; start with sync byte

; ----------------------------------------------------------------------
; transmitter loop
; ----------------------------------------------------------------------
txloop:
	sbrs	byte, 0
	eor	output, usbmask
	out	USB_OUT, output		; output bit 0
	ror	byte
	ror	done
stuffed0:
	cpi	done, 0xfc
	brcc	stuff0
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
stuffed1:
	out	USB_OUT, output		; output bit 1
	ror	done
	cpi	done, 0xfc
	brcc	stuff1
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
	nop
stuffed2:
	out	USB_OUT, output		; output bit 2
	ror	done
	cpi	done, 0xfc
	brcc	stuff2
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
	nop
stuffed3:
	out	USB_OUT, output		; output bit 3
	ror	done
	cpi	done, 0xfc
	brcc	stuff3
	sbrs	byte, 0
	eor	output, usbmask
	ld	next, Y+		; 2 cycles
	out	USB_OUT, output		; output bit 4
	ror	byte
	ror	done
stuffed4:
	cpi	done, 0xfc
	brcc	stuff4
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
stuffed5:
	out	USB_OUT, output		; output bit 5
	ror	done
	cpi	done, 0xfc
	brcc	stuff5
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
stuffed6:
	ror	done
	out	USB_OUT, output		; output bit 6
	cpi	done, 0xfc
	brcc	stuff6
	sbrs	byte, 0
	eor	output, usbmask
	ror	byte
	mov	byte, next
stuffed7:
	ror	done
	out	USB_OUT, output		; output bit 7
	cpi	done, 0xfc
	brcc	stuff7
	dec	count
	brpl	txloop			; 2 cycles

	rjmp	gen_eop

; ----------------------------------------------------------------------
; out-of-line code to insert stuffing bits
; ----------------------------------------------------------------------
stuff0:					; 2+3
	eor	output, usbmask
	clr	done
	out	USB_OUT, output
	rjmp	stuffed0

stuff1:					; 3
	eor	output, usbmask
	rjmp	stuffed1

stuff2:					; 3
	eor	output, usbmask
	rjmp	stuffed2

stuff3:					; 3
	eor	output, usbmask
	rjmp	stuffed3

stuff4:					; 2+3
	eor	output, usbmask
	clr	done
	out	USB_OUT, output
	rjmp	stuffed4

stuff5:					; 3
	eor	output, usbmask
	rjmp	stuffed5

stuff6:					; 3
	eor	output, usbmask
	rjmp	stuffed6

stuff7:					; 3
	eor	output, usbmask
	rjmp	stuffed7

; ----------------------------------------------------------------------
; generate EOP, release the bus, and return from interrupt
; ----------------------------------------------------------------------
gen_eop:
	cbr	output, USB_MASK
	out	USB_OUT, output		; output SE0 for 2 bit times
	pop	even
	pop	fixup
	pop	byte
	ldi	count, 1<<USB_INT_PENDING_BIT
	out	USB_INT_PENDING, count	; interrupt was triggered by transmit
	pop	YH			; this is the saved SREG
	pop	YL
	in	usbmask, USB_DDR
	mov	count, output
	ori	output, USB_MASK_DMINUS
	out	USB_OUT, output		; output J state for 1 bit time
	cbr	usbmask, USB_MASK
	out	SREG, YH
	pop	YH
	pop	odd			; is the same register as output!
	nop
	out	USB_DDR, usbmask	; release bus
	out	USB_OUT, count		; disable D- pullup
	pop	usbmask
	pop	count
	reti
