/*
 * Copyright (C) 2005-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

/* set this to '1' to add debug messages */
#define DEBUG_PIT		0

#ifdef STATE

struct {
	unsigned long tsc_to_timer;	/* ticks per timer interval */
	unsigned long long last_event_tsc;
	unsigned int running;

	unsigned char status_latched[3];
	unsigned char status[3];
	unsigned char rw[3];
	unsigned char mode[3];
	unsigned char bcd[3];
	unsigned short start[3];
	unsigned char write_state[3];
	unsigned short act[3];
	unsigned char read_state[3];
	unsigned short latch[3];
	unsigned char latch_state[3];
	unsigned int gate[3];
	unsigned char null_count[3];
	unsigned char out[3];
} NAME;

#endif /* STATE */
#ifdef BEHAVIOR

#define UMPIT_REAL_HZ   1193180

/*forward*/ static void
NAME_(event)(void *data);

static void
NAME_(tick_one)(struct cpssp *cpssp, unsigned int ch, unsigned long ticks)
{
	assert(/* 0 <= ch && */ ch < 3);
	assert(0 < ticks);

	switch (cpssp->NAME.mode[ch]) {
	case 0:
		/*
		 * Mode 0
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.null_count[ch] == 1) {
			cpssp->NAME.act[ch] = cpssp->NAME.start[ch];
			cpssp->NAME.null_count[ch] = 0;
			ticks--;
		}

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return;
		}

		if (cpssp->NAME.act[ch] <= ticks
		 && cpssp->NAME.out[ch] == 0) {
			/*
			 * Timer expires.
			 */
			cpssp->NAME.out[ch] = 1;
			NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
		}

		/*
		 * Timer counting.
		 */
		cpssp->NAME.act[ch] -= ticks;
		break;

	case 1:
		/*
		 * Mode 1
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		assert(0);				/* FIXME VOSSI */

	case 2:
	case 6:
		/*
		 * Mode 2 binary
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return;
		}

		while (0 < ticks) {
			if (cpssp->NAME.act[ch] == 0x0001) {
				/*
				 * Timer 0x0001 -> N
				 */
				cpssp->NAME.act[ch] = cpssp->NAME.start[ch];
				cpssp->NAME.null_count[ch] = 0;
				cpssp->NAME.out[ch] = 1;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
				ticks--;

			} else if (cpssp->NAME.act[ch] - 1 <= ticks) {
				/*
				 * Timer N -> 0x0001
				 */
				ticks -= cpssp->NAME.act[ch] - 1;
				cpssp->NAME.act[ch] = 0x0001;
				cpssp->NAME.out[ch] = 0;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);

			} else { assert(ticks < cpssp->NAME.act[ch] - 1);
				/*
				 * Timer N -> N-ticks
				 */
				cpssp->NAME.act[ch] -= ticks;
				ticks = 0;
			}
		}
		break;

	case 3:
	case 7:
		/*
		 * Mode 3
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return;
		}

		while (0 < ticks) {
			if (cpssp->NAME.act[ch] == 0x0002) {
				/*
				 * Timer 0x0002 -> N
				 */
				cpssp->NAME.act[ch] = cpssp->NAME.start[ch];
				cpssp->NAME.null_count[ch] = 0;
				cpssp->NAME.out[ch] = ! cpssp->NAME.out[ch];
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
				ticks--;

			} else if (cpssp->NAME.act[ch] & 1) {
				if (cpssp->NAME.out[ch]) {
					/*
					 * Timer N -> N-1
					 */
					cpssp->NAME.act[ch] -= 1;
				} else {
					/*
					 * Timer N -> N-3
					 */
					cpssp->NAME.act[ch] -= 3;
				}
				ticks--;

			} else if (cpssp->NAME.act[ch] / 2 - 2 <= ticks) {
				/*
				 * Timer X -> 0x0002
				 */
				ticks -= cpssp->NAME.act[ch] / 2 - 2;
				cpssp->NAME.act[ch] = 0x0002;

			} else { assert(ticks < cpssp->NAME.act[ch] / 2 - 2);
				/*
				 * Timer X -> Y
				 */
				cpssp->NAME.act[ch] -= ticks * 2;
				ticks = 0;
			}
		}
		break;

	case 4:
		/*
		 * Mode 4
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return;
		}

		while (0 < ticks) {
			if (cpssp->NAME.act[ch] == 0x0001) {
				/*
				 * Timer 0x0001 -> 0x0000
				 */
				ticks--;
				cpssp->NAME.act[ch] = 0x0000;

				cpssp->NAME.out[ch] = 0;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);

			} else if (cpssp->NAME.act[ch] == 0x0000) {
				/*
				 * Timer 0x0000 -> 0xffff
				 */
				ticks--;
				cpssp->NAME.act[ch] = 0xffff;

				cpssp->NAME.out[ch] = 1;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);

			} else if (cpssp->NAME.act[ch] - 1 <= ticks) {
				/*
				 * Timer X -> 0x0001
				 */
				ticks -= cpssp->NAME.act[ch] - 1;
				cpssp->NAME.act[ch] = 0x0001;

			} else { assert(ticks < cpssp->NAME.act[ch] - 1);
				/*
				 * Timer X -> Y
				 */
				cpssp->NAME.act[ch] -= ticks;
				ticks = 0;
			}
		}
		break;

	case 5:
		/*
		 * Mode 5
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		assert(0);

	default:
		fprintf(stderr, "cpssp->NAME.mode[%d]=%d\n", ch, cpssp->NAME.mode[ch]);
		assert(0);
	}
}

static void
NAME_(tick_all)(struct cpssp *cpssp, unsigned long ticks)
{
	int ch;

	assert(0 < ticks);

	for (ch = 0; ch < 3; ch++) {
		NAME_(tick_one)(cpssp, ch, ticks);
	}
}

static void
NAME_(update)(struct cpssp *cpssp)
{
	unsigned long long cur_tsc;
	unsigned long delta;
	unsigned long ticks;

	cur_tsc = time_virt();

	assert(cpssp->NAME.last_event_tsc <= cur_tsc);

	/* Might overflow on fast machines with high load - FIXME Florian */
	delta = (unsigned long) (cur_tsc - cpssp->NAME.last_event_tsc);
	ticks = delta / cpssp->NAME.tsc_to_timer;
	cpssp->NAME.last_event_tsc = cur_tsc - delta % cpssp->NAME.tsc_to_timer;
	
	assert(cpssp->NAME.last_event_tsc <= cur_tsc);
	
	if (0 < ticks) {
		NAME_(tick_all)(cpssp, ticks);
	}
}

static unsigned long
NAME_(next_one)(struct cpssp *cpssp, unsigned int ch)
{
	assert(/* 0 <= ch && */ ch < 3);

	switch (cpssp->NAME.mode[ch]) {
	case 0:
		/*
		 * Mode 0
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.null_count[ch] == 1) {
			/* Next clk will load start value. */
			return 0x00000001;
		}

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return 0xffffffff;
		}

		if (cpssp->NAME.out[ch] == 1) {
			/* Timer already expired. */
			return 0xffffffff;
		}

		/* Timer running. */
		return cpssp->NAME.act[ch];

	case 1:
		/*
		 * Mode 1
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */
		assert(0);				/* FIXME VOSSI */

	case 2:
	case 6:
		/*
		 * Mode 2
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return 0xffffffff;
		}

		if (cpssp->NAME.act[ch] == 0x0000) {
			return cpssp->NAME.start[ch];
		} else {
			return cpssp->NAME.act[ch];
		}

	case 3:
	case 7:
		/*
		 * Mode 3
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return 0xffffffff;
		}

		if (cpssp->NAME.act[ch] == 0x0002) {
			return 1;
		} else if (cpssp->NAME.act[ch] & 1) {
			if (cpssp->NAME.out[ch]) {
				return (cpssp->NAME.act[ch] - 1) / 2;
			} else {
				return (cpssp->NAME.act[ch] - 3) / 2;
			}
		} else {
			return cpssp->NAME.act[ch] / 2;
		}

	case 4:
		/*
		 * Mode 4
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */

		if (cpssp->NAME.gate[ch] == 0) {
			/* Timer stopped. */
			return 0xffffffff;
		}

		return cpssp->NAME.act[ch] - 1;

	case 5:
		/*
		 * Mode 5
		 */
		assert(! cpssp->NAME.bcd[ch]);	/* FIXME VOSSI */
		assert(0);				/* FIXME VOSSI */

	default:
		assert(0);
		return 0xffffffff;
	}
}

static unsigned long
NAME_(next_all)(struct cpssp *cpssp)
{
	unsigned long next;
	int ch;

	next = 0xffffffff;

	for (ch = 0; ch < 3; ch++) {
		unsigned long tmp;

		tmp = NAME_(next_one)(cpssp, ch);
		if (tmp < next) {
			next = tmp;
		}
	}

	return next;
}

static void
NAME_(stop)(struct cpssp *cpssp)
{
	if (cpssp->NAME.running) {	
		int ret;

		ret = time_call_delete(NAME_(event), cpssp);
		assert(ret == 0);

		cpssp->NAME.running = 0;
	}

	NAME_(update)(cpssp);
}

static void
NAME_(start)(struct cpssp *cpssp)
{
	unsigned long next;

	assert(! cpssp->NAME.running);

	next = NAME_(next_all)(cpssp);

	if (next == 0xffffffff) {
		/* No next event. */
		return;
	}
	next++; /* FIXME VOSSI */
	assert(1 <= next);

	if (cpssp->state_power) {
		cpssp->NAME.running = 1;
		time_call_at(cpssp->NAME.last_event_tsc
				+ (unsigned long long) next * cpssp->NAME.tsc_to_timer,
				NAME_(event), cpssp);
	}
}

static void
NAME_(event)(void *_cpssp)
{
	struct cpssp *cpssp = (struct cpssp *) _cpssp;

	/* assert(cpssp->NAME.running); */
	cpssp->NAME.running = 0;

	NAME_(stop)(cpssp);
	NAME_(start)(cpssp);
}

static void
NAME_(gate_set)(struct cpssp *cpssp, unsigned int ch, unsigned char flag)
{
	NAME_(stop)(cpssp);

	cpssp->NAME.gate[ch] = flag;

	switch (cpssp->NAME.mode[ch]) {
	case 2:
		if (flag == 0) {
			cpssp->NAME.out[ch] = 1;
			NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
			NAME_(out_period_set)(cpssp, ch, (unsigned long long) -1);
		} else {
			/* Reload counter. */
			/* FIXME VOSSI */
		}
		break;
	}

	NAME_(start)(cpssp);
}

static int
NAME_(timer_get)(struct cpssp *cpssp, unsigned int ch)
{
	int ret;

	assert(ch == 2);

	NAME_(stop)(cpssp);

	/* Speaker timer. */
	ret = cpssp->NAME.out[ch];

	NAME_(start)(cpssp);

	return ret;
}

static unsigned char
NAME_(_inb)(struct cpssp *cpssp, unsigned short port)
{
	unsigned char value;

	if (cpssp->NAME.status_latched[port]) {
		value = cpssp->NAME.status[port];
		cpssp->NAME.status_latched[port] = 0;

	} else {
		switch (port) {
		case 0: /* clock timer */
		case 1: /* memory refresh timer */
		case 2: /* speaker timer */
			// assert(port == 0 || port == 2);

			if (cpssp->NAME.latch_state[port] & 1) {
				/* Reading low byte of latch. */
				value = (cpssp->NAME.latch[port] >> 0) & 0xff;
				cpssp->NAME.latch_state[port] &= ~1;

			} else if (cpssp->NAME.latch_state[port] & 2) {
				/* Reading high byte of latch. */
				value = (cpssp->NAME.latch[port] >> 8) & 0xff;
				cpssp->NAME.latch_state[port] &= ~2;

			} else if (cpssp->NAME.read_state[port] & 1) {
				/* Reading low byte of counter. */
				value = (cpssp->NAME.act[port] >> 0) & 0xff;
				cpssp->NAME.read_state[port] &= ~1;

			} else if (cpssp->NAME.read_state[port] & 2) {
				/* Reading high byte of counter. */
				value = (cpssp->NAME.act[port] >> 8) & 0xff;
				cpssp->NAME.read_state[port] &= ~2;

			} else {
				assert(0);	/* Mustn't happen. */
				value = 0;	/* To make gcc happy. */
			}
			if (cpssp->NAME.read_state[port] == 0) {
				cpssp->NAME.read_state[port] = cpssp->NAME.rw[port];
			}
			break;

		case 3:
			/* control register */
			/* write-only */
#if 0	/* FIXME VOSSI */
			assert(0);
#else
			fprintf(stderr, "WARNING: reading reg 3 of PIT!\n");
			value = 0;
#endif
			break;

		default:
			assert(0);
			value = 0;	/* To make gcc happy. */
		}
	}

#if DEBUG_PIT
	fprintf(stderr, "%s: read %02x from port %u\n",
			__FUNCTION__, value, port);
#endif
	return value;
}

static void
NAME_(inb)(struct cpssp *cpssp, unsigned char *valuep, unsigned short port)
{
	NAME_(stop)(cpssp);
	*valuep = NAME_(_inb)(cpssp, port);
	NAME_(start)(cpssp);
}

static void
NAME_(_outb)(struct cpssp *cpssp, unsigned char value, unsigned short port)
{
#if DEBUG_PIT
	fprintf(stderr, "%s: write 0x%02x to port %d\n", __FUNCTION__,
			value, port);
#endif

	switch (port) {
	case 0: /* clock timer */
	case 1: /* memory refresh timer */
	case 2: /* speaker timer */

		if (cpssp->NAME.write_state[port] & 0x1) {
			/* should write low byte of counter */
			cpssp->NAME.start[port] &= 0xff00;
			cpssp->NAME.start[port] |= value << 0;
			cpssp->NAME.write_state[port] &= ~0x1;

			switch (cpssp->NAME.mode[port]) {
			case 0:
				cpssp->NAME.act[port] &= 0xff00;
				cpssp->NAME.act[port] |= value << 0;
				break;
			}

		} else if (cpssp->NAME.write_state[port] & 0x2) {
			/* should write high byte of counter */
			cpssp->NAME.start[port] &= 0x00ff;
			cpssp->NAME.start[port] |= value << 8;
			cpssp->NAME.write_state[port] &= ~0x2;

			switch (cpssp->NAME.mode[port]) {
			case 0:
				cpssp->NAME.act[port] &= 0x00ff;
				cpssp->NAME.act[port] |= value << 8;
				break;
			}

			if (cpssp->NAME.start[port] == 0) {
				NAME_(out_period_set)(cpssp, port,
					(unsigned long long) -1);
			} else {
				NAME_(out_period_set)(cpssp, port,
					TIME_HZ / UMPIT_REAL_HZ * cpssp->NAME.start[port]);
			}
		}

		if ((cpssp->NAME.write_state[port] & 0x3) == 0) {
			cpssp->NAME.write_state[port]
				= cpssp->NAME.rw[port];
			cpssp->NAME.null_count[port] = 1;

			switch (cpssp->NAME.mode[port]) {
			case 0:
				cpssp->NAME.out[port] = 0;
				NAME_(out_val_set)(cpssp, port, cpssp->NAME.out[port]);
				break;
			}
		}
		break;

	case 3: {
		/*
		 * Control register
		 */
		unsigned char ch;

		if (((value >> 6) & 3) == 3) {
			/*
			 * Read-back command.
			 */
			for (ch = 0; ch < 3; ch++) {
				if (! (value & (2 << ch))) {
					continue;
				}
				if (! (value & 0x20)
				 && ! cpssp->NAME.latch_state[ch]) {
					cpssp->NAME.latch[ch] = cpssp->NAME.act[ch];
					cpssp->NAME.latch_state[ch] = cpssp->NAME.rw[ch];
				}
				if (! (value & 0x10)
				 && ! cpssp->NAME.status_latched[ch]) {
					/* Status latch */
					/* FIXME: add BCD and null count */
					cpssp->NAME.status[ch] = 0 /* FIXME VOSSI */
						| (cpssp->NAME.rw[ch] << 4)
						| (cpssp->NAME.mode[ch] << 1)
						| (cpssp->NAME.bcd[ch] << 0);
					cpssp->NAME.status_latched[ch] = 1;
				}
			}

		} else if (((value >> 4) & 0x03) == 0) {
			/*
			 * Latch command.
			 */
			ch = (value >> 6) & 3;
			assert(/* 0 <= ch && */ ch < 3);

			cpssp->NAME.latch[ch] = cpssp->NAME.act[ch];
			cpssp->NAME.latch_state[ch] = cpssp->NAME.rw[ch];

		} else {
			/*
			 * Set command.
			 */
			ch = (value >> 6) & 3;
			assert(/* 0 <= ch && */ ch < 3);

			cpssp->NAME.rw[ch] = (value >> 4) & 0x3;
			cpssp->NAME.mode[ch] = (value >> 1) & 0x7;
			cpssp->NAME.bcd[ch] = (value >> 0) & 0x1;
			cpssp->NAME.write_state[ch] = (value >> 4) & 0x3;
			cpssp->NAME.read_state[ch] = (value >> 4) & 0x3;
			cpssp->NAME.latch_state[ch] = 0x0;
			cpssp->NAME.null_count[ch] = 1;

			switch (cpssp->NAME.mode[ch]) {
			case 0:
				cpssp->NAME.out[ch] = 0;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
				break;
			case 2:
				cpssp->NAME.out[ch] = 1;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
				break;
			case 4:
				cpssp->NAME.out[ch] = 1;
				NAME_(out_val_set)(cpssp, ch, cpssp->NAME.out[ch]);
				break;
			}
		}
		break;
	    }
	default:
		assert(0);
	}
}

static void
NAME_(outb)(struct cpssp *cpssp, unsigned char value, unsigned short port)
{
	NAME_(stop)(cpssp);
	NAME_(_outb)(cpssp, value, port);
	NAME_(start)(cpssp);
}

static void
NAME_(reset)(struct cpssp *cpssp)
{
	unsigned int ch;

	/* More to reset? - FIXME VOSSI */

	cpssp->NAME.last_event_tsc = 0;

	for (ch = 0; ch < 3; ch++) {
		cpssp->NAME.rw[ch] = 3;
		cpssp->NAME.mode[ch] = 0;
		cpssp->NAME.bcd[ch] = 0;
		cpssp->NAME.start[ch] = 0;
		cpssp->NAME.write_state[ch] = 0;
		cpssp->NAME.act[ch] = 0;
		cpssp->NAME.read_state[ch] = 0;
		cpssp->NAME.latch[ch] = 0;
		cpssp->NAME.latch_state[ch] = 0;
		cpssp->NAME.null_count[ch] = 0;
	}

	/* FIXME VOSSI */
	cpssp->NAME.gate[0] = 1;
	cpssp->NAME.gate[1] = 1;
	cpssp->NAME.gate[2] = 0;
	
	cpssp->NAME.running = 0;
}

static void
NAME_(init)(struct cpssp *cpssp)
{
	cpssp->NAME.tsc_to_timer = TIME_HZ / UMPIT_REAL_HZ;

	/* Must be a valid value */
	assert(80 < cpssp->NAME.tsc_to_timer);
}

#endif /* BEHAVIOR */
