/*
 * setest.c
 *
 * Purpose
 *	Test the serial port.
 *
 * Description
 *	An external loopback dongle is required to use this program.
 *	Twiddling the serial port also requires executiion by root.
 *
 * History
 *	Jul 26, 1999 - Andrew E. Mileski <andrewm@netwinder.org>
 *	General cleanup and commit to CVS.
 *
 *	Jul 2, 1999 - Woody Suwalski <woody@netwinder.org>
 *	Concevied, coded, and tested.
 */

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>

/* these globals handle the differences between Linux 2.0.x
 and 2.2.x */
#define IO_BASE_20 0xE0000000
#define IO_BASE_22 0x7C000000
#define PAGE_SIZE 0x1000

static long io_base = IO_BASE_22;
static char* pIO;

#define outb(p, v)    *(pIO+(p)) = (char)v
#define inb(p) *(pIO+(p))


/* Signal bits */
#define DTR		0x01
#define RTS		0x02
#define CTS		0x10
#define DSR		0x20
#define RI		0x40
#define DCD		0x80

/* Flags */
#define DATA_READY	0x01

/* Register addresses */
#define UARTBASE 	0x3f8
#define XMITREG		(UARTBASE + 0)
#define RECVREG		(UARTBASE + 0)
#define CTRLREG		(UARTBASE + 4)
#define UARTREG		(UARTBASE + 5)
#define STATREG		(UARTBASE + 6)

static int devmem = 0;

void dump_regs(int debugflag)
{
    int	i;

	if (!debugflag)
		return;

	printf("UART: ");

	for(i = 0; i < 8; i++)
		printf("0x%02x ", inb(UARTBASE + i));

	printf("\n");
}

int main(int argc, char *argv[])
{
	volatile unsigned char ctemp;
	int i;
	int debugflag = 0;
	char textline[256];

	fprintf(stderr, "Serial Port Test Tool V.1.0, REBEL.COM 1999\n");

	if (getuid()) {
		fprintf (stderr, "This program must be run as root!\n");
		return 1;
	}

	devmem = open ("/dev/mem", O_RDWR);
	if (devmem < 0) {
		perror("Could not open /dev/mem");
		return 1;
	}


	i=open("/proc/version",O_RDONLY);
	if (i==-1)
	{
	    printf("\n/proc directory is not mounted; assuming Linux 2.2.x");
	}
	else
	{
	    read(i,textline,255);
	    close(i);
	    if (strstr(textline,"2.0."))
	    {
		printf("Using Linux 2.0 memory map\n");
    		io_base = IO_BASE_20;
	    }
	}

	pIO = mmap(0,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED,devmem,io_base);

        if (pIO == NULL)
              printf ("Memory access error!\n");
              
	dump_regs(debugflag);

	/* Read the UART queue (16 bytes) to flush any potential garbage */
	outb(CTRLREG, 0);
	usleep(100);
	outb(XMITREG, 0x96);
	usleep(100);
	ctemp = inb(UARTREG);
	ctemp = inb(UARTREG);
	for (i = 1; i <= 32 && (ctemp & DATA_READY); i++) {
		usleep(100);
		ctemp = inb(RECVREG);
		usleep(100);
		ctemp = inb(UARTREG);
	}

	/* UART is bad if we have to read more than 16 times */
	if (i > 17) {
		fprintf(stderr, "Error: UART constantly generating data!\n");
		dump_regs(1);
		return 1;
	}

	/* Now UART is empty, so send 1 char and see if it is received */
	outb (XMITREG, 0xA5);
	usleep(100);
	ctemp = inb(UARTREG);
	if (!ctemp & DATA_READY) {
		fprintf(stderr, "Error: Serial port not receiving data!\n");
		dump_regs(1);
		return 2;
	}
	usleep(100);
	ctemp = inb(RECVREG);
	if (ctemp != 0xA5) {
		fprintf(stderr, "Error: Serial port receiving bad data!(0xA5 != 0x%02X)\n",ctemp);
		dump_regs(1);
		return 3;
	}


	/* Repeat with a different char */
	usleep(100);
	outb (XMITREG, 0x5A);
	usleep(100);
	ctemp = inb(UARTREG);
	if (!ctemp & DATA_READY) {
		fprintf(stderr, "Error: Serial port not receiving data(2)!\n");
		dump_regs(1);
		return 2;
	}
	usleep(100);
	ctemp = inb(RECVREG);
	if (ctemp != 0x5A) {
		fprintf(stderr, "Error: Serial port receiving bad data (2)! (0x5A != 0x%02X)\n",ctemp);
		dump_regs(1);
		return 3;
	}

	dump_regs(debugflag);

	/* clear DTR and RTS bits */
	outb (CTRLREG, 0);
	usleep(100);
	ctemp = inb(STATREG);
	if ((ctemp & CTS) || (ctemp & DSR) || (ctemp & RI) || (ctemp & DCD)) {
		fprintf(stderr, "Error: Serial port control lines problem!\n");
		dump_regs(1);
		exit(4);
	}

	dump_regs(debugflag);

	outb (CTRLREG, DTR);
	usleep(100);
	ctemp = inb(STATREG);
	if (!(ctemp & CTS) || !(ctemp & DSR) || (ctemp & RI) || (ctemp & DCD)) {
		fprintf(stderr, "Error: Serial port DTR control line problem!\n");
		dump_regs(1);
		return 5;
	}

	dump_regs(debugflag);

	outb (CTRLREG, RTS);
	usleep(100);
	dump_regs(debugflag);
	ctemp = inb(STATREG);
	if ((ctemp & CTS) || (ctemp & DSR) || !(ctemp & RI) || !(ctemp & DCD)) {
		fprintf(stderr, "Error: Serial port RTS control line problem!\n");
		dump_regs(1);
		return 6;
	}

	dump_regs(debugflag);

	fprintf(stderr, "All tests were successful\n");
	munmap(pIO,PAGE_SIZE);

	close (devmem);
	return 0;
}

