; ======================================================================
; 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.
;
; 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 6.5 bit times
; for a detachable cable (TRSPIPD1), and 7.5 bit times for a captive cable
; (TRSPIPD2). The worst-case delay here is 51 cycles, which is just below
; the 52 cycles for a detachable cable.
;
; The interrupt handler must be reached within 34 cycles after D+ goes high
; for the first time, so the interrupts should not be disabled for longer
; than 34-4-2=28 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 (C) 2006 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

; ----------------------------------------------------------------------
; 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
        ; ignore packets shorter than 3 bytes
        subi    count, USB_BUFSIZE
        neg     count                   ; count = packet length
        cpi     count, 3
        brlo    ignore
        ; get PID
        sub     YL, count
        ld      pid, Y
        ; check for DATA0/DATA1 first, as this is the critical path (SE0+12)
        cpi     pid, USB_PID_DATA0
        breq    is_data                 ; handle DATA0 packet
        cpi     pid, USB_PID_DATA1
        breq    is_data                 ; handle DATA1 packet
        ; check ADDR (SE0+16)
        ldd     addr, Y+1
        andi    addr, 0x7f
        lds     tmp, usb_address
        cp      addr, tmp               ; is this packet for me?
        brne    ignore                  ; no, ignore
        ; check for other PIDs (SE0+23)
        cpi     pid, USB_PID_IN
        breq    is_in                   ; handle IN packet
        cpi     pid, USB_PID_SETUP
        breq    is_setup_out            ; handle SETUP packet
        cpi     pid, USB_PID_OUT
        breq    is_setup_out            ; handle OUT packet

; ----------------------------------------------------------------------
; exit point for ignored packets
; ----------------------------------------------------------------------
ignore:
        clr     tmp
        sts     token_pid, tmp
        pop     even
        pop     fixup
        pop     byte
        rjmp    return

; ----------------------------------------------------------------------
; Handle SETUP/OUT (SE0+30)
; ----------------------------------------------------------------------
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+42)

; ----------------------------------------------------------------------
; restore registers and return from interrupt
; ----------------------------------------------------------------------
return:
        pop     count
        out     SREG, count
        pop     YL
        pop     YH
        pop     odd
        pop     usbmask
        pop     count
        reti

; ----------------------------------------------------------------------
; Handle IN (SE0+26)
; ----------------------------------------------------------------------
is_in:
        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)
        ldi     YL, lo8(usb_tx_buf)
        ldi     YH, hi8(usb_tx_buf)
        rjmp    send_packet             ; SE0+40, SE0 --> SOP <= 51

; ----------------------------------------------------------------------
; Handle DATA0/DATA1 (SE0+17)
; ----------------------------------------------------------------------
is_data:
        lds     pid, token_pid
        tst     pid                     ; data following our SETUP/OUT
        breq    ignore                  ; 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+35)
; ----------------------------------------------------------------------
ack:
        ldi     YL, lo8(tx_ack)
        ldi     YH, hi8(tx_ack)
        rjmp    send_token

; ----------------------------------------------------------------------
; send NAK packet (SE0+36)
; ----------------------------------------------------------------------
nak:
        ldi     YL, lo8(tx_nak)
        ldi     YH, hi8(tx_nak)
send_token:
        ldi     count, 1                ; SE0+40, SE0 --> SOP <= 51

; ----------------------------------------------------------------------
; 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