/*
	Skelton for retropc emulator

	Author : Takeda.Toshiya
	Date   : 2006.06.01-

	[ i8253/i8254 ]
*/

#include "i8253.h"

void I8253::initialize()
{
	DEVICE::initialize();
	__HAS_I8254 = osd->check_feature(_T("HAS_I8254"));
	for(int ch = 0; ch < 3; ch++) {
		counter[ch].prev_out = true;
		counter[ch].prev_in = false;
		counter[ch].gate = true;
		counter[ch].count = 0x10000;
		counter[ch].count_reg = 0;
		counter[ch].ctrl_reg = 0x34;
		counter[ch].mode = 3;
		counter[ch].count_latched = false;
		counter[ch].low_read = counter[ch].high_read = false;
		counter[ch].low_write = counter[ch].high_write = false;
		counter[ch].delay = false;
		counter[ch].start = false;
//#ifdef HAS_I8254
		if(__HAS_I8254) {
			// 8254 read-back command
			counter[ch].null_count = true;
			counter[ch].status_latched = false;
		}
//#endif
	}
}

void I8253::reset()
{
	for(int ch = 0; ch < 3; ch++) {
		counter[ch].register_id = -1;
	}
}

#define COUNT_VALUE(n) ((counter[n].count_reg == 0) ? 0x10000 : (counter[n].mode == 3 && counter[n].count_reg == 1) ? 0x10001 : counter[n].count_reg)

void I8253::write_io8(uint32_t addr, uint32_t data)
{
	int ch = addr & 3;
	
	switch(addr & 3) {
	case 0:
	case 1:
	case 2:
		// write count register
		if(!counter[ch].low_write && !counter[ch].high_write) {
			if(counter[ch].ctrl_reg & 0x10) {
				counter[ch].low_write = true;
			}
			if(counter[ch].ctrl_reg & 0x20) {
				counter[ch].high_write = true;
			}
		}
		if(counter[ch].low_write) {
			counter[ch].count_reg = data;
			counter[ch].low_write = false;
		} else if(counter[ch].high_write) {
			if((counter[ch].ctrl_reg & 0x30) == 0x20) {
				counter[ch].count_reg = data << 8;
			} else {
				counter[ch].count_reg |= data << 8;
			}
			counter[ch].high_write = false;
		}
//#ifdef HAS_I8254
		if(__HAS_I8254) counter[ch].null_count = true;
//#endif
		// set signal
		if(counter[ch].mode == 0) {
			set_signal(ch, false);
		} else {
			set_signal(ch, true);
		}
		// start count
		if(counter[ch].mode == 0 || counter[ch].mode == 4) {
			// restart with new count
			stop_count(ch);
			counter[ch].delay = true;
			start_count(ch);
		} else if(counter[ch].mode == 2 || counter[ch].mode == 3) {
			// start with new counter after the current count is finished
			if(!counter[ch].start) {
				counter[ch].delay = true;
				start_count(ch);
			}
		}
		break;
		
	case 3: // ctrl reg
		if((data & 0xc0) == 0xc0) {
//#ifdef HAS_I8254
			if(__HAS_I8254) {
				// i8254 read-back command
				for(ch = 0; ch < 3; ch++) {
					uint8_t bit = 2 << ch;
					if(!(data & 0x10) && !counter[ch].status_latched) {
						counter[ch].status = counter[ch].ctrl_reg & 0x3f;
						if(counter[ch].prev_out) {
							counter[ch].status |= 0x80;
						}
						if(counter[ch].null_count) {
							counter[ch].status |= 0x40;
						}
						counter[ch].status_latched = true;
					}
					if(!(data & 0x20) && !counter[ch].count_latched) {
						latch_count(ch);
					}
				}
			}
//#endif
			break;
		}
		ch = (data >> 6) & 3;
		
		if(data & 0x30) {
			static const int modes[8] = {0, 1, 2, 3, 4, 5, 2, 3};
//			int prev = counter[ch].mode;
			counter[ch].mode = modes[(data >> 1) & 7];
			counter[ch].count_latched = false;
			counter[ch].low_read = counter[ch].high_read = false;
			counter[ch].low_write = counter[ch].high_write = false;
			counter[ch].ctrl_reg = data;
			// set signal
			if(counter[ch].mode == 0) {
				set_signal(ch, false);
			} else {
				set_signal(ch, true);
			}
			// stop count
//			if(counter[ch].mode != prev || counter[ch].mode == 0 || counter[ch].mode == 4) {
				stop_count(ch);
				counter[ch].count_reg = 0;
//			}
//#ifdef HAS_I8254
				if(__HAS_I8254) counter[ch].null_count = true;
//#endif
		} else if(!counter[ch].count_latched) {
			latch_count(ch);
		}
		break;
	}
}

uint32_t I8253::read_io8(uint32_t addr)
{
	int ch = addr & 3;
	
	switch(ch) {
	case 0:
	case 1:
	case 2:
//#ifdef HAS_I8254
		if(__HAS_I8254) {
			if(counter[ch].status_latched) {
				counter[ch].status_latched = false;
				return counter[ch].status;
			}
		}
//#endif
		// if not latched, through current count
		if(!counter[ch].count_latched) {
			if(!counter[ch].low_read && !counter[ch].high_read) {
				latch_count(ch);
			}
		}
		// return latched count
		if(counter[ch].low_read) {
			counter[ch].low_read = false;
			if(!counter[ch].high_read) {
				counter[ch].count_latched = false;
			}
			return counter[ch].latch & 0xff;
		} else if(counter[ch].high_read) {
			counter[ch].high_read = false;
			counter[ch].count_latched = false;
			return (counter[ch].latch >> 8) & 0xff;
		}
	}
	return 0xff;
}

void I8253::event_callback(int event_id, int err)
{
	int ch = event_id;
	counter[ch].register_id = -1;
	input_clock(ch, counter[ch].input_clk);
	
	// register next event
	if(counter[ch].freq && counter[ch].start) {
		counter[ch].input_clk = counter[ch].delay ? 1 : get_next_count(ch);
		counter[ch].period = (int)(cpu_clocks * counter[ch].input_clk / counter[ch].freq + err);
		counter[ch].prev_clk = get_current_clock() + err;
		register_event_by_clock(this, ch, counter[ch].period, false, &counter[ch].register_id);
	}
}

void I8253::write_signal(int id, uint32_t data, uint32_t mask)
{
	bool next = ((data & mask) != 0);
	
	switch(id) {
	case SIG_I8253_CLOCK_0:
		if(counter[0].prev_in && !next) {
			input_clock(0, 1);
		}
		counter[0].prev_in = next;
		break;
	case SIG_I8253_CLOCK_1:
		if(counter[1].prev_in && !next) {
			input_clock(1, 1);
		}
		counter[1].prev_in = next;
		break;
	case SIG_I8253_CLOCK_2:
		if(counter[2].prev_in && !next) {
			input_clock(2, 1);
		}
		counter[2].prev_in = next;
		break;
	case SIG_I8253_GATE_0:
		input_gate(0, next);
		break;
	case SIG_I8253_GATE_1:
		input_gate(1, next);
		break;
	case SIG_I8253_GATE_2:
		input_gate(2, next);
		break;
	}
}

void I8253::input_clock(int ch, int clock)
{
	if(!(counter[ch].start && clock)) {
		return;
	}
	if(counter[ch].delay) {
		clock -= 1;
		counter[ch].delay = false;
		counter[ch].count = COUNT_VALUE(ch);
//#ifdef HAS_I8254
		if(__HAS_I8254) counter[ch].null_count = false;
//#endif
	}
	
	// update counter
	counter[ch].count -= clock;
	int32_t tmp = COUNT_VALUE(ch);
loop:
	if(counter[ch].mode == 3) {
		int32_t half = tmp >> 1;
		set_signal(ch, counter[ch].count > half);
	} else {
		if(counter[ch].count <= 1) {
			if(counter[ch].mode == 2 || counter[ch].mode == 4 || counter[ch].mode == 5) {
				set_signal(ch, false);
			}
		}
		if(counter[ch].count <= 0) {
			set_signal(ch, true);
		}
	}
	if(counter[ch].count <= 0) {
		if(counter[ch].mode == 0 || counter[ch].mode == 2 || counter[ch].mode == 3) {
			counter[ch].count += tmp;
//#ifdef HAS_I8254
			if(__HAS_I8254) counter[ch].null_count = false;
//#endif
			goto loop;
		} else {
			counter[ch].start = false;
			counter[ch].count = 0x10000;
		}
	}
}

void I8253::input_gate(int ch, bool signal)
{
	bool prev = counter[ch].gate;
	counter[ch].gate = signal;
	
	if(prev && !signal) {
		// stop count
		if(!(counter[ch].mode == 1 || counter[ch].mode == 5)) {
			stop_count(ch);
		}
		// set output signal
		if(counter[ch].mode == 2 || counter[ch].mode == 3) {
			set_signal(ch, true);
		}
	} else if(!prev && signal) {
		// restart count
		stop_count(ch);
		if(!(counter[ch].mode == 0 || counter[ch].mode == 4)) {
			counter[ch].delay = true;
		}
		start_count(ch);
		// set output signal
		if(counter[ch].mode == 1) {
			set_signal(ch, false);
		}
	}
}

void I8253::start_count(int ch)
{
	if(counter[ch].low_write || counter[ch].high_write) {
		return;
	}
	if(!counter[ch].gate) {
		return;
	}
	counter[ch].start = true;
	
	// register event
	if(counter[ch].freq) {
		counter[ch].input_clk = counter[ch].delay ? 1 : get_next_count(ch);
		counter[ch].period = (int)(cpu_clocks * counter[ch].input_clk / counter[ch].freq);
		counter[ch].prev_clk = get_current_clock();
		register_event_by_clock(this, ch, counter[ch].period, false, &counter[ch].register_id);
	}
}

void I8253::stop_count(int ch)
{
	counter[ch].start = false;
	
	// cancel event
	if(counter[ch].register_id != -1) {
		cancel_event(this, counter[ch].register_id);
	}
	counter[ch].register_id = -1;
}

void I8253::latch_count(int ch)
{
	if(counter[ch].register_id != -1) {
		// update counter
		int passed = get_passed_clock(counter[ch].prev_clk);
		uint32_t input = (uint32_t)(counter[ch].freq * passed / cpu_clocks);
		if(input > 0) {
			bool expired = (counter[ch].input_clk <= input);
			input_clock(ch, input);
			// cancel and re-register event
			if(expired) {
				cancel_event(this, counter[ch].register_id);
				if(counter[ch].freq && counter[ch].start) {
					counter[ch].input_clk = counter[ch].delay ? 1 : get_next_count(ch);
					counter[ch].period = (int)(cpu_clocks * counter[ch].input_clk / counter[ch].freq);
					counter[ch].prev_clk = get_current_clock();
					register_event_by_clock(this, ch, counter[ch].period, false, &counter[ch].register_id);
				}
			} else {
				cancel_event(this, counter[ch].register_id);
				counter[ch].input_clk -= input;
				counter[ch].period -= passed;
				counter[ch].prev_clk = get_current_clock();
				register_event_by_clock(this, ch, counter[ch].period, false, &counter[ch].register_id);
			}
		}
	}
	// latch counter
	counter[ch].latch = (uint16_t)counter[ch].count;
	counter[ch].count_latched = true;
	if((counter[ch].ctrl_reg & 0x30) == 0x10) {
		// lower byte
		counter[ch].low_read = true;
		counter[ch].high_read = false;
	} else if((counter[ch].ctrl_reg & 0x30) == 0x20) {
		// upper byte
		counter[ch].low_read = false;
		counter[ch].high_read = true;
	} else {
		// lower -> upper
		counter[ch].low_read = counter[ch].high_read = true;
	}
}

void I8253::set_signal(int ch, bool signal)
{
	bool prev = counter[ch].prev_out;
	counter[ch].prev_out = signal;
	
	if(prev && !signal) {
		// H->L
		write_signals(&counter[ch].outputs, 0);
	} else if(!prev && signal) {
		// L->H
		write_signals(&counter[ch].outputs, 0xffffffff);
	}
}

int I8253::get_next_count(int ch)
{
	if(counter[ch].mode == 2 || counter[ch].mode == 4 || counter[ch].mode == 5) {
		return (counter[ch].count > 1) ? counter[ch].count - 1 : 1;
	}
	if(counter[ch].mode == 3) {
		int32_t half = COUNT_VALUE(ch) >> 1;
		return (counter[ch].count > half) ? counter[ch].count - half : counter[ch].count;
	}
	return counter[ch].count;
}

bool I8253::get_debug_regs_info(_TCHAR *buffer, size_t buffer_len)
{	
	_TCHAR regs1[3][1024];
	for(int ch = 0; ch < 3; ch++) {
		memset(regs1[ch], 0x00, sizeof(_TCHAR) * 1024);
		my_sprintf_s(regs1[ch], 1024 -1,
					 _T("CH%d: FREQ=%d ")
					 _T("MODE=%d DELAY=%s START=%s ")
					 _T("CTRL=%02X COUNT_REG=%04X COUNT=%d ")
					 _T("GATE=%s PREV_IN=%s PREV_OUT=%s ")
					 _T("\n")
					 ,
					 ch, counter[ch].freq,
					 counter[ch].mode, (counter[ch].delay) ? _T("YES") : _T("NO "),  (counter[ch].start) ? _T("YES") : _T("NO "),
					 counter[ch].ctrl_reg, counter[ch].count_reg, counter[ch].count,
					 (counter[ch].gate) ? _T("YES") : _T("NO "), (counter[ch].prev_in) ? _T("YES") : _T("NO "), (counter[ch].prev_out) ? _T("YES") : _T("NO "));
	}
	my_sprintf_s(buffer, buffer_len, _T("TYPE=%s\n%s%s%s"),
				 (__HAS_I8254) ? _T("i8254") : _T("i8253"),
				 regs1[0], regs1[1], regs1[2]
		);
	return true;
}
	
#define STATE_VERSION	1

bool I8253::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
 		return false;
 	}
	if(!state_fio->StateCheckInt32(this_device_id)) {
 		return false;
 	}
 	for(int i = 0; i < 3; i++) {
		state_fio->StateValue(counter[i].prev_out);
		state_fio->StateValue(counter[i].prev_in);
		state_fio->StateValue(counter[i].gate);
		state_fio->StateValue(counter[i].count);
		state_fio->StateValue(counter[i].latch);
		state_fio->StateValue(counter[i].count_reg);
		state_fio->StateValue(counter[i].ctrl_reg);
		state_fio->StateValue(counter[i].count_latched);
		state_fio->StateValue(counter[i].low_read);
		state_fio->StateValue(counter[i].high_read);
		state_fio->StateValue(counter[i].low_write);
		state_fio->StateValue(counter[i].high_write);
		state_fio->StateValue(counter[i].mode);
		state_fio->StateValue(counter[i].delay);
		state_fio->StateValue(counter[i].start);
		if(__HAS_I8254) {
			state_fio->StateValue(counter[i].null_count);
			state_fio->StateValue(counter[i].status_latched);
			state_fio->StateValue(counter[i].status);
		}
		state_fio->StateValue(counter[i].freq);
		state_fio->StateValue(counter[i].register_id);
		state_fio->StateValue(counter[i].input_clk);
		state_fio->StateValue(counter[i].period);
		state_fio->StateValue(counter[i].prev_clk);
 	}
	state_fio->StateValue(cpu_clocks);
 	return true;
}
