#include <unistd.h>
#include <ddi.h>
#include <libarch/ddi.h>
#include <stdio.h>

#include "isa.h"
#include "serial.h"

#define NAME "serial"

#define REG_COUNT 7
#define COM1 0x3F8
#define COM2 0x2F8

static ioport8_t *com1 = 0, *com2 = 0;

static void serial_init_port(ioport8_t *port);
static void serial_write_8(ioport8_t *port, uint8_t c);
static bool is_transmit_empty(ioport8_t *port);
static uint8_t serial_read_8(ioport8_t *port);
static bool serial_received(ioport8_t *port);
static void serial_probe(bridge_to_isa_t *parent);
static void * serial_probe_port(void *phys_addr);

static isa_drv_ops_t serial_isa_ops = {
	.probe = serial_probe	
};

static isa_drv_t serial_isa_drv = {
	.name = NAME,
	.ops = &serial_isa_ops	
};

int serial_init()
{
	// TODO: register this driver by generic isa bus driver
	
	isa_register_driver(&serial_isa_drv);
	return 1;
}

static bool serial_received(ioport8_t *port) 
{
   return (pio_read_8(port + 5) & 1) != 0;
}

static uint8_t serial_read_8(ioport8_t *port) 
{
	while (!serial_received(port)) 
		;

	uint8_t c = pio_read_8(port);
	return c;
}

static bool is_transmit_empty(ioport8_t *port) 
{
   return (pio_read_8(port + 5) & 0x20) != 0;
}

static void serial_write_8(ioport8_t *port, uint8_t c) 
{
	while (!is_transmit_empty(port)) 
		;
	
	pio_write_8(port, c);
}

static void serial_init_port(ioport8_t *port) 
{	
	pio_write_8(port + 1, 0x00);    // Disable all interrupts
	pio_write_8(port + 3, 0x80);    // Enable DLAB (set baud rate divisor)
	pio_write_8(port + 0, 0x60);    // Set divisor to 96 (lo byte) 1200 baud
	pio_write_8(port + 1, 0x00);    //                   (hi byte)
	pio_write_8(port + 3, 0x07);    // 8 bits, no parity, two stop bits
	pio_write_8(port + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
	pio_write_8(port + 4, 0x0B);    // IRQs enabled, RTS/DSR set
}

static void serial_probe(bridge_to_isa_t *parent)
{
	printf(NAME " driver: probe()\n");	

	printf(NAME " driver: probing com1 \n");
	if (com1 = (ioport8_t *)serial_probe_port(parent->ops->absolutize(COM1))) {
		printf(NAME " driver: initializing com1 \n");
		serial_init_port(com1);
	}	else {
		printf(NAME " driver: com1 is not present \n");
	}
	
	printf(NAME " driver: probing com2 \n");
	if (com2 = (ioport8_t *)serial_probe_port(parent->ops->absolutize(COM2))) {
		printf(NAME " driver: initializing com2 \n");
		serial_init_port(com2);
	}
	else {
		printf(NAME " driver: com2 is not present \n");
	}	
}

// returns virtual address of the serial port, if the serial port is present at this physical address, NULL otherwise  
static void * serial_probe_port(void *phys_addr)
{
	ioport8_t *port_addr = NULL;
	
	if (pio_enable(phys_addr, REG_COUNT, (void **)(&port_addr))) {  // Gain control over port's registers.
		printf(NAME ": Error - cannot gain the port %lx.\n", phys_addr);
		return NULL;
	}
	
	uint8_t olddata;
	
	olddata = pio_read_8(port_addr + 4);
	pio_write_8(port_addr + 4, 0x10);
	if ((pio_read_8(port_addr + 6) & 0xf0)) {
		return NULL;
	}
	
	pio_write_8(port_addr + 4, 0x1f);
	if ((pio_read_8(port_addr + 6) & 0xf0) != 0xf0) {
		return 0;
	}
	pio_write_8(port_addr + 4, olddata);
	
	return port_addr;
}
