/* Firmware für ATmega8 im USB2LPT 1.6, haftmann#software 11/10
 * basierend auf: »usbdrv« von Objective Development GmbH (Österreich),
 * Beispieldatei von Henning Paul
 * Lizenz: GNU GPL v2 (see License.txt)
 * Zu übersetzen und zu brennen mit zugehörigem »makefile«,
 * bspw. »make« zum Kompilieren, »make flash« zum Brennen (ggf. mit Kompilation),
 * (einmalig pro Chip) »make fuse« zum Setzen der Sicherungen (Taktquelle u.ä.)
 * Das Paket »winavr« ist erforderlich!
 * Alle Schiebeoperationen (>>, <<) sind wegen Compiler-Umständen (int-Cast)
 * in Schiebe-Zuweisungen (>>=, <<=) umgesetzt;
 * dito auch & und | bei Tests (bspw. bei »if«) mit mehr als einem Bit.
 * Das Schlüsselwort »inline« scheint unnötig, gcc hat eigenen Kopf.
 *
 * Mit dem eingesparten Quarz handelt man sich Probleme bei schwankender
 * Speisespannung ein! Also: Hiermit nur geringe Lasten treiben!
 * An manchen PCs ist die 5V so versaut, dass USB2LPT 1.6 nicht funktioniert.
 *
*101128	Mittels Option "-nostartfiles" wird Startup-Code minimiert.
	4 KByte (für ATmega48) bleiben illusorisch.
+101213	HID-Interface. Wegen Win64 und Treiberproblem.
 * 
 */

// Aus »winavr«
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <string.h>	// memcpy

// Aus »usbdrv«
#include "usbdrv.c"

// Firmware-Datum (Meldung bei A0-Request mit wData==6 und wLength==2)
#define DATEYEAR	2010
#define DATEMONTH	12
#define DATEDAY		13

/************************
 * Hardware:
 *
 * PortB = (zumeist) Steuerport
 *[12] ICP   0 = USB D- (sowie Interrupt und Pullup 10k nach 5P)
 *[13] OC1   1 = USB D+
 *[14] SS    2 = /STB - /C0 (1)
 *[15] MOSI  3 = /AF  - /C1 (14)
 *[16] MISO  4 = /INI -  C2 (16)
 *[17] SCK   5 = /SEL - /C3 (17)
 * [8] XTAL2 6 = Quarz, nunmehr frei (wird per internem Pullup high gezogen...)
 * [7] XTAL1 7 = Quarz, nunmehr frei (...um erhöhter Stromaufnahme vorzubeugen)
 * Schade, jetzt hätte man ein zweites 8-bit-Port wie beim USB2LPT 1.4 und 1.7
 * herausführen können (Verzicht auf LED /oder/ RESET).
 * Aber die 500 Leiterplatten sind gefertigt...
 *
 * PortC = (zumeist) Statusport
 *[23] ADC0  0 = /LED (low-aktiver Ausgang)
 *[24] ADC1  1 = /ERR -  S3 (15)
 *[25] ADC2  2 =  ONL -  S4 (13)
 *[26] ADC3  3 =  PE  -  S5 (12)
 *[27] ADC4  4 = /ACK -  S6 (10)
 *[28] ADC5  5 =  BSY - /S7 (11)
 *[29] RESET 6 = /Reset (kein E/A-Anschluss) - Lötbrücke nach ONL S4 (13)
 *
 * PortD = Datenport
 *[30] RxD   0 = D0 (2)
 *[31] TxD   1 = D1 (3)
 *[32] INT0  2 = D2 (4)
 * [1] INT1  3 = D3 (5)
 * [2] T0    4 = D4 (6)
 * [9] T1    5 = D5 (7)
 *[10] AIN0  6 = D6 (8)
 *[11] AIN1  7 = D7 (9)
 *
 * In eckigen Klammern: ATmega8-Pinnummer (TQFP-Gehäuse)
 * In runden Klammern: Pinnummer SubD25-Buchse
 * Übrige Pins:
 *[4][6][18] - Betriebsspannung (5V, nicht 3,3V, wegen Ausgabe auf SubD)
 *[3][5][21] - Masse
 *[19][20][22] - zusätzliche A/D-Wandler-Eingänge; A/D-Referenzspannung
 *(18)..(24) - Masse
 *(25) - Masse oder umlötbar 5V
 *
 * Die Zuordnung zu den Portpins erfolgte nach der Maßgabe,
 * Portadressen nicht aufzuteilen, um bei Ausgaben mit zwei Pegelwechseln
 * diese Pegelwechsel exakt gleichzeitig erscheinen zu lassen.
 * Zur Ausrichtung von Ein-Ausgabedaten genügen Schiebebefehle.
 * Damit war PortD als Datenport zwangsweise festgelegt, und INT0 (INT1)
 * nicht für USB nutzbar.
 ************************/

#define FATDATE (((DATEYEAR)-1980)*512+(DATEMONTH)*32+(DATEDAY))

inline __attribute((naked,section(".ctors"))) void __vectors() {
 asm volatile(
"	ldi	r30,0x5F\n"	// initialize stack
"	ldi	r31,0x04\n"
"	rjmp	main\n"
#ifdef PCINT0_vect
"	rjmp	__vector_3\n"::);	// das FAT-Datum wird simuliert
#else
"	.byte	%0,%1\n"	// hier liegt das FAT-Datum
"	.byte	'h','s'\n"	// 2 ungenutzte Bytes
"	rjmp	__vector_5\n"
	:
	:"M" (FATDATE&0xFF),
	 "M" ((FATDATE>>8)&0xFF)
	);
#endif
}
void __do_copy_data(void) __attribute__((naked));
void __do_copy_data(void) {}	// no non-zero-initialized static data

void __do_clear_bss(void) __attribute__((naked));
void __do_clear_bss(void) {}	// no static data at all
int main(void) __attribute__((noreturn,naked));
 
/* Parallelport */
static uchar data_byte;
static uchar DCR;	// Device Control Register (SPP +2)
#define DIRECTION_INPUT (DCR&0x20)
static uchar ECR;	// Extended Control Register (ECP +402)
static uchar ECR_Bits;	// 1-aus-n-Code der höchsten 3 Bits aus ECR
static uchar EppTimeOut;// Bit0 = 0: kein Timeout aufgetreten
			// Bit2 = 0: Interrupt aufgetreten (zz. ungenutzt)
			// Alle anderen Bits müssen =1 sein!
static uchar Feature;	// Bit0 = Offene-Senke-Simulation für Daten (SPP +0)
			// Bit2 = Offene-Senke-Simulation für Steuerport (+2)
			// Bit6 = DirectIO (keine Invertierungen)
/* FIFO */
#define FIFOSIZE 16	// muss laut Programmlogik Vielfaches von 2 sein
static unsigned Fifo[FIFOSIZE] __attribute((section(".noinit")));
				//wegen ECP brauchen wir 9 bit Breite
static uchar fifor, fifow;	//Indizes

/* sonstiges */
static uchar Led_T;	// "Nachblinkzeit" der LED in ms, 0 = LED blinkt nicht
static uchar Led_F;	// "Blinkfrequenz" in ms (halbe Periode)
static uchar Led_C;	// Blink-Zähler (läuft alles über SOF-Impuls)

// Ein WORD in FIFO schreiben - nichts tun wenn FIFO voll
static void PutFifo(unsigned w) {
 uchar idx, ecr = ECR;		// mit Registern (nicht RAM) arbeiten
 if (ecr & 2) return;		// nichts tun wenn FIFO voll
 idx = fifow;			// in Register holen
 Fifo[idx++] = w;		// einschreiben
 idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = 0;
 ecr &= ~1;			// FIFO nicht leer
 if (idx == fifor) ecr |= 2;	// FIFO voll
 fifow = idx;			// Register rückspeichern
 ECR = ecr;
}

// ein WORD aus FIFO lesen - letztes WORD liefern wenn FIFO leer
// (Ein echtes ECP tut das auch!)
static unsigned GetFifo(void) {
 uchar idx = fifor, ecr = ECR;	// mit Registern (nicht RAM) arbeiten
 unsigned w;
 if (ecr & 1) {			// bei leerer FIFO vorhergehendes WORD liefern
  idx--;
  idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = FIFOSIZE - 1;
  w = Fifo[idx];
 }else{
  w = Fifo[idx++];
  idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = 0;
  ecr &= ~2;			// FIFO nicht voll
  if (fifow == idx) ecr |= 1;	// FIFO leer
  fifor = idx;			// Register rückspeichern
  ECR = ecr;
 }
 return w;
}

// Datenrichtungswechsel von DCR Bit 5 wirksam werden lassen,
// dabei Simulation der Offenen Senke (Open Collector) beachten
static void LptSetDataDir(void) {
 if (DIRECTION_INPUT) {
  DDRD  = 0x00;		// alles EINGÄNGE
  PORTD = 0xFF;		// alle Pull-Ups EIN
 }else{
  PORTD = data_byte;	// Datenbyte ausgeben
  DDRD  = Feature & 0x01 ? ~PORTD : 0xFF;
 }
}

// Aktivieren der Parallelschnittstelle bei USB-Aktivität
// oder Rückstellen beim Löschen des DirectIO-Bits im Feature-Register
static void LptOn(void) {
 uchar t;
 LptSetDataDir();	// Datenport: Ausgänge (oder nur Pullups)
 DDRC  = 0x01;		// Statusport: nur die LED ist Ausgang
 PORTC = 0x3E;		// Statusport: fünf Pullups
 t = DCR;
 t ^= 0x0B;
 t <<= 2;
 PORTB = t | 0xC0;
 DDRB  = 0x3C;		// USB-Anschlüsse bleiben Eingänge
}

// <strobe>-Low-Bits auf Steuerport ausgeben und max. 10 µs auf WAIT=H warten
// Liefert Daten eines READ-Zyklus'
// Ausgabedaten müssen vorher auf PORTD gelegt werden.
// Bei Fehler wird das TimeOut-Bit gesetzt
// (Gelöscht wird es durch Schreiben einer Null aufs Statusport.)
static uchar epp_io(uchar strobe) {
 uchar i, saveoe = DDRD, save = PORTB;
 if (PINC & 0x20) goto n_ok;	// BUSY
 PORTB = save & strobe;	// ASTROBE bzw. DSTROBE sowie ggf. WRITE auf LOW
 if (!(strobe & 0x04)) DDRD = 0xFF;	// Ausgabe (jetzt erst)
 i = 24; do{
  if (PINC & 0x20) goto okay;	// sbic PINC,5; rjmp okay (2)
 }while (--i);			// dec r18; brne l1 (3)
n_ok:
 EppTimeOut |= 0x01;		// TimeOut markieren
okay:
 i = PIND;			// Daten einlesen (nur für INPUT relevant:-)
 if (!(strobe & 0x04)) DDRD = saveoe;
 PORTB = save;
 return i;
}

// === OUT auf Adresse +0 (Datenport) ===
static void out0(uchar b) {
 uchar t = ECR_Bits & 0x4C;	// Compiler macht int-Murks ohne Hilfsvariable
 if (t) PutFifo(b);		// eine FIFO-Betriebsart
 else{
  data_byte = b;
  if (!DIRECTION_INPUT) {
   PORTD = b;
   if (Feature & 1) DDRD = ~b;	// Simulation OC (mit Pullups)
  }
 }
}

// === OUT auf Adresse +1 (Statusport) ===
// Eine Ausgabe erfolgt nur bei DirectIO = 1 oder bei (via out405)
// auf Ausgabe geschalteten Leitungen.
// Ansonsten keine Ausgabe; Pullups bleiben eingeschaltet
static void out1(uchar b) {
 if (ECR_Bits & 0x10 && !(b & 0x01)) EppTimeOut &=~ 0x01;	// TimeOut-Bit
 b >>= 2;
 if (!(Feature & 0x40)) {
  b ^= 0x20;		// BSY-Bit invertieren
  b |= ~DDRC;		// Ausgabe nur bei DirectIO oder vorhandenen Ausgabepins
 }
 b &= 0x3E;
 PORTC = (PORTC & 1) | b;
}

// === OUT auf Adresse +2 (Steuerport) ===
// Bit4=IRQ-Freigabe (nicht unterstützt, aber gespeichert)
// Bit5=Ausgabetreiber-Freigabe
static void out2(uchar b) {
 uchar t = ECR_Bits & 0x05;
 b |= 0xC0;
 if (t) b &=~ 0x20;	// Richtung auf Ausgabe fixieren
 t = b^DCR;	// geänderte Bits
 DCR = b;
 if (!(Feature & 0x40)) {	// kein DirectIO?
  if (t & 0x20) LptSetDataDir();// bei Richtungswechsel
  b ^= 0x0B;			// Invertierungen anpassen
 }
 b <<= 2;
 PORTB = b | 0xC0;
 if (Feature & 0x04) DDRB = ~b & 0x3C;	// USB-DDR-Bits müssen 0 bleiben
}

// === OUT auf Adresse +3 (EPP-Adresse) ===
static void out3(uchar b) {
 PORTD = b;
 epp_io(~0x24);			// AddrStrobe und Write LOW (0xx0)
}

// === OUT auf Adresse +4 (EPP-Daten) ===
static void out4(uchar b) {
 PORTD = b;
 epp_io(~0x0C);			// DataStrobe und Write LOW (xx00)
}

// === OUT auf Adresse +400 (ECP-Daten-FIFO) ===
static void out400(uchar b) {
 uchar t = ECR_Bits & 0x4C;
 if (t) PutFifo(b | 0x100);	// Datenbyte einschreiben
}

// === OUT auf Adresse +402 (ECP-Steuerport) ===
static void out402(uchar b) {	// svw. SetECR
 uchar t;
 ECR = (b & 0xF8) | 0x05;	// FIFOs leeren
 b >>= 5;
 t = 1;
 t <<= b;
 ECR_Bits = t;			// 1-aus-n-Kode setzen
 EppTimeOut = t & 0x10 ? 0xFE : 0xFF;	// Timeout-Bit voreinstellen
 if (t & 0x05) {		// SPP oder SPP-FIFO?
  if (DIRECTION_INPUT) {
   DCR &=~ 0x20;		// Richtung fest auf AUSGABE
   LptSetDataDir();
  }
 }
 fifor = fifow = 0;
}

// === OUT auf Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
static void out404(uchar b) {
 DDRD = b;
}

// === OUT auf Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
static void out405(uchar b) {
 b >>= 2;
 b |= 0xC1;
 DDRC = b;			// LED-Ausgang immer EIN
 if (!(Feature & 0x40)) {	// ohne DirectIO Pullups sicherstellen!
  PORTC |= ~b;
 }
}

// === OUT auf Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
static void out406(uchar b) {
 b <<= 2;
 DDRB = b & 0x3C;		// USB-Anschlüsse stets Eingang
}

// === OUT auf Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
static void out407(uchar b) {
 uchar t;
 b &= 0x45;			// nur unterstützte Bits durchlassen
 t = Feature ^ b;
 Feature = b;
 if (t) {
// BrennFeature()
  if (t & 0x01 && !(b & 0x40) && !DIRECTION_INPUT) {
   DDRD = b & 0x01 ? ~PORTD : 0xFF;
  }
  if (t & 0x04 && !(b & 0x40)) {
   DDRB = b & 0x04 ? ~PORTB & 0x3C : 0x3C;
  }
  if (t & 0x40 && !(b & 0x40)) {	// Portrichtungen wiederherstellen
   LptOn();
  }
 }
}
 
// Der Teufel hat ECP erfunden! Wie soll festgestellt werden, ob in der
// FIFO ein Adress- oder ein Datenbyte liegt?
// Mein "echtes" EC-Port ignoriert beim Rücklesen einfach das neunte Bit.
// === IN von Adresse +0 (Datenport) ===
static uchar in0(void) {
 uchar t = ECR_Bits & 0x4C;
 if (t) t = GetFifo();
 else t = PIND;			// ansonsten stets Portpins lesen
 return t;
}

// === IN von Adresse +1 (Statusport) ===
static uchar in1(void) {
 uchar t = PINC;
 t <<= 2;
 t |= 0x07;
 if (!(Feature & 0x40)) t ^= 0x80;
 t &= EppTimeOut;
 return t;
}

// === IN von Adresse +2 (Steuerport) ===
// Überraschung: Bei einem "genügend neuen" echten Parallelport sind die
// Steuerleitungen gar nicht (mehr) rücklesbar!
// Hier wird diese Einschränkung nur in den 3 FIFO-Betriebsarten nachgeahmt
static uchar in2(void) {
 uchar b = ECR_Bits & 0x4C;
 b = b ? PORTB : PINB;
 b >>= 2;
 b &= 0x0F;
 if (!(Feature & 0x40)) b ^= 0x0B;
 b |= DCR & 0xF0;
 return b;
}

// === IN von Adresse +3 (EPP-Adresse) ===
static uchar in3(void) {
 return epp_io(~0x20);		// AddrStrobe LOW (0xxx)
}

// === IN von Adresse +4 (EPP-Daten) ===
static uchar in4(void) {
 return epp_io(~0x08);		// DataStrobe LOW (xx0x)
}

// === IN von Adresse +400 (ECP-FIFO) ===
static uchar in400(void) {
 uchar t = ECR_Bits & 0xCC;
 if (!t) return 0xFF;
 if (t & 0x80) return 0x10;	// Konfigurationsregister A (konstant)
 return GetFifo();
}

// === IN von Adresse +401 (ECP-???) ===
static uchar in401(void) {
 return ECR_Bits & 0x80 ? 0 : 0xFF;	// Konfigurationsregister A (0)
}

// === IN von Adresse +402 (ECP-Steuerport) ===
static uchar in402(void) {
 return ECR;
}

// === IN von Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
static uchar in404(void) {
 return DDRD;
}

// === IN von Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
static uchar in405(void) {
 uchar t = DDRC;
 t <<= 2;
 t &= 0xF8;
 return t;
}

// === IN von Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
static uchar in406(void) {
 uchar t = DDRB;
 t >>= 2;
 t &= 0x0F;
 return t;
}

// === IN von Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
static uchar in407(void) {
 return Feature;
}

// === WARTEN (blockieren) in 4-µs-Stückelung ===
static void wait(uchar us) {
 if (us) do{
  uchar t = 15;	// ldi R17,imm (1 Takt verschwindet im nicht ausgeführten brnz)
  asm("label: dec %0\n brne label" :  : "r" (t) : "cc");
//while (--t);		// dec r25; brne lbl (3) *15 = (45)
 }while (--us);		// dec R16; brnz lbl (3) --> (48)
}

// Zyklischer Aufruf; Emulation der SPP-Ausgabefifo
static void SppXfer(void) {
 if (ECR & 1) return;		// bei leerer FIFO nichts tun
 if (PINC & 0x20) return;	//  BSY = H: nichts tun
 PORTD = GetFifo();
 PORTB &= ~0x04;		// /STB = L
 wait(1);
 PORTB |=  0x04;		// /STB = H
}

// Zyklischer Aufruf; Emulation der ECP-Ein/Ausgabefifo
static void EcpXfer(void) {
 unsigned w;
 if (DIRECTION_INPUT) {		// FIFO-Eingabe
  if (ECR & 2) return;		// bei voller FIFO nichts tun
  if (!(PORTB & 0x08)) {	//  /AF = L: Byte einlesen?
   if (PINC & 0x10) return;	// /ACK = H: kein Byte von außen vorhanden
   PORTB |= 0x08;		//  /AF = H setzen
  }				//  /AF = H: hier zweite Handshake-Phase
  if (!(PINC & 0x10)) return;	// /ACK = L: nichts tun!
  w = PIND;
  if (PINC & 0x20) w|=0x0100;	//  BSY = Command(0) / Data(1)
  PutFifo(w);
  PORTB &= ~0x08;		//  /AF = L setzen
 }else{				// FIFO-Ausgabe
  if (PORTB & 0x04) {		// /STB = H: Byte ausgeben?
   uchar t;
   if (ECR & 1) return;		// bei leerer FIFO nichts tun
   if (PINC & 0x20) return;	//  BSY = H: nichts tun!
   w = GetFifo();		// Adress- oder Datenbyte lesen
   PORTD = (uchar)w;		// Byte anlegen
   t = PORTB & 0x34;
   if (w & 0xFF00) t |= 0x08;	// AF setzen oder nicht
   PORTB = t;
   PORTB &= ~0x04;		// /STB = L setzen
  }				// /STB = L: hier zweite Handshake-Phase
  if (!(PINC & 0x20)) return;	//  BSY = L: nichts tun!
  PORTB |= 0x04;		// /STB = H setzen
 }
}

// LED starten mit Blinken, t = Periodendauer
void Led_Start(uchar t) {
 if (!Led_T) {
  Led_C = t;
  PORTC |= 0x01;	// LED ausschalten
 }
 Led_T = Led_F = t;
}

// LED-Zustand alle 1 ms (SOF) aktualisieren
static void Led_On1ms(void) {
 uchar led_t = Led_T, led_c;
 if (!led_t) return;
 led_c = Led_C;		// in Register laden
 if (!--led_c) {
  PORTC ^= 0x01;	// LED umschalten
  led_c = Led_F;	// Zähler neu laden
 }
 if (!--led_t) PORTC &= ~0x01;	// LED einschalten
 Led_T = led_t;		// Register rückschreiben
 Led_C = led_c;
}

static void initIOPorts(void) {
 data_byte = 0x00;
 DCR = 0xCC;
}

// Ausgabedaten bearbeiten und Ergebnisbytes in <buf> aufsammeln,
// liefert Anzahl der nach <buf> geschriebenen Bytes.
// Behandelt auch über Puffergrenzen hinauslaufende 2-Byte-Kommandos
static uchar ProcessInOut(const uchar *data, uchar len, uchar *buf) {
 static uchar PendingByte;
 uchar *InData = buf, command, value;
 if (!len) return 0;
 Led_Start(100);	// schnelles Blinken (5 Hz)
 do{
  command = PendingByte;
  if (command) {
   command &= ~0x10;	// eh' nur 2-Byte-Ausgabekommando
   PendingByte = 0;
   len++;
  }else command = *data++;
  if (command & 0x10) {	// IN-Kommandos
   value = 0xFF;
   if (command==0x10) value = in0();
   else if (command==0x11) value = in1();
   else if (command==0x12) value = in2();
   else if (command==0x13) value = in3();
   else if (command>=0x14
          && command<0x18) value = in4();
   else if (command==0x18) value = in400();
   else if (command==0x19) value = in401();
   else if (command==0x1A) value = in402();
   else if (command==0x1C) value = in404();
   else if (command==0x1D) value = in405();
   else if (command==0x1E) value = in406();
   else if (command==0x1F) value = in407();
   *InData++ = value;	// IN-Kommandos erzeugen Antwort-Bytes
  }else{		// OUT-Kommandos mit Folgebyte
   if (!--len) {	// Wird erst beim nächsten ProcessInOut verarbeitet
    command |= 0x10;
    PendingByte = command;	// merken für Fortsetzung
    break;		// mit len==0 aus Schleife ausbrechen
   }
   value = *data++;
   if (command==0x00) out0(value);
   else if (command==0x01) out1(value);
   else if (command==0x02) out2(value);
   else if (command==0x03) out3(value);
   else if (command>=0x04
          && command<0x07) out4(value);
   else if (command==0x08) out400(value);
   else if (command==0x0A) out402(value);
   else if (command==0x0C) out404(value);
   else if (command==0x0D) out405(value);
   else if (command==0x0E) out406(value);
   else if (command==0x0F) out407(value);
   else if (command==0x20) wait(value);
  }
 }while (--len);
 return InData - buf;	// Geschriebene Bytes liefern
}

// Antwort-Puffer (zurzeit nicht überlauf-geschützt!)
static uchar ReplyBuf[65] __attribute((section(".noinit")));
#define gReplyLen ReplyBuf[0]	// ist gleich Report-ID 1..7
#define gReplyBuf (ReplyBuf+1)

static uchar gbRequest;		// Kopie vom letzten SETUP-Paket
static size_t gAdr, gLen __attribute((section(".noinit")));

//-090624: Ohne diese Maßnahme gelangen Daten „in den falschen Hals“
static uchar ExpectEP1OutToken/*=USBPID_DATA0*/;

USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
 static uchar replyBuf[2] __attribute((section(".noinit")));
 
 gbRequest = data[1];		// SETUPDAT merken (wie Cypress-Controller)
 gAdr = ((usbRequest_t*)data)->wValue.word;
 gLen = ((usbRequest_t*)data)->wLength.word;

// Rudiment von Henning Paul, für seinen Linux-Treiber
 usbMsgPtr = replyBuf;
// SetFeature -> Stall -> EP1OUT: Data-Toggle rücksetzen
 if (data[0]==0x02 && data[1]==0x03 && data[2]==0x00 && data[4]==0x01) {
  ExpectEP1OutToken=USBPID_DATA0;
 }else if (data[0]==0x21 && data[1]==USBRQ_HID_SET_REPORT && data[4]==0x01) {
  return 0xFF;		// usbFunctionWrite() kümmert sich 
 }else if (data[0]==0xA1 && data[1]==USBRQ_HID_GET_REPORT && data[4]==0x01
   && gReplyLen) {
  usbMsgPtr = &gReplyLen;	// Report-ID = Länge, danach Datenbytes
  return gReplyLen+1;	// Anzahl Bytes 
 }else if (data[0] == 0xC0) {	// IN, Vendor, Device
// Ehemalige BULK-Transfers
  if (data[1] >= 0x90 && data[1] <= 0x94) {
// bis zu 4 Bytes von SETUPDAT als OUT-Daten verarbeiten
// (Speed! - Für übliche geringe Ausgabemengen kein extra SETUP-Transfer)
   gReplyLen += ProcessInOut(data+2, data[1]-0x90, gReplyBuf+gReplyLen);
   gbRequest = 0xA1;		// vom RAM lesen (lassen)
   gAdr = (size_t)gReplyBuf;	// feste Speicheradresse
   if (gLen > gReplyLen) gLen = gReplyLen;
   gReplyLen = 0;		// für's nächste ProcessInOut sei gReplyBuf leer
// Cypress' EZUSB-kompatible Routinen (VendAx.hex)
  }else if (data[1] >= 0xA1 && data[1] <= 0xA3) {	// lesen
#ifdef PCINT0_vect
// im Sonderfall Adresse=6 und Länge=2 wird das Datums-WORD (FAT) geliefert
   if (data[1] == 0xA3 && gAdr == 6 && gLen == 2) {
    ((usbWord_t*)replyBuf)->word = FATDATE;
    return 2;
   }
#endif
   return 0xFF;			// usbFunctionRead() kümmert sich
  }
 }else if (data[0] == 0x40) {	// OUT, Vendor, Device
  if (data[1] >= 0x90 && data[1] <= 0x94) {
// bis zu 4 Bytes von SETUPDAT als OUT-Daten verarbeiten (Bandbreite sparen)
   gReplyLen += ProcessInOut(data+2, data[1]-0x90, gReplyBuf+gReplyLen);
   gbRequest = 0x90;	// alle weiteren OUT-Daten in usbFunctionWrite abarbeiten lassen
  }else if (data[1] == 0xA1 || data[1] == 0xA2) {	// RAM oder EEPROM schreiben
   return 0xFF;		// usbFunctionWrite() kümmert sich
  }
#if 1
 }else	// Ballast!! Welchen Wert hat bmRequestType?
     if(data[1] == 0){       /* ECHO */
        replyBuf[0] = data[2];
        replyBuf[1] = data[3];
        return 2;
    }else if(data[1] == 1){       /* READ_REG */
	if (data[2] == 0){
            replyBuf[0] = in0();
            return 1;
	} 
	else if (data[2] == 1){
            replyBuf[0] = in1();
            return 1;
	}
	else if (data[2] == 2){
	    replyBuf[0] = in2();
            return 1;
	}
    }else if(data[1] == 2){       /* WRITE_REG */
	if (data[2] == 0){
		out0(data[4]);
        }
	else if (data[2] == 2){
		out2(data[4]);
        }
#endif
 }
 return 0;
}

// Datenübertragungsfunktionen für Cypress-kompatible "lange" Setup-Transfers
// sowie - neu - für ehemals Bulk-Daten
USB_PUBLIC uchar usbFunctionRead(uchar *data, uchar len) {
 void *adr = (void*)gAdr;	// im Register halten
 if (len > gLen) len = gLen;	// begrenzen auf Restlänge
 if (gbRequest == 0xA1) {	// RAM (oder speziell gReplyBuf) lesen
  memcpy(data, adr, len);
 }else if (gbRequest == 0xA2) {	// EEPROM lesen
  eeprom_read_block(data, adr, len);
 }else if (gbRequest == 0xA3) {	// Flash lesen
  memcpy_P(data, (prog_char*)adr, len);
 }else return 0xFF;		// Fehler: nichts zu liefern! (STALL EP0)
 gAdr = (size_t)adr + len;
 gLen -= len;
 return len;
}

USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) {
 void *adr = (void*)gAdr;	// im Register halten
 if (len > gLen) len = gLen;	// begrenzen auf Restlänge
 if (gbRequest == 0xA1) {	// RAM schreiben
  memcpy(adr, data, len);
  adr += len;
 }else if (gbRequest == 0xA2) {	// EEPROM schreiben
  uchar ll;
  for (ll = 0; ll < len; ll++) {
   eeprom_write_byte(adr, *data++);	// statt eeprom_write_block()
   wdt_reset();			// nach jedem Byte Watchdog beruhigen
   adr++;
  }
 }else if (gbRequest == 0x90) {	// OUT-Daten über EP0 (Vista/Linux-Kompatibilität)
  gReplyLen += ProcessInOut(data, len, gReplyBuf+gReplyLen);
 }else if (gbRequest == USBRQ_HID_SET_REPORT) {
  gReplyLen = ProcessInOut(data+1, data[0], gReplyBuf);
 }else return 0xFF;		// Fehler: unerwartete Daten (STALL EP0)
 gAdr = (size_t)adr;
 gLen -= len;
 return len;
}

USB_PUBLIC void usbFunctionWriteOut(uchar *data, uchar len) {
 uchar tmp=ExpectEP1OutToken;
 if (usbCurrentDataToken==tmp) {
  ExpectEP1OutToken=tmp^USBPID_DATA0^USBPID_DATA1;
  uchar InDataLen = ProcessInOut(data, len, gReplyBuf);
  if (InDataLen) usbSetInterrupt(gReplyBuf, InDataLen);	// Antwort zum Host
 }
}

uchar lastTimer0Value;	// erforderlich in "libs-device/osctune.h"
//extern uchar usbTxLen;
extern uchar usbMsgLen;

int main(void) {
// continue initialization that does not fit into interrupt vector table
 asm volatile(
"	out	0x3E,r31\n"	// SPH
"	out	0x3D,r30\n"	// SPL
"	clr	r1\n"
"l1:	st	-Z,r1\n"	// clear entire BSS
"	cpi	r30,0x60\n"	// this code is ATmega8 specific
"	cpc	r31,r1\n"
"	brne	l1");
// initialize two bytes of data declared inside USBDRV.C,
// because there is no data copy routine (this here is shorter)
 usbTxLen = USBPID_NAK;	
 usbMsgLen = USB_NO_MSG; 
 ExpectEP1OutToken=USBPID_DATA0;

 uchar SofCmp = 0;
#ifdef PCINT0_vect	// hier genutztes wesentliches Merkmal der ATmegaX8
 MCUSR = 0;
 wdt_disable();		// Watchdog-Zustand überlebt Reset! Ausschalten!
 PRR = 0xCF;		// getaktete Peripherie außer Timer0 totlegen
 ACSR |= 0x80;		// Analogvergleicher ausschalten - 70 µA Strom sparen
 TCCR0B = 0x03;		// Vorteiler 64 (für Synchronisation benötigt)
 usbInit();		// Pegelwechsel-Interrupt aktivieren
 sei();
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 if (USBIN & USBMASK) {	// kein SE0?
  sleep_cpu();		// Ohne Oszillator schlafen bis zum Pegelwechsel
 }			// warten bis SE0
 set_sleep_mode(SLEEP_MODE_STANDBY);	// kein ...IDLE weil keine Peripherie
 wdt_enable(WDTO_15MS);	// Watchdog ist Strom sparender als ein Zeitgeber,
			// ein 3-ms-Zeitgeber wäre aber die USB-konforme Lösung
#else		// ATmega8-Kode: Polling per Watchdog-Timer (WDT-Fuse gesetzt)
 uchar mcucsr = MCUCSR;
 MCUCSR = 0;
 ACSR |= 0x80;		// Analogvergleicher ausschalten - 70 µA Strom sparen
 TCCR0 = 0x03;		// Vorteiler 64 (für Synchronisation benötigt)
 sleep_enable();	// normaler Schlafmodus
 if (mcucsr & (1 << WDRF)) {
  if (USBIN & USBMASK) {// kein SE0?
   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
   sleep_cpu();		// Schlafen ohne sei() - bis zum nächsten Watchdog
  }			// Gemessene mittlere Stromaufnahme: 500 µA (hurra!)
 }
 usbInit();
 sei();
#endif
 Feature = eeprom_read_byte((uchar*)0xFFFB); // letztes Byte vor Seriennummer
 if (Feature == 0xFF) Feature = 0;	// ungebrannt als 0 annehmen
 { uchar osccal = eeprom_read_byte((uchar*)0xFFFA);
   if (osccal && osccal!=0xFF) OSCCAL = osccal;
 }	// beschleunigt auf 12,8 MHz ziehen, außer bei erster Inbetriebnahme
 initIOPorts();
 LptOn();
 out402(0x20);		// bidirektionalen PS/2-Modus voreinstellen
  
 for(;;) {		// Hauptschleife
  uchar t = ECR_Bits;
  if (t & 0x04) SppXfer();		// SPP-FIFO-Transfer prüfen
  else if (t & 0x08) EcpXfer();		// ECP-FIFO-Transfer prüfen
  else if (USBIN & USBMASK) sleep_cpu();// Schlafen, außer bei SE0
  t = usbSofCount;
  if (SofCmp != t || !(USBIN & USBMASK)) {
   wdt_reset();		// alle 1 ms (oder öfter bei SE0) Watchdog beruhigen
  }
  usbPoll();
  if (SofCmp != t) {	// SOF eingetroffen?
   SofCmp = t;
   Led_On1ms();		// LED blinken lassen
   if (eeprom_is_ready()) {	// Feature-Byte im EEPROM nachführen
    if (eeprom_read_byte((uchar*)0xFFFB) != Feature)
      eeprom_write_byte((uchar*)0xFFFB, Feature);
    else if (eeprom_read_byte((uchar*)0xFFFA) != OSCCAL)
      eeprom_write_byte((uchar*)0xFFFA, OSCCAL);	// OSCCAL nachführen
   }
  }
 }
}

/*** Untersuchung Stromverbrauch USB-Standby (und Normalbetrieb) ***
 * ATmega8:
 Wie erwartet läuft alle 15 ms der RC-Oszillator kurz an, um den Pegel
 bei ICP zu prüfen. Die Länge des USB-Resets (SE0) ist hier unkritisch,
 weil der interne Oszillator schnell hochläuft.
 Der mittlere Stromverbrauch liegt bei < 500 µA, also < 300 µA des ATmega8
 und 200 µA des Pullups.
 Im Normalbetrieb beträgt die Stromaufnahme datenblattgerechte 7 mA.
 * ATmegaX8:
 ATmega48 nicht untersucht; die Firmware ist bereits größer als 4 KB.
*/

#define W(x) (x)&0xFF,(x)>>8

PROGMEM char usbDescriptorConfiguration[80]={
	9,	//bLength
	2,	//bDescriptorType	2=CONFIG
	W(80),	//wTotalLength		!!55
	2,	//bNumInterfaces	!!1
	1,	//bConfigurationValue (willkürliche Nummer dieser K.)
	0,	//iConfiguration	ohne Text
	0x80,	//bmAttributes (Busversorgt, kein Aufwecken)
	100/2,	//MaxPower (in 2 Milliampere)	100 mA
//Interface-Beschreiber 0, Alternative 0:
	9,	//bLength
	4,	//bDescriptorType	INTERFACE
	0,	//bInterfaceNumber
	0,	//bAlternateSetting
	2,	//bNumEndpoints
	-1,	//bInterfaceClass	hersteller-spezifisch
	0,	//bInterfaceSubClass	(passt in keine Klasse)
	0,	//bInterfaceProtocol
	0,	//iInterface		ohne Text
//Enden-Beschreiber C0I0A0:Bulk EP1OUT
	7,	//bLength
	5,	//bDescriptorType	ENDPOINT
	1,	//bEndpointAddress	EP1OUT
	2,	//bmAttributes		BULK
	W(8),	//wMaxPacketSize
	0,	//bInterval		keine Bedeutung bei BULK
//Enden-Beschreiber C0I0A0:Bulk EP1IN
	7,	//bLength
	5,	//bDescriptorType	ENDPOINT
	0x81,	//bEndpointAddress	EP1IN
	2,	//bmAttributes		BULK
	W(8),	//wMaxPacketSize
	0,	//bInterval		keine Bedeutung bei BULK
// Zweite Alternative: Interrupt-Endpoints statt Bulk (für Linux)
//Interface-Beschreiber 0, Alternative 1:
	9,	//bLength
	4,	//bDescriptorType	INTERFACE
	0,	//bInterfaceNumber
	1,	//bAlternateSetting
	2,	//bNumEndpoints
	-1,	//bInterfaceClass	hersteller-spezifisch
	0,	//bInterfaceSubClass	(passt in keine Klasse)
	0,	//bInterfaceProtocol
	0,	//iInterface		ohne Text
//Enden-Beschreiber C0I0A1:Interrupt EP1OUT
	7,	//bLength
	5,	//bDescriptorType	ENDPOINT
	1,	//bEndpointAddress	EP1OUT
	3,	//bmAttributes		INTERRUPT
	W(8),	//wMaxPacketSize
	10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
//Enden-Beschreiber C0I0A1:Interrupt EP1IN
	7,	//bLength
	5,	//bDescriptorType	ENDPOINT
	0x81,	//bEndpointAddress	EP1IN
	3,	//bmAttributes		INTERRUPT
	W(8),	//wMaxPacketSize
	10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
//Interface-Beschreiber 1, Alternative 0:
	9,	//bLength
	4,	//bDescriptorType	INTERFACE
	1,	//bInterfaceNumber
	0,	//bAlternateSetting
	1,	//bNumEndpoints
	3,	//bInterfaceClass	HID
	0,	//bInterfaceSubClass	(ohne Spezifizierung)
	0,	//bInterfaceProtocol
	0,	//iInterface		ohne Text
//HID-Beschreiber (musste an Offset 18 liegen - deshalb usbdrv.c geändert!)
	9,	// bLength
	0x21,	// bDescriptorType	HID
	1,1,	// BCD representation of HID version
	0,	// target country code
	1,	// number of HID Report Descriptor infos to follow
	0x22,	// descriptor type: report
	W(15+7*9),	// total length of report descriptor
//Enden-Beschreiber C0I1A0:Interrupt EP3IN (Dummy?)
	7,	//bLength
	5,	//bDescriptorType	ENDPOINT
	0x83,	//bEndpointAddress	EP3IN
	3,	//bmAttributes		INTERRUPT
	W(8),	//wMaxPacketSize
	10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
};

// Für sinnvoll-maximale USB-Performance ist der Report
// nicht größer als die FIFO (8 Bytes) gewählt - inkl. Report-ID
// Das Report-ID gibt die Anzahl der relevanten
// Bytes an, bleiben 1..7 Bytes Nutzdaten
// (bspw. 3 OUT-Befehle und 1 IN-Befehl).
PROGMEM char usbDescriptorHidReport[15+7*9] = {
 0x06, 0x00, 0xff,	// USAGE_PAGE (Generic Desktop)
 0x09, 0x01,		// USAGE (Vendor Usage 1)
 0xa1, 0x01,		// COLLECTION (Application)
 0x15, 0x00,		//   LOGICAL_MINIMUM (0)
 0x26, 0xff, 0x00,	//   LOGICAL_MAXIMUM (255)
 0x75, 0x08,		//   REPORT_SIZE (8 bits)

 0x85, 0x01,		//   REPORT_ID (1)
 0x95, 0x01,		//   REPORT_COUNT (1 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x02,		//   REPORT_ID (2)
 0x95, 0x02,		//   REPORT_COUNT (2 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x03,		//   REPORT_ID (3)
 0x95, 0x03,		//   REPORT_COUNT (3 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x04,		//   REPORT_ID (4)
 0x95, 0x04,		//   REPORT_COUNT (4 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x05,		//   REPORT_ID (5)
 0x95, 0x05,		//   REPORT_COUNT (5 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x06,		//   REPORT_ID (6)
 0x95, 0x06,		//   REPORT_COUNT (6 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0x85, 0x07,		//   REPORT_ID (7)
 0x95, 0x07,		//   REPORT_COUNT (7 Byte)
 0x09, 0x00,		//   USAGE (Undefined)
 0xb2, 0x02, 0x01,	//   FEATURE (Data,Var,Abs,Buf)

 0xc0,			// END_COLLECTION
};
