;
;    Mecrimus-B - A firmware-only USB implementation for MSP430 microcontrollers
;    Copyright (C) 2012  Matthias Koch
;
;    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 3 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, see <http://www.gnu.org/licenses/>.
;  


; Empfangsroutine für 18 MHz, 12 Takte/Bit 
; Receiver        for 18 MHz, 12 cycles/bit

nrzi_r10 macro
  mov.b @r9, r10          ; [1]  NRZI
  ;                       ; [2]  <--- Sampling
  and #usbminus, r10      ; [3]
  xor r10, r11            ; [4]
  rrc.b r12               ; [5]
  endm

nrzi_r11 macro
  mov.b @r9, r11          ; [1]  NRZI                         
  ;                       ; [2]  <--- Sampling
  and #usbminus, r11      ; [3]
  xor r11, r10            ; [4]
  rrc.b r12               ; [5]
  endm


unstuff macro register, bitmask
  bit r8, r12             ; [10]   Stopferkennung, 6 Bits nacheinander kein Wechsel !  Detect stuffing, 6 bits without change...
  jnz +                   ; [11]   Datenbits hier invertiert !                         Data bits inverted here !
                          ; [12]

  mov.b @r9, register           ; [e 1]  Für die NRZI-Dekodierung Zustand wechseln.    Read state again for proper NRZI decoding
                                ; [e 2]  <--- Sampling Zustand einfach nochmal lesen
  and #usbminus, register       ; [e 3]      
  bis #80h, r12                 ; [e 4]  Oberstes Bit in Daten setzen                  Set MSB in data
                                ; [e 5]

  bis #bitmask, r13             ; [e 6] Entsprechende Stelle in Stopfmaske merken      Set changed place in stuffmask
  ;                                     Je nach Maske einen Takt mehr.                                

  if (bitmask == 1) | ( bitmask == 2 ) | ( bitmask == 4 ) | (bitmask == 8) ;           Insert one nop more if constant generator can be used
    nop2                        ; [ e 7, e 8 ]
  elseif
    nop                         ; [ e 8 ]
  endif

  nop4                          ; [e 9 - e 12]
+ ; Fertig Finished stuffing
  endm

  ; Signal for Sync pattern

  ; Signal: 
  ; D-:   normally high, Sync: 01 01 01 00 
  ; D+:            low         10 10 10 11
  ; Idea: Search 1-->0 transtition, sample it precisely and look if double zero follows, else repeat.
  ; As initialisation after IRQ (after bit.b for zero) needs some time, we will normally not see the first zero here.
 

  include "Sync-18MHz.asm"

  ; ---------------------------------------------------------------------------
  ; Fertig synchronisiert, lege los ! Sync is ok, lets start !
  ; ---------------------------------------------------------------------------
  
  ; r12 muss für die Stopferkennung passend initialisiert werden !
  ; r13 = Stopfmaske wird ausgenullt
  ; r15 = Pufferzeiger setzen

  ; Habe beim ersten Einlauf Zeit, da ganz am Anfang kein Stopfen möglich ist.
  ; Can do preparations while running in, because there cannot occour stuffing on the first few bits.

  ; Register usage:
  ; r8   ; Vergleichs-Stopfmaskenmuster #11111100b    Mask for detect stuffing
  ; r9   ; P2IN-Adresse                               Address of P2IN

  ; r10  ; NRZI-Pinzustand                            Buffer for NRZI pin state
  ; r11  ; NRZI-Pinzustand                            --

  ; r12  ; Datenempfangsregister                      Data input shift register, has to be initialized to sync-pattern not($80)
  ; r13  ; Stopfmaske                                 Stuffing mask, has to be cleared

  ; r14  ; Datensicherung                             Data backup register
  ; r15  ; Pointer                                    Pointer to write into receive buffer
  
  ; Note: Data is inverted because of clever use of flags and side-effects to save cycles
  ;       Stuffing mask holds those bits that have been inverted, so that stuffing detection 
  ;       will not detect stuffing on the same bits more than one time.

  ; We have some time in the first byte of data, as stuffing cannot occour the first 5 bits.
  ; D- is used for sync and input, D+ is only used to detect packet end SE0.

  ; ---------------------------------------------------------------------------
  ; Einlauf, Erstes Bit: (0)  Run-in with preparations, first bit
  ; ---------------------------------------------------------------------------

  mov.b @r9, r10     ; [1]  NRZI             ( 3  4)  Chance counting here so that mov.b @r9 always counts on one.
  ;                  ; [2]  <--- Sampling    ( 4  5)  Center-Sampling on 5 or 6 !

  mov #7Fh, r12      ; [3]  not($80)         ( 5  6)  Initialize data, as last bit of Syncpattern may take part in stuffing.
  ;                  ; [4]                            Syncbyte spielt beim Stopfen mit !Präparation des Datenregisters für Stopferkennung.
  and #usbminus, r10 ; [5] statt bit einleiten        Wechsele hier die Zählweise !              Eine davon müsste direkt den Wert des ersten Bits liefern. Hängt vom Sync-Byte-Endzustand ab. Dort ist D- low. Wechsel --> Carry gesetzt. Kommt hin :-)
  rrc.b r12          ; [6]  und rotieren              Der Mov-Befehl kommt so immer auf [1] :-)  Erstes Datenbit aufnehmen ! Usbminus enthält das erste Datenbit invertiert. Passt :-)
                     ;      Catch first data bit, use only AND here, as last state out of sync pattern is always the same.
  push r13           ; [7]  Weitere Register sichern  Save more registers
  ;                  ; [8] 
  ;                  ; [9] 
  push r14           ; [10] "Datensicherung" braucht keine Initialisierung.
  ;                  ; [11] The data backup register doesn't need initialisation.
  ;                  ; [12]

  ; ---------------------------------------------------------------------------
  ; Einlauf, Zweites Bit: (1)   Run-in with preparations
  ; ---------------------------------------------------------------------------

  nrzi_r11           ; [1-5]
  clr r13            ; [6]  Stopfmaske löschen   Clear stuffing mask
  push r15           ; [7]  Der Pointer für Empfangenes.
  ;                  ; [8]  Pointer to write received data, has to be initialized.
  ;                  ; [9]  
  mov &Receivebufferpointer, r15 ; [10] Pufferadresse in Pointer laden
  ;                              ; [11] Fetch buffer pointer
  ;                              ; [12]

  ; ---------------------------------------------------------------------------
  ; Einlauf, Drittes Bit: (2)  Run-in with preparations
  ; ---------------------------------------------------------------------------

  nrzi_r10           ; [1-5]
  push r8            ; [6]  Stopfmaskenregister sichern
                     ; [7]
                     ; [8]
  mov #11111100b, r8 ; [9]  und Stopfmaske bereitlegen.    Prepare mask to quickly detect last 6 bits for stuffing
                     ; [10]
  jmp Viertes_Bit_Einsprung ; [11]  Einsprung ins reguläre Schema  Jump into regular cycle
  ;                         ; [12]





USB_ReceiverLoop:  
  ; ---------------------------------------------------------------------------
  ; Erstes Bit: (0)  Sonderaufgabe: SE0-Check  Special task: Detect SE0
  ; ---------------------------------------------------------------------------
  mov.b @r9, r10          ; [1]  NRZI
                          ; [2]  <--- Sampling

  and #usbplus+usbminus, r10 ; [s3] ; SE0-Check im ersten Bit !  Check for SE0 
                             ; [s4]
  jz usb_se0                 ; [s5]                               which occours as low low in both USB lines
                             ; [s6]                               Unfortunately this takes whole 4 cycles...

  and #usbminus, r10      ; [7]
  xor r10, r11            ; [8]
  rrc.b r12               ; [9]

  unstuff r10, 1          ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Zweites Bit: (1)  Sonderaufgabe: SE0-Check  Special task: Detect SE0
  ; ---------------------------------------------------------------------------
  mov.b @r9, r11             ; [1]  NRZI einleiten mit Sampling
                             ; [2]   <--- Sampling

  and #usbplus+usbminus, r11 ; [s3] ; SE0-Check im zweiten Bit !  Check for SE0 
                             ; [s4]
  jz usb_se0                 ; [s5]                               Which occours as low low in both USB lines
                             ; [s6]                               Unfortunately this takes whole 4 cycles...

  and #usbminus, r11         ; [7]  NRZI durchführen
  xor r11, r10               ; [8] 
  rrc.b r12                  ; [9]

  unstuff r11, 2             ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Drittes Bit: (2)  Sonderaufgabe: Daten in Puffer schreiben  Special task: Write data into buffer
  ; ---------------------------------------------------------------------------
  nrzi_r10                ; [1-5]

  mov.b r14, @r15         ; [p6]  Daten in den Puffer legen !
                          ; [p7]  Store data in buffer !
                          ; [p8]
                          ; [p9]

  unstuff r10, 4          ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Viertes Bit: (3)  Frei  Free of special tasks
  ; ---------------------------------------------------------------------------
Viertes_Bit_Einsprung:
  nrzi_r11           ; [1-5]
  inc r15            ; [6]
  nop3               ; [7-9]  Platz für Sonderfunktionen  Holidays :-)
  unstuff r11, 8     ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Fünftes Bit: (4)  Frei  Free of special tasks
  ; ---------------------------------------------------------------------------
  nrzi_r10           ; [1-5]
  nop4               ; [6-9] Platz für Sonderfunktionen  Holidays :-)
  unstuff r10, 16    ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Sechstes Bit: (5)  Frei  Free of special tasks
  ; ---------------------------------------------------------------------------
  nrzi_r11           ; [1-5]
  nop4               ; [6-9]  Platz für Sonderfunktionen  Holidays :-)
  unstuff r11, 32    ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Siebtes Bit: (6)  Frei  Free of special tasks
  ; ---------------------------------------------------------------------------
  nrzi_r10           ; [1-5]
  nop4               ; [6-9]  Platz für Sonderfunktionen  Holidays :-)
  unstuff r10, 64    ; [10-12]

  ; ---------------------------------------------------------------------------
  ; Achtes Bit: (7)  Sonderaufgabe: Daten sichern und Stopfmaske anwenden ! Rücksprung via Stopfsprung.   
  ;                  Special task: Save data for later preparation and apply stuffmask. Jump back with stuffing-jump to save cycles !
  ; ---------------------------------------------------------------------------

  nrzi_r11                ; [1-5]

  mov r12, r14            ; [6] Datenbyte ist fertig ! Hole es ab.  Backup of Data
  bic r13, r14            ; [7] Stopfmaske darauf anwenden.         Apply stuffmask
  clr r13                 ; [8] Sonderaufgabe: Stopfmaske leeren !  Clear stuffing mask. Relevant information is still in r12 and gets shifted out by the way.
  inv r14                 ; [9] Gesichertes Datenbyte invertieren.  Invert saved data byte.

  bit r8, r12             ; [10]  Stopferkennung, 6 Bits nacheinander kein Wechsel !                         
  jnz USB_ReceiverLoop    ; [11]  Return with stuff-detect-jump
                          ; [12]


        ; Enstopfung für letztes Bit. Unstuffing for last bit.
        mov.b @r9, r11                ; [e 1]  Für die NRZI-Dekodierung Zustand wechseln.
                                      ; [e 2]  <--- Sampling  Zustand einfach nochmal lesen
        and #usbminus, r11            ; [e 3]
        bis #80h, r12                 ; [e 4]  Oberstes Bit in Daten setzen, Datenregister ist 16 Bits breit und läuft durch !
                                      ; [e 5]  Set MSB in data, this is rotated further later.  
        nop                           ; [e 6]    Nicht in Stopfmaske vermerken,
        nop2                          ; [e 7]    da dieses Bit komplett herausgeschoben wird
        ;nop                          ; [e 8]    und die Stopfmaske sowieso gelöscht wird.
        nop2                          ; [e 9]
        ;nop                          ; [e 10]
        jmp USB_ReceiverLoop          ; [e 11]
                                      ; [e 12]  In the last bit there is no need to update stuffmask, as data byte is already saved,
                                      ;         but it is necessary to set the bit in data register, as stuffing continues over byte boundaries.
                                      ;         It will be shifted out later anyway... 

  ; ---------------------------------------------------------------------------
  ; Paketende SE0:  End of packet detected.
  ; ---------------------------------------------------------------------------
usb_se0:  ; Komme mit [7] hier an !

  bic.b #usbplus, &P2IFG ; Interrupt-Pending-Flag löschen. Gleich nach SE0 und nach dem Senden. 
  ;                      ; Das Wiederansteigen von SE0 auf J triggert keinen Interrupt.
 
  mov.b r14, @r15 ; [8] :-)  Das letzte Datenbyte vermerken !!     Store last Databyte in Buffer
                  ; [9]
                  ;[10]
 
  mov &Receivebufferpointer, r14            ;  [1]    Fetch bufferpointer
  ;                                         ;  [2]
  ;                                         ;  [3]
  sub r14, r15  ; Paketlänge bestimmen      ;  [4]    Calculate length of received packet
  mov.b r15, @r14                           ;  [5]    Store length like in a counted string
  ;                                         ;  [6]
  ;                                         ;  [7]
  
  toggle_syncled ; Finished. Toggle syncled again, to see on oscilloscope if SE0 detection missed its job due to wrong sync.
    
;------------------------------------------------------------------------------      
  mov.b 1(r14), r10  ; PID holen      Fetch PID of packet for further investigation.
  mov.b 2(r14), r11  ; Adresse holen  Fetch field that may contain address
  mov.b 3(r14), r12  ; Endpunkt holen Fetch field that may contain endpoint
;------------------------------------------------------------------------------
  bit #080h, r11 ; Fetch LSB bit of endpoint
  rlc r12        ; Rotate that bit in
  and #00Fh, r12 ; Remove CRC5 from endpoint
  
  bic #080h, r11 ; Clear Endpoint bit in address  
;------------------------------------------------------------------------------
  
  cmp #usb_pid_in, r10
  jne usb_in_finished
  
    ; Für mich ? For me ?
    cmp.b r11, &Current_Address
    jne usb_pop_and_finish
  
    cmp #1, r12 ; Prüfe den Endpunkt ! Check endpoint !
    jne +
      mov &ReadyforTransmit_Irq_in, r15  ; Zeiger für ausgehende Daten holen  Fetch pointer for outgoing packets for Irq-In endpoint
      clr &ReadyforTransmit_Irq_in       ; Puffer wieder auffüllbereit.       Buffer ready for refill
      jmp ++
  
+   mov &ReadyforTransmit, r15  ; Zeiger für ausgehende Daten holen    Fetch pointer for outgoing packets
    clr &ReadyforTransmit       ; Puffer wieder auffüllbereit.         Buffer ready for refill
    
+   cmp #0, r15                 ; Ist etwas vorhanden ?                Something inside ?
    jne +
      mov #usb_paket_nak, r15   ; Ansonsten ein NAK-Paket senden.      If not, send NAK
+
 
    mov &New_Address, &Current_Address ; Adressänderungen werden nach dem Status Stage übernommen. Tue dies hier !
    jmp usb_transmit                   ; Address changes are committed after Status Stage. Do this here !
    
usb_in_finished:
;------------------------------------------------------------------------------  
  
  cmp #usb_pid_out, r10
  je +
  cmp #usb_pid_setup, r10
  jne ++  
  
+   ; Für mich ? For me ?
    cmp.b r11, &Current_Address
    jne usb_pop_and_finish
        
      mov.b r10, &Datapacketbuffer+15 ; PID vermerken  Save PID
      mov #Datapacketbuffer, &ReceiveBufferPointer ; Zum Empfang bereitmachen !  Get ready to receive data with destination "me"
      jmp usb_pop_and_finish
+        
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; Alles Weitere nur, wenn dies Paket für mich ist und der Empfangspuffer bereits gesetzt wurde.
; Do this only if the packet was for me and the receive pointer has been set accordingly.
  cmp #Datapacketbuffer, &ReceiveBufferPointer
  jne usb_pop_and_finish
  ; Es ist für mich. Nimm es an !       This is for me ! Accept it.
  ; Schalte schonmal den Puffer zurück: Switch back receive buffer
  mov #Tokenpacketbuffer, &ReceiveBufferPointer
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
  cmp #usb_pid_data0, r10
  je +  
  cmp #usb_pid_data1, r10
  jne ++  
  
+   mov #usb_paket_ack, r15
    jmp usb_transmit
+ 
;------------------------------------------------------------------------------
  
usb_pop_and_finish:  
  pop r8
  pop r15
  pop r14
  pop r13
  pop r12
  pop r11
                         ; Is there already the next packet in the line ?
  bit.b #usbplus, &P2IFG ; Kommt bereits das nächste Paket ? (Oder umgekehrt, um Takte zu sparen ?)
  jc USB_quick_Resync    ; Wenn ja, sofort empfangen. Der Einsprung geht nur, wenn r9 nicht verändert wurde. 
                         ; Catch it immediately ! This entry point is only possible if r9 is not changed.
  pop r10                 
  pop r9
                          ; Next packet now in line ?
  bit.b #usbplus, &P2IFG  ; Kommt bereits das nächste Paket ? (Oder umgekehrt, um Takte zu sparen ?)
  jc USB_Sync             ; Wenn ja, sofort empfangen  If yes, catch it. Else handle it with another interrupt. 
  reti                    ; Sonst ausspringen und in einem neuen Interrupt bearbeiten.

        
          ; Rücksprung in die Synchronisation ! Das neue Paket beginnt ziemlich bald.
          ; Mindestwartezeit: 2 Bitzeiten = 20 Takte. Weiterhin kann ich die ersten Bits des Syncs überspringen...
          ; Muss hier noch Proben einfügen, ob gleich ein nächstes Paket kommt, was bei Setup und Out der Fall ist.
          ; Bei IN habe ich 6.5 oder 7.5 Bitzeiten Ruhe, und somit noch ein bisschen Luft.

          ; Next paket may follow in 2 bittimes = 20 cycles.
          ; If I have to reply, I have 6.5 to 7.5 bittimes maximum until I have to transmit my answer.
          ; So I should check first if I have to receive some more...
          ; Note that we can take up some time of the sync pattern, as we need only the second half "0100" of it...

