/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-  */
/*
 * displaylcd.cpp
 * Copyright (C) 2016 salvi <stefano@salvi.mn.it>
 *
 * hc08pgm 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.
 * 
 * hc08pgm 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/>.
 */

/* Specifiche Display HD44780
 * TcycE = 500ns -> Da fronte di salita a fronte di salita di EN
 * Pweh  = 230nS -> Da salita a discesa di EN
 * Tas   = 40nS  -> Da cambiamento di RS a Salita di EN
 * Tah   = 10 nS -> Da discesa di EN a cambio di RS
 * Tdsw  = 80 nS -> Da cambiamento dato a Discesa di EN
 * Th    = 10 nS -> Da discesa di E a cambio dati
 *
 *  DISPLAY:
 *  DB4       15          PTB0
 *  DB5       14          PTB1
 *  DB6       11          PTB2
 *  DB7       10          PTB3
 *
 *  EN_LCD    3           PTB6                LED Verde
 *  RS_LCD    5           PTA4/OSC2/AD2/KBI4  LED Giallo
 *
 *  IN-CIRCUIT
 *  RESET       8     7   PTA3/RST/KBI3 - (pull-up) S4
 *  IRQ1/VTEST  9     5   PTA2/IRQ/KBI2/TCLK (5V)
 *  MOD2        5     3   PTA4/OSC2/AD2/KBI4 (Pull-Down) - RS_LCD
 *  COMMSERIE   13    2   PTA0/AD0/TCH0/KIB0 (Pull-Up) -> In linea
 *  MOD1        12    4   PTA1/AD1/TCH1/KIB1 (Pull-Up) -> Out audio
 *
 *  S5          2         PTB7
 *  RXD-SCI     6         PTB5
 *  TXD-SCI     7         PTB4
 *
 *  MAPPA PIEDINI:
 *  PTA0        COMMSERIE (IC PU)
 *  PTA1        MOD1 (IC - PU) - S5 R18
 *  PTA2        IRQ1/VTEST (IC 5V)
 *  PTA3        RESET (IC PU)
 *  PTA4        RS_LCD (IC/LCD PD)  0=Instruction Register 1=Data Register
 * (PTA5        Oscillatore)
 *
 *  PRB0        DB4 (LCD)
 *  PTB1        DB5 (LCD)
 *  PTB2        DB6 (LCD)
 *  PTB3        DB7 (LCD)
 *  PTB4        TXD-SCI
 *  PTB5        RXD-SCI
 *  PTB6        EN_LCD (LCD) Sul fronte di discesa legge il dato (prima Alto poi Basso)
 *  PTB7        S5
 *  _____
 *  |
 *  | S3
 *  |
 *  | S4 <-- Reset
 *  |
 *  | S5 <--
 *  |
 *  | S1
 *  |
 *  | S2
 *  |
 *  | [R18  ] <--
 *  |
 *
 * Display:
 * Comandi:
 *
 * Clear      0   0   0   0   0   0   0   1   Clears entire display and
 * display                                    sets DDRAM address 0 in
 *                                            address counter.
 *
 * Return     0   0   0   0   0   0   1   —   Sets DDRAM address 0 in
 * home                                       address counter. Also
 *                                            returns display from being
 *                                            shifted to original position.
 *                                            DDRAM contents remain
 *                                            unchanged. (1,5 mS)
 *
 * Entry      0   0   0   0   0   1   I/D S   Sets cursor move direction
 * mode set                                   and specifies display shift.
 *                                            These operations are
 *                                            performed during data write
 *                                            and read.
 *
 * Display    0   0   0   0   1   D   C   B   Sets entire display (D) on/off,
 * on/off                                     cursor on/off (C), and
 * control                                    blinking of cursor position
 *                                            character (B).
 *
 * Cursor or  0   0   0   1   S/C R/L —   —   Moves cursor and shifts
 * display                                    display without changing
 * shift                                      DDRAM contents.
 *
 * Function   0   0   1   DL  N   F   —   —   Sets interface data length
 * set                                        (DL), number of display lines
 *                                            (N), and character font (F).
 *
 * Set        0   1   ACG ACG ACG ACG ACG ACG Sets CGRAM address.
 * CGRAM                                      CGRAM data is sent and
 * address                                    received after this setting.
 *
 * Set        1   ADD ADD ADD ADD ADD ADD ADD Sets DDRAM address.
 * DDRAM                                      DDRAM data is sent and
 * address                                    received after this setting.
 *
 *            I/D = 1: Increment                      DDRAM: Display data RAM *
 *
 *            I/D = 0: Decrement                      CGRAM: Character generator
 *            S   = 1: Accompanies display shift                RAM
 *            S/C = 1: Display shift                  ACG:      CGRAM address
 *            S/C = 0: Cursor move                    ADD:      DDRAM address
 *            R/L = 1: Shift to the right                  (corresponds to cursor
 *            R/L = 0: Shift to the left                   address)
 *            DL  = 1: 8 bits, DL = 0: 4 bits         AC: Address counter used for
 *            N   = 1: 2 lines, N = 0: 1 line              both DD and CGRAM
 *                     5 × 10 dots, F = 0: 5 × 8 dots      addresses
 *            F   = 1:
 *            BF  = 1: Internally operating
 *            BF  = 0: Instructions acceptable
 *
 * Trasfermiento dati:
 * Interfacing to the MPU
 * The  HD44780U  can  send  data  in  either  two  4-bit  operations  or  one  8-bit  operation,  thus  allowing
 * interfacing with 4- or 8-bit MPUs.
 * * For 4-bit interface data, only four bus lines (DB4 to DB7) are used for transfer. Bus lines DB0 to DB3
 *   are disabled. The data transfer between the HD44780U and the MPU is completed after the 4-bit data
 *   has been transferred twice. As for the order of data transfer, the four high order bits (for 8-bit operation,
 *   DB4 to DB7) are transferred before the four low order bits (for 8-bit operation, DB0 to DB3).
 *   The busy flag must be checked (one instruction) after the 4-bit data has been transferred twice. Two
 *   more 4-bit operations then transfer the busy flag and address counter data.
 * * For 8-bit interface data, all eight bus lines (DB0 to DB7) are used.
 * TRANSAZIONE DI BUS:
 * Sul fronte di DISCESA di En viene leto il BUS.
 *  Se la comunicazione è a 8 bit, il dato è completo, altrimenti 
 *  ogni 2 transazioni viene letto un dato. Il primo inviato è la metà alta.
 * https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
 * 
 * Test:
 * - Se vado avanti a scrivere sulla prima riga in modalità "due linee", 
 *   dopo la cella 40 si posiziona sulla seconda riga
 * - Se vado avanti a scrivere sulla seconda riga, in modalità "due linee",
 *   dopo la cella 40 della seconda riga, inizia di nuovo la prima
 * - Se indirizzo la cella 42 della prima riga, mi trovo sulla 2
 * - Se indirizzo la cella 0x3e (62) della prima riga, mi trovo sulla 22
 * - Se indirizzo la cella 42 della seconda riga, mi trovo sulla 0
 * - Se indirizzo la cella 0x3e della seconda riga, mi trovo sulla 0
 * Quindi gli indirizzi della RAM sono visti contigui e "circolari" in scrittura.
 * Nell'impostazione degli indirizzi, invece, in modalità 2 righe:
 * - gli indirizzi inferiori a 0x40 vengono presi in modulo 40;
 * - gli indirizzi superiori a 104 vengono presi come 40.
 * 
 * NOTA!
 * Da prove fatte, l'indirtizzo limite della prima riga non è 0x40 come indicato
 * in documentazione ma 40 (decimale).
 *
 * DDRAM:
 * 00000000001111111111222222222233333333334444444444555555555566666666667777777777
 * 01234567890123456789012345678901234567890123456789012345678901234567890123456789
 * |------L1------|                        |------L2------|
 * Reale:
 *                                   18,1->*   34,1->*
 *                                   20,1->*         16,1->*(?)
 *                                   24,1->*        39,1->*
 * 42628303234363840424446485052545658606264666870727476780002040608101214161820222
 * Emulato:
 *                                                                          39,1->*                                                               
 * 00204060810121416182022242628303234363840424446485052545658606264666070727476780
 */

#define CURSES_H
#include <curses.h>
#include "displaylcd.h"
#ifndef HCIWINDOW_H
#include "hcwindow.h"
#endif
#include "log.h"

DisplayLCD::DisplayLCD() {
	int r;
	en = false;
	rs = false;
	bus = 0;
	data = 0;
	for (r = 0; r < DDRAM_LEN; r++) {
			ddram[r] = ' ';
	}

	// Reset State
	ddaddres = 0;
	dshift = 0;
	comm4bit = false;
	loByte = false;
	displayOn = false;
	twoLines = false;
}

void DisplayLCD::setBus(unsigned char value) {
//	Log:printf("setBus: %02x", value);
	bus = value; 
}

void DisplayLCD::refresh(WINDOW * win)
{
	int i;
	wcolor_set(win, COLOR_DISPLAY, NULL);
	for (i = 0; i < 16; i++) {
		mvwprintw (win, 1, 21+i, "%c", (displayOn) ? ddram[(dshift + i) % DDRAM_LEN] : ' ');
		mvwprintw (win, 2, 21+i, "%c", (displayOn && twoLines) ? ddram[(dshift + i + SECONDLINE) % DDRAM_LEN] : ' ');
	}
	wcolor_set(win, COLOR_NORMAL, NULL);
	// Visualizza lo stato dell'interfaccia
	// mvwprintw (win, 3, 21, "%02x %02x %s %s %c %c", bus, data, (en) ? "EN" : "en", (rs) ? "RS" : "rs", 
	//  (comm4bit) ? '4' : '8', (comm4bit) ? ( (loByte) ? 'L' : 'H') : 'F');
	// Visualizza lo stato del display
	// mvwprintw (win, 0, 21, "%c %c %2d %2d", (twoLines) ? '2' : '1', (displayOn) ? 'A' : 's', ddaddres, dshift);
	mvwprintw (win, 0, 21, "%c %2d %2d", (displayOn) ? 'A' : 's', ddaddres, dshift);

	mvwprintw (win, 3, 7, messaggio);
	strcpy (messaggio, "              ");
}

void DisplayLCD::setEn(bool value)
{
	if (en && !value) { // Fronte di discesa
		if (comm4bit) {
			Log::printf("Reset EN: Bus %02x Dato %02x byte=%s", bus, value, (loByte) ? "lo" : "hi");
			if (loByte) {
				data = (data & 0xf0) | (bus >> 4 & 0x0f);
				loByte = false;
			} else {
				data = bus;
				loByte = true;
				en = value;
				return; // Non ha ricevuto il dato intero.
			}
		} else {
			Log::printf("Reset EN: Bus %02x Dato %02x 8bit", bus, value);
			data = bus;
			loByte=false;
		}
		if (rs) {
			gotData();
		} else {
			gotCommand ();
		}
	}
	if (!en && value) {
		Log::printf("Set EN");
	}
	en = value;
}

void DisplayLCD::gotData() {
Log::printf("gotData: %02x (%02x)", data, ddaddres);
	ddram[ddaddres++] = data;
	ddaddres %= DDRAM_LEN;
}

void DisplayLCD::gotCommand () {
Log::printf("gotCommand: %02x", data);
	if (data == 1) { // Clear display
		ddaddres = 0;
		for (int i = 0; i < DDRAM_LEN; i++) {
			ddram[i] = ' ';
		}
	} else if ((data & 0xfe) == 0x02) { // Return home
		Log::printf("Return home");
		ddaddres = 0;
		dshift = 0;
	} else if ((data & 0xfc) == 0x04) { // Entry mode set
		// ToDo
	} else if ((data & 0xf8) == 0x08) { // Display on/off control
		Log::printf("Display on/off control - %02x", data & 0x04);
		displayOn = data & 0x04;
	} else if ((data & 0xf0) == 0x10) { // Cursor or display shift
		// ToDo
	} else if ((data & 0xe0) == 0x20) { // Function set
		comm4bit = !(data & 0x10);
		twoLines = data & 0x08;
		Log::printf("Function set - %02x", data);
	} else if ((data & 0xc0) == 0x40) { // Set CGRAM address
		// Unimplemented
	} else if ((data & 0x80) == 0x80) { // Set DDRAM address
		data &= 0x7f;
		if (twoLines) {
			if (data < SECONDADDR) {
				ddaddres = data % SECONDLINE;
			} else {
				ddaddres = data - SECONDADDR + SECONDLINE;
				if (ddaddres > (DDRAM_LEN - DISPLAYCHARS) && ddaddres < DDRAM_LEN) {
					ddaddres = data - (DDRAM_LEN - DISPLAYCHARS) + SECONDLINE;
				} else if (ddaddres >= (SECONDLINE + DISPLAYCHARS)) {
					ddaddres = SECONDLINE;
				}
			}
		} else {
			ddaddres = data % DDRAM_LEN;
		}
		// Log::printf("*** Set DDRAM address: %02x (%dd) -> %02x (%dd)", data, data, ddaddres, ddaddres);
	}
}

