/***************************************************************************
         byteblaster.c - Low-level functions to access the byteblaster
         -------------------------------------------------------------
			    
    begin                : Sun Jan 09 2005
    copyright            : (C) 2005 by Aurelien Jarno
    email                : aurelien@aurel32.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/* Application specific includes */
#include "byteblaster.h"
#include "main.h"

/* Standard includes */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#ifdef HAVE_LINUX_PPDEV_H
#  include <linux/ppdev.h>
#endif
#ifdef HAVE_LINUX_PARPORT_H
#  include <linux/parport.h>
#endif
#ifdef HAVE_DEV_PPBUS_PPI_H
#  include <dev/ppbus/ppi.h>
#endif
#ifdef HAVE_DEV_PPBUS_PPBCONF_H
#  include <dev/ppbus/ppbconf.h>
#  define PPRDATA 			PPIGDATA
#  define PPWDATA 			PPISDATA

#  define PPRSTATUS			PPIGSTATUS 
#  define PPWSTATUS			PPISSTATUS

#  define PPRCONTROL			PPIGCTRL
#  define PPWCONTROL			PPISCTRL

#  define PARPORT_CONTROL_AUTOFD 	AUTOFEED
#  define PARPORT_STATUS_ACK 		nACK
#  define PARPORT_STATUS_SELECT 	SELECT
#  define PARPORT_STATUS_BUSY 		nBUSY
#endif

/***************************************************************************
 *                                                                         *
 *   Private functions                                                     *
 *                                                                         *
 ***************************************************************************/

static void out_data(int device, unsigned char mask, unsigned char val)
{
	static unsigned char data = 0;

	data = (data & ~mask) | val;
	ioctl(device, PPWDATA, &data);
}

static void set_AUTOFEED(int device, int value)
{
	unsigned char control;

	ioctl(device, PPRCONTROL, &control);
	control &= ~PARPORT_CONTROL_AUTOFD;
	if (value != 0)
		control |= PARPORT_CONTROL_AUTOFD;
	ioctl(device, PPWCONTROL, &control);
	usleep(100000);
}

static void set_D5(int device, int value)
{
	/* D5 */
	out_data(device, 0x20, (value != 0)? 0x20 : 0x00);
}

static int get_ACK(int device)
{
        unsigned char status;
	
	ioctl(device, PPRSTATUS, &status);
	return (status & PARPORT_STATUS_ACK) == PARPORT_STATUS_ACK;
}

static int open_parport(char *device_name)
{
	int dev;

	if (debug)
		printf("Opening %s\n", device_name);

	dev = open(device_name, O_RDWR);
#ifdef PPCLAIM
	if (dev < 0)
	{
		if (!quiet)
			perror("open");
		return -1;
	}
	if (ioctl(dev, PPCLAIM) < 0)
	{
		if (!quiet)
			perror("open");
		perror("ioctl");
		close(dev);
		return -1;
	}
#endif
	return dev;
}

static void close_parport(int device)
{
	if (device)
	{
#ifdef PPRELEASE
		ioctl(device, PPRELEASE);
#endif
		close(device);
	}
}

static int detect_byteblaster(int device)
{
	if (debug)
		printf("Detecting byteblaster\n");

	/* ACK and D5 are connected together */
	if (debug)
		printf("Driving D5 low\n");
	set_D5(device, 0);
	usleep(10000);
	if (get_ACK(device) == 1)
	{
		if (debug)
			fprintf(stderr, "ACK stuck high\n");
		return -1;
	}
	
	if (debug)
		printf("Driving D5 high\n");
	set_D5(device, 1);
	usleep(10000);
	if (get_ACK(device) == 0)
	{
		if (debug)
			fprintf(stderr, "ACK stuck low\n");
		return -1;
	}
	
	if (debug)
		printf("Driving D5 low\n");
	set_D5(device, 0);
	usleep(10000);
	if (get_ACK(device) == 1)
	{
		if (debug)
			fprintf(stderr, "ACK stuck high\n");
		return -1;
	}

	return 0;
}


/***************************************************************************
 *                                                                         *
 *   Public functions                                                      *
 *                                                                         *
 ***************************************************************************/

int get_nSTATUS(int device)
{
        unsigned char status;
	
	ioctl(device, PPRSTATUS, &status);
	return (status & PARPORT_STATUS_SELECT) == PARPORT_STATUS_SELECT;
}

int get_CONF_DONE(int device)
{
	unsigned char status;

	ioctl(device, PPRSTATUS, &status);
	return (status & PARPORT_STATUS_BUSY) != PARPORT_STATUS_BUSY;
}

void set_DCLK(int device, int value)
{
	/* D0 */
	out_data(device, 0x01, (value != 0)? 0x01 : 0x00);
}

void set_nCONFIG(int device, int value)
{
	/* D1 */
	out_data(device, 0x02, (value != 0)? 0x02 : 0x00);
}

void set_DATA0(int device, int value)
{
	/* D6 */
	out_data(device, 0x40, (value != 0)? 0x40 : 0x00);
}

int open_byteblaster(char *devicename)
{
	int dev;
	
	dev = open_parport(devicename);
	if (dev < 0 )
	{
		if (!quiet)
			fprintf(stderr, "Can't open %s!\n", devicename);
		return -1;
	}
	
	if (detect_byteblaster(dev) < 0)
	{
		if (!quiet)
			fprintf(stderr, "Byteblaster not found on %s!\n", devicename);
		close_byteblaster(dev);
		return -1;	
	}
	
	return dev;
}

void close_byteblaster(int device)
{
	enable_byteblaster(device, 0);
	close_parport(device);
}

void enable_byteblaster(int device, int value)
{
	set_AUTOFEED(device, value);
}