/** \file server/drivers/t6963_low.c
 * Low level output routines for Toshiba T6963 based LCD displays connected
 * to a parallel port.
 *
 * Wiring (no pins for the display given. Check with your datasheet!)
 *
 *\verbatim
 *  Parallel:              LCD:
 *  1 (Strobe) ----------- /WR
 *  2-9 (Data) ----------- DB0-DB7
 *  14 (Autofeed) -------- /CE
 *  16 (Init) ------------ C/D
 *  17 (Slct) ------------ /RD
 *                + 5V --- FS (6x8 font, used for t6963 driver)
 *                 GND --- FS (8x8 font, used for glcd connection type)
 *\endverbatim
 *
 */

/*-
 * Parts of this file are based on the kernel driver by
 * Alexander Frink <Alexander.Frink@Uni-Mainz.DE>
 *
 * Copyright (c) 2001 Manuel Stahl <mythos@xmythos.de>
 *               2011 Markus Dolze
 *
 * This file is released under the GNU General Public License. Refer to the
 * COPYING file distributed with this package.
 */

#include <stdio.h>

#include "port.h"
#include "lpt-port.h"
#include "timing.h"
#include "t6963_low.h"

/* Define the wiring */
#define nWR	nSTRB
#define nRD	nSEL
#define nCE	nLF
#define T_CMD	INIT
#define T_DATA	0x00		/* ~INIT didn't work here */


/**
 * Acquires access to parallel port and initializes timing. The parallel port
 * must be an I/O-address between 0x200 and 0x400.
 * \param p        Pointer to port configuration.
 * \return  0 on success, -1 if an error occured.
 */
int
t6963_low_init(T6963_port *p) {
	if ((p->port < 0x200) || (p->port > 0x400))
		return -1;

	if (port_access_multiple(p->port, 3))
		return -1;

	if (timing_init() == -1)
		return -1;

	return 0;
}


/**
 * Releases access to the parallel port.
 * \param p        Pointer to port configuration.
 */
void
t6963_low_close(T6963_port *p) {
	if ((p->port >= 0x200) && (p->port <= 0x400))
		port_deny_multiple(p->port, 3);
}

/**
 * Send one data byte to the display.
 * \param p        Pointer to port configuration.
 * \param byte     Data byte.
 */
void
t6963_low_data(T6963_port *p, u8 byte)
{
	t6963_low_dsp_ready(p, STA0|STA1);
	t6963_low_send(p, T_DATA, byte);
}

/**
 * Send one command byte to the display.
 * \param p        Pointer to port configuration.
 * \param byte     Command byte.
 */
void
t6963_low_command(T6963_port *p, u8 byte)
{
	t6963_low_dsp_ready(p, STA0|STA1);
	t6963_low_send(p, T_CMD, byte);
}

/**
 * Write one byte of data to display in AUTO mode (needs a different ready
 * check).
 * \param p        Pointer to port configuration.
 * \param byte     Data byte.
 */
void
t6963_low_auto_write(T6963_port *p, u8 byte)
{
	t6963_low_dsp_ready(p, STA3);
	t6963_low_send(p, T_DATA, byte);
}

/**
 * Send one byte of data followed by one command byte to the display.
 * \param p        Pointer to port configuration.
 * \param cmd      Command byte.
 * \param byte     Data value.
 */
void
t6963_low_command_byte(T6963_port *p, u8 cmd, u8 byte)
{
	t6963_low_data(p, byte);
	t6963_low_command(p, cmd);
}

/**
 * Send one word (two bytes) of data followed by one command byte to the
 * display. Low byte is output first.
 * \param p        Pointer to port configuration.
 * \param cmd      Command byte.
 * \param word     Data value (2 bytes)
 */
void
t6963_low_command_word(T6963_port *p, u8 cmd, u16 word)
{
	t6963_low_data(p, word & 0xFF);
	t6963_low_data(p, (word >> 8) & 0xFF);
	t6963_low_command(p, cmd);
}

/**
 * Check display status.
 * \param p        Pointer to port configuration.
 * \param sta      Bitmap of expected STA flags
 * \return  0 on success, -1 if ready could not be read
 */
int
t6963_low_dsp_ready(T6963_port *p, u8 sta)
{
	int portcontrol = 0;

	if (p->bidirectLPT == 1) {
		int val;
		int loop = 0;

		do {
			portcontrol = T_CMD | nWR | nRD | nCE;
			port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
			/* lower nRD, nCE, set bi-directional mode */
			portcontrol = T_CMD | nWR | ENBI;
			port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
			/* possible wait required here: tACC = 150 ns max */
			if (p->delayBus)
				timing_uPause(1);
			val = port_in(T6963_DATA_PORT(p->port));
			portcontrol = T_CMD | nWR | nRD | nCE;
			port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
			loop++;
			if (loop == 100)
				return -1;
		} while ((val & sta) != sta);
	}
	else {
		portcontrol = T_CMD | nWR | nRD | nCE;
		port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
		portcontrol = T_CMD | nWR;
		port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
		timing_uPause(150);
		portcontrol = T_CMD | nWR | nRD | nCE;
		port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
	}

	return 0;
}

/**
 * Write a single command / or byte to the parallel port.
 * \param p        Pointer to port configuration.
 * \param type     Command or Data
 * \param byte     Data byte.
 */
void
t6963_low_send(T6963_port *p, u8 type, u8 byte)
{
	int portcontrol = 0;

	portcontrol = type | nWR | nRD | nCE;
	port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
	port_out(T6963_DATA_PORT(p->port), byte);
	portcontrol = type | nRD;	/* lower nWR, nCE */
	port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
	/* possible wait required here: tWR */
	if (p->delayBus)
		timing_uPause(1);
	portcontrol = type | nWR | nRD | nCE;
	port_out(T6963_CONTROL_PORT(p->port), portcontrol ^ OUTMASK);
}
