/*
* written by David Borowski, david575@golden.net

		Copyright (C) 2003 David Borowski.

		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 2 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, write to the Free Software
		Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 * this code is specificly written as a driver for the speakup screenreview
 * package and is not a general device driver.
		*/
#include "spk_priv.h"
#include "dtpc_reg.h"

#define MY_SYNTH synth_dec_pc
#define PROCSPEECH 0x0b
#define SYNTH_IO_EXTENT 8

static int synth_portlist[] = { 0x340, 0x350, 0x240, 0x250, 0 };
static int in_escape = 0, is_flushing = 0;
static int dt_stat, dma_state = 0;

static inline int dt_getstatus( void )
{
	dt_stat =  inb_p( synth_port_tts )|(inb_p( synth_port_tts+1 )<<8);
	return dt_stat;
}

static inline void dt_sendcmd( u_int cmd )
{
	outb_p( cmd & 0xFF, synth_port_tts );
	outb_p( (cmd>>8) & 0xFF, synth_port_tts+1 );
}

static int dt_waitbit( int bit )
{
	int timeout = 100;
	while ( --timeout > 0 ) {
		if( (dt_getstatus( ) & bit ) == bit ) return 1;
		udelay( 50 );
	}
	return 0;
}

static int dt_wait_dma( void )
{
	int timeout = 100, state = dma_state;
	if( ! dt_waitbit( STAT_dma_ready ) ) return 0;
	while ( --timeout > 0 ) {
		if( (dt_getstatus()&STAT_dma_state) == state ) return 1;
		udelay( 50 );
	}
	dma_state = dt_getstatus( ) & STAT_dma_state;
	return 1;
}

int dt_ctrl( u_int cmd )
{
	int timeout = 10;
	if ( !dt_waitbit( STAT_cmd_ready ) ) return -1;
	outb_p( 0, synth_port_tts+2 );
	outb_p( 0, synth_port_tts+3 );
	dt_getstatus( );
	dt_sendcmd( CMD_control|cmd );
	outb_p( 0, synth_port_tts+6 );
	while ( dt_getstatus( ) & STAT_cmd_ready ) {
		udelay( 20 );
		if ( --timeout == 0 ) break;
	}
	dt_sendcmd( CMD_null );
	return 0;
}

static void synth_flush( void )
{
	int timeout = 10;
	if ( is_flushing ) return;
	is_flushing = 4;
	in_escape = 0;
	while ( dt_ctrl( CTRL_flush ) ) {
		if ( --timeout == 0 ) break;
udelay( 50 );
	}
	for ( timeout = 0; timeout < 10; timeout++ ) {
		if ( dt_waitbit( STAT_dma_ready ) ) break;
udelay( 50 );
	}
	outb_p( DMA_sync, synth_port_tts+4 );
	outb_p( 0, synth_port_tts+4 );
	udelay( 100 );
	for ( timeout = 0; timeout < 10; timeout++ ) {
		if ( !( dt_getstatus( ) & STAT_flushing ) ) break;
udelay( 50 );
	}
	dma_state = dt_getstatus( ) & STAT_dma_state;
	dma_state ^= STAT_dma_state;
	is_flushing = 0;
}

static int dt_sendchar( char ch )
{
	if( ! dt_wait_dma( ) ) return -1;
	if( ! (dt_stat & STAT_rr_char) ) return -2;
	outb_p( DMA_single_in, synth_port_tts+4 );
	outb_p( ch, synth_port_tts+4 );
	dma_state ^= STAT_dma_state;
	return 0;
}

static int testkernel( void )
{
	int status = 0;
	if ( dt_getstatus( ) == 0xffff ) {
		status = -1;
		goto oops;
	}
	dt_sendcmd( CMD_sync );
	if( ! dt_waitbit( STAT_cmd_ready ) ) status = -2;
	else if ( ( dt_stat&0x8000 ) ) {
		return 0;
	} else if ( dt_stat == 0x0dec )
		pr_warn( "dec_pc at 0x%x, software not loaded\n", synth_port_tts );
	status = -3;
oops:	synth_release_region( synth_port_tts, SYNTH_IO_EXTENT );
	synth_port_tts = 0;
	return status;
}

static void do_catch_up( unsigned long data )
{
	unsigned long jiff_max = jiffies+synth_jiffy_delta;
	u_char ch;
static u_char last='\0';
	synth_stop_timer( );
	while ( synth_buff_out < synth_buff_in ) {
		ch = *synth_buff_out;
		if ( ch == '\n' ) ch = 0x0D;
		if ( dt_sendchar( ch ) ) {
			synth_delay( synth_full_time );
			return;
		}
		synth_buff_out++;
		if ( ch == '[' ) in_escape = 1;
		else if ( ch == ']' ) in_escape = 0;
		else if ( ch <= SPACE ) {
			if ( !in_escape && strchr( ",.!?;:", last ) )
				dt_sendchar( PROCSPEECH );
			if ( jiffies >= jiff_max ) {
				if ( !in_escape )
					dt_sendchar( PROCSPEECH );
				synth_delay( synth_delay_time );
				return;
			}
		}
		last = ch;
	}
	if ( synth_done( ) || !in_escape )
	dt_sendchar( PROCSPEECH );
}

static const char *synth_immediate ( const char *buf )
{
	u_char ch;
	while ( ( ch = *buf ) ) {
	if ( ch == 0x0a ) ch = PROCSPEECH;
		if ( dt_sendchar ( ch ) )
			return buf;
	buf++;
	}
	return 0;
}

static int synth_probe ( void )
{
	int i=0, failed=0;
	pr_info ( "Probing for %s.\n", synth->long_name );
	for( i=0; synth_portlist[i]; i++ ) {
		if ( synth_request_region( synth_portlist[i], SYNTH_IO_EXTENT ) ) {
			pr_warn( "request_region:  failed with 0x%x, %d\n",
				synth_portlist[i], SYNTH_IO_EXTENT );
			continue;
		}
		synth_port_tts = synth_portlist[i];
		if (( failed = testkernel( )) == 0 ) break;
	}
	if ( failed ) {
		pr_info ( "%s:  not found\n", synth->long_name );
		return -ENODEV;
	}
	pr_info ( "%s: %03x-%03x, Driver Version %s,\n", synth->long_name,
		synth_port_tts, synth_port_tts + 7, synth->version );
	return 0;
}

static void dtpc_release(  void )
{
	if (  synth_port_tts )
		synth_release_region( synth_port_tts, SYNTH_IO_EXTENT );
	synth_port_tts = 0;
}

static int synth_is_alive( void )
{
	synth_alive = 1;
	return 1;
}

static const char init_string[] = "[:pe -380]";

static string_var stringvars[] = {
	{ CAPS_START, "[:dv ap 200]" },
	{ CAPS_STOP, "[:dv ap 100]" },
	V_LAST_STRING
};
static num_var numvars[] = {
	{ RATE, "[:ra %d]", 9, 0, 18, 150, 25, 0 },
	{ PITCH, "[:dv ap %d]", 80, 0, 100, 20, 0, 0 },
	{ VOL, "[:vo se %d]", 5, 0, 9, 5, 10, 0 },
	{ PUNCT, "[:pu %c]", 0, 0, 2, 0, 0, "nsa" },
	{ VOICE, "[:n%c]", 0, 0, 9, 0, 0, "phfdburwkv" },
	V_LAST_NUM
};

struct spk_synth synth_dec_pc = { "decpc", "1.1", "Dectalk PC",
	init_string, 500, 50, 50, 1000, 0, SF_DEC, SYNTH_CHECK,
	stringvars, numvars, synth_probe, dtpc_release, synth_immediate,
	do_catch_up, NULL, synth_flush, synth_is_alive, NULL, NULL, NULL,
	{NULL,0,0,0} };

#ifdef MODULE
#include "mod_code.c"
#endif
