;=============================================================================
; s_i2cbusIO.s: Assembler-Version der KICK-Pascal Prozedur i2cbusIO (seriell)
; Parameterübergabe (auf dem Stack):
;  12(A7).B: logische Busadresse           -> D0
;   8(A7).L: Zeiger auf den I/O-Puffer     -> A0
;   6(A7).W: Anzahl zu übertragender Daten -> D1
;   4(A7).W: Zeitschleifen-Durchgänge      -> D5
; Rückgabewert: 
;   D0.W: Status, 0 = OK, 1 = Fehler, 2 = Fehler beim Adressbyte
;-----------------------------------------------------------------------------
; Funktionsweise:
; Startet den I²C-Bus und spricht den in D0 bezeichneten Chip an. Wenn D1
; negativ ist, werden -D1 Bytes vom Bus geholt und ab Adresse A0 im Speicher 
; abgelegt, sonst +D1 Bytes ab A0 über den Bus abgeschickt. Die Busadresse 
; (D0) wird dabei automatisch zur Schreib- bzw. Leseadresse gemäß I²C-Bus-
; Konvention korrigiert. Abschließend wird der I²C-Bus wieder gestoppt.
; Anmerkungen:
; 1. Die über D5 bestimmte Zeitschleife soll den Bustakt bremsen, wobei
;    maximal 100 kHz zulässig sind, "zu langsam" gibt es dagegen am I²C-Bus 
;    NICHT. Auf normalen Amigas sollte D5=0 sein.
; 2. Mehr Bytes zum Lesen anzufordern, als der bereitgestellte Puffer fassen
;    kann, ist ein Fehler, der nicht erkannt werden kann und wahrscheinlich
;    mit einem GURU endet.
;=============================================================================

; interne Registerverwendung:
; D0: Datenbyte, das gesendet oder empfangen wird
; D1: Anzahl zu übertragende Bytes
; D2: Zähler über gesendete Bytes
; D3: Zähler über 8 Bits
; D4: Zähler für Zeitschleife
; D5: Grenze für Zeitschleife
; A0: Pufferzeiger
; A1: Adresse des CIA-Ports

CIABPRA  = $BFD000
CIABDDRA = $BFD200
ClkOutBit  = 6 ; CIAB_COMRTS
ClkInBit   = 4 ; CIAB_COMCTS
DataOutBit = 7 ; CIAB_COMDTR
DataInBit  = 5 ; CIAB_COMCD

DELAY MACRO
  move.w d5,d4
loop\@ dbf d4,loop\@
  ENDM

; Linker-Information:
  xdef s_i2cbusIO

s_i2cbusIO:
; Parameter vom Stack holen:
  move.b 12(a7),d0
  move.l 8(a7),a0
  move.w 6(a7),d1
  move.w 4(a7),a1

test:
; veränderte Register retten: D2,D3,D4,D5
  movem.l d2-d5,-(a7)

; einen der Parameter noch ins richtige Register packen:
  move.w a1,d5

; CIA-Datenrichtungsregister initialisieren
  move.l #CIABDDRA,a1
  bset #ClkOutBit,(a1)
  bset #DataOutBit,(a1)
  bclr #ClkInBit,(a1)
  bclr #DataInBit,(a1)

; Bus in Ruhezustand bringen: DATA=HI, CLK=HI
  move.l #CIABPRA,a1
  bset #DataOutBit,(a1)
  bset #ClkOutBit,(a1)
  
; Bus starten (Protokollverletzung H->L): DATA=LO, CLK=LO
  DELAY
  bclr #DataOutBit,(a1)
  DELAY
  bclr #ClkOutBit,(a1)
  DELAY

; Soll ich senden oder empfangen?
  bclr #0,d0 ; Voreinstellung: Adresse als Sendeadresse
  tst.w d1
  bpl .zweck
    bset #0,d0   ; Adresse als Leseadresse
.zweck:

; Daten senden, mindestens die Adresse, und evtl. <D1> weitere Bytes
; (Adressbyte steht bereits in D0):
  clr.w d2 ; Bytezähler auf Null
SendLoop: ; das in D0 enthaltene Byte über den Bus schicken, MSB zuerst
  moveq.b #7,d3
SBitLoop:
  btst d3,d0
  beq .SL1
  bset #DataOutBit,(a1) ; "1" senden
  bra .SL2
.SL1 bclr #DataOutBit,(a1) ; "0" senden
.SL2: ; Einen CLK-Impuls geben, um das Bit lesen zu lassen
  bset #ClkOutBit,(a1)
  DELAY
  bclr #ClkOutBit,(a1)
  DELAY
  dbf d3,SBitLoop
; 9. CLK-Impuls, um Quittungsbit zu lesen:
  bset #DataOutBit,(a1) ; sonst liest man nur das eigene LO!
  bset #ClkOutBit,(a1)
  DELAY
  btst #DataInBit,(a1)
  bne SendErr ; DATA=HI -> NAK, Übertragung abbrechen
  bclr #ClkOutBit,(a1)
  DELAY
; genug Daten gesendet?
  addq.w #1,d2 ; gesendetes Byte zählen
  cmp.w d1,d2 ; dran denken: Adressbyte wurde mitgezählt
  bgt SendEnd ; ja, fertig (schließt auch den Fall d1<0 ein)
  move.b (a0)+,d0 ; nein, Byte aus Puffer holen
  bra SendLoop
SendErr:
  moveq.w #1,d0 ; Status = unquittiertes Byte
  tst.w d2 ; das wievielte Byte war es?
  bne BusStop ; irgendeins
  moveq.w #2,d0 ; sonst: Status = unquittiertes Adressbyte
  bra BusStop  
SendEnd:
  tst.w d1
  bmi RecvLoop ; es sollen auch noch Bytes empfangen werden
  moveq.w #0,d0 ; Status = OK
  bra BusStop
  
; soviele Bytes empfangen, wie in A1 angegeben:
RecvLoop: ; ein Byte vom Bus holen, MSB zuerst, und in D0 zusammensetzen
  bset #DataOutBit,(a1) ; DATA-Leitung freigeben
  clr.b d0
  moveq.b #7,d3
RBitLoop:
; Einen CLK-Impuls geben, ein Bit lesen:
  bset #ClkOutBit,(a1)
  DELAY
  btst #DataInBit,(a1)
  beq .RL1 ; "0" empfangen, nichts tun
  bset d3,d0 ; "1" empfangen, entsprechendes Bit setzen
.RL1 bclr #ClkOutBit,(a1)
  DELAY
  dbf d3,RBitLoop
  move.b d0,(a0)+ ; Byte im Puffer speichern
; Quittierungsbit senden, "0", außer nach dem letzten Byte:
  addq.w #1,d1 ; empfangenes Byte zählen
  beq .RL2 ; letztes Byte, DATA auf HI lassen
  bclr #DataOutBit,(a1) ; nein, DATA=LO
.RL2: ; CLK-Impuls geben, Quittungsbit lesen lassen:
  bset #ClkOutBit,(a1)
  DELAY
  bclr #ClkOutBit,(a1)
  DELAY
; alle Daten empfangen?
  tst.w d1
  bne RecvLoop ; nein, weitermachen
  moveq.w #0,d0 ; Status: OK

BusStop: ; I²C-Bus stoppen (Protokollverletzung L->H): CLK=HI, DATA=HI
; zunächst noch CLK=LO, DATA=LO sicherstellen:
  bclr #ClkOutBit,(a1)
  bclr #DataOutBit,(a1)
  
  DELAY
  bset #ClkOutBit,(a1)
  DELAY
  bset #DataOutBit,(a1)
  DELAY
  
; gerettete Register zurückholen: D2,D3,D4,D5
  movem.l (a7)+,d2-d5
  rts

  end
  
