#include <u.h>
#include <lib9.h>
#include "dat.h"
#include "fns.h"

enum {
	AC0 = 0,
	AC1,
	AC2,
	Actl,

	Readback = 3,

	RBC0 = 1<<1,
	RBC1 = 1<<2,
	RBC2 = 1<<3,
	RBlatchstatus = 1<<4,
	RBlatchcount = 1<<5,

	AMlatchcount = 0,
	AMloonly,
	AMhionly,
	AMlohi,

	OM0 = 0,
	OM1,
	OM2,
	OM3,
	OM4,
	OM5,
	OM2b,
	OM3b,
};

static void
latchstatus(Pit *ch)
{
	print_func_entry();
	if(ch->rlatched) {
		print_func_exit();
		return;
	}
	ch->rlatch[0] = ch->bcd | ch->omode<<1 | ch->amode<<4 | ch->count0<<6 | ch->out<<7;
	ch->rcount = 0;
	ch->rlatched = 1;
	print_func_exit();
}

static void
latchcount(Pit *ch)
{
	print_func_entry();
	unsigned long w;

	if(ch->rlatched) {
		print_func_exit();
		return;
	}
	w = ch->count & 0xFFFF;
	if(ch->bcd)
		w = (w % 10) + ((w/10) % 10)<<4 + ((w/100) % 10)<<8 + ((w/1000) % 10)<<12;
	ch->rlatch[0] = w & 0xFF;
	ch->rlatch[1] = (w >> 8) & 0xFF;
	ch->rcount = 0;
	ch->rlatched = 1;
	switch(ch->amode){
	case AMhionly:
		ch->rcount++;
		break;
	case AMlohi:
		ch->rlatched++;
		break;
	}
	print_func_exit();
}

static void
setcount(Pit *ch)
{
	print_func_entry();
	unsigned long w;

	w = (unsigned long)ch->wlatch[0] | (unsigned long)ch->wlatch[1] << 8;
	if(ch->bcd)
		w = (w & 0xF) + 10*((w >> 4)&0xF) + 100*((w >> 8)&0xF) + 1000*((w >> 12)&0xF);
	ch->count = w;
	ch->count0 = 0;
	print_func_exit();
}

static int
deccount(Pit *ch, long long *cycles)
{
	print_func_entry();
	if(ch->count0){
		*cycles = 0;
		print_func_exit();
		return 0;
	} else {
		long long passed, remain;

		passed = *cycles;
		if(ch->count == 0){
			ch->count = ch->bcd ? 9999 : 0xFFFF;
			passed--;
		}
		if(passed <= ch->count){
			remain = 0;
			ch->count -= passed;
		} else {
			remain = passed - ch->count;
			ch->count = 0;
		}
		*cycles = remain;
		print_func_exit();
		return ch->count == 0;
	}
}

void
setgate(Pit *ch, unsigned char gate)
{
	print_func_entry();
	if(ch->gate == 0 && gate)
		ch->gateraised = 1;
	ch->gate = gate;
	print_func_exit();
}

static void
clockpit1(Pit *ch, long long *cycles)
{
	print_func_entry();
	switch(ch->omode){
	case OM0:	/* Interrupt On Terminal Count */
		if(ch->count0){
			setcount(ch);
			ch->out = 0;
		Next:
			--*cycles;
			print_func_exit();
			return;
		}
		if(ch->gate && deccount(ch, cycles)){
			ch->out = 1;
			print_func_exit();
			return;
		}
		break;

	case OM1:	/* Hardware Re-triggerable One-shot */
		if(ch->gateraised){
			ch->gateraised = 0;
			setcount(ch);
			ch->out = 0;
			goto Next;
		}
		if(deccount(ch, cycles) && ch->out == 0){
			ch->out = 1;
			print_func_exit();
			return;
		}
		break;

	case OM2:	/* Rate Generator */
	case OM2b:
		ch->out = 1;
		if(ch->count0){
			setcount(ch);
			goto Next;
		}
		if(ch->gate == 0)
			break;
		if(ch->gateraised){
			ch->gateraised = 0;
			setcount(ch);
			goto Next;
		}
		if(deccount(ch, cycles)){
			setcount(ch);
			ch->out = 0;
			print_func_exit();
			return;
		}
		break;

	case OM3:	/* Square Wave Generator */
	case OM3b:
		if(ch->count0){
			setcount(ch);
			goto Next;
		}
		if(ch->gate == 0)
			break;
		if(ch->gateraised){
			ch->gateraised = 0;
			setcount(ch);
			goto Next;
		}
		if(deccount(ch, cycles)){
			setcount(ch);
			ch->out ^= 1;
			print_func_exit();
			return;
		}
		break;

	case OM4:	/* Software Triggered Strobe */
		ch->out = 1;
		if(ch->count0){
			setcount(ch);
			goto Next;
		}
		if(ch->gate && deccount(ch, cycles)){
			ch->out = 0;
			print_func_exit();
			return;
		}
		break;

	case OM5:	/* Hardware Triggered Strobe */
		ch->out = 1;
		if(ch->gateraised){
			ch->gateraised = 0;
			setcount(ch);
			goto Next;
		}
		if(deccount(ch, cycles)){
			ch->out = 0;
			print_func_exit();
			return;
		}
		break;
	}
	*cycles = 0;
	print_func_exit();
}

void
clockpit(Pit *pit, long long cycles)
{
	print_func_entry();
	Pit *ch;
	int i;

	if(cycles <= 0) {
		print_func_exit();
		return;
	}
	for(i = 0; i<Actl; i++){
		ch = pit + i;
		if(ch->wlatched){
			long long c;

			switch(ch->omode){
			case OM3:
			case OM3b:
				c = cycles * 2;
				break;
			default:
				c = cycles;
			}
			while(c > 0)
				clockpit1(ch, &c);
		}
		ch->gateraised = 0;
	}
	print_func_exit();
}

unsigned char
rpit(Pit *pit, unsigned char addr)
{
	print_func_entry();
	Pit *ch;
	unsigned char data;

	if(addr >= Actl) {
		print_func_exit();
		return 0;
	}
	ch = pit + addr;
	if(ch->rlatched){
		data = ch->rlatch[ch->rcount & 1];
		ch->rlatched--;
	} else {
		data = 0;
		switch(ch->amode){
		case AMloonly:
			data = ch->count & 0xFF;
			break;
		case AMhionly:
			data = (ch->count >> 8) & 0xFF;
			break;
		case AMlohi:
			data = (ch->count >> ((ch->rcount & 1)<<3)) & 0xFF;
			break;
		}
	}
	ch->rcount++;
	if(0) fprint(2, "rpit %p: %.2x %.2x\n", pit, (int)addr, (int)data);
	print_func_exit();
	return data;
}

void
wpit(Pit *pit, unsigned char addr, unsigned char data)
{
	print_func_entry();
	Pit *ch;

	if(0) fprint(2, "wpit %p: %.2x %.2x\n", pit, (int)addr, (int)data);
	if(addr > Actl) {
		print_func_exit();
		return;
	}
	if(addr == Actl){
		unsigned char sc, amode, omode, bcd;

		bcd = (data & 1);
		omode = (data >> 1) & 7;
		amode = (data >> 4) & 3;
		sc = (data >> 6) & 3;
	
		if(sc == Readback){
			ch = nil;
			for(;;){
				if(data & RBC0){
					ch = pit;
					break;
				}
				if(data & RBC1){
					ch = pit + 1;
					break;
				}
				if(data & RBC2){
					ch = pit + 2;
					break;
				}
				break;
			}
			if(ch == nil) {
				print_func_exit();
				return;
			}
			if((data & RBlatchcount) == 0)
				latchcount(ch);
			if((data & RBlatchstatus) == 0)
				latchstatus(ch);
			print_func_exit();
			return;
		}

		ch = pit + sc;
		if(amode == AMlatchcount){
			latchcount(ch);
			print_func_exit();
			return;
		}
		ch->bcd = bcd;
		
		ch->amode = amode;
		ch->omode = omode;

		ch->rlatched = 0;
		ch->rcount = 0;
		ch->rlatch[0] = 0;
		ch->rlatch[1] = 0;

		ch->wlatched = 0;
		ch->wcount = 0;
		ch->wlatch[0] = 0;
		ch->wlatch[1] = 0;

		ch->count0 = 1;
		ch->out = !!omode;
		print_func_exit();
		return;
	}

	ch = pit + addr;
	switch(ch->amode){
	case AMloonly:
	case AMhionly:
		ch->wlatch[ch->amode - AMloonly] = data;
		ch->wcount++;
		break;
	case AMlohi:
		ch->wlatch[ch->wcount++ & 1] = data;
		if(ch->wcount < 2) {
		print_func_exit();
		return;
		}
		break;
	}
	ch->wlatched = ch->wcount;
	ch->wcount = 0;
	ch->count0 = 1;
	print_func_exit();
}
