/* parport.c - Parallel port I/O functions
 * 
 * Copyright (C) 2004, 2005 Anderson Lizardo
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#ifdef __linux__
#  include <linux/parport.h>
#  include <linux/ppdev.h>
#elif defined (__FreeBSD__) || defined(__FreeBSD_kernel__)
#  include <dev/ppbus/ppi.h>
#  include <dev/ppbus/ppbconf.h>
#  define PPRDATA	PPIGDATA
#  define PPWDATA	PPISDATA
#  define PPRSTATUS	PPIGSTATUS
#  define PPWSTATUS	PPISSTATUS
#  define PPRCONTROL	PPIGCTRL
#  define PPWCONTROL	PPISCTRL
/* Not available anymore in userland in FreeBSD 8, for no good reason */
#  define PPB_COMPATIBLE 0x0
#  define PPB_EPP        0x4
#endif

#define DEBUG_DECLARE_ONLY
#include "geniusvp2.h"
#include "geniusvp2-parport.h"

static int port_fd;

int
sane_geniusvp2_parport_open (const char *pp_device)
{
    int retval;
    unsigned int port_feature;

    port_fd = open (pp_device, O_RDWR);
    if (port_fd == -1)
    {
        DBG (1, "sane_geniusvp2_parport_open: could not open %s (%s)\n",
             pp_device, strerror (errno));
        return 1;
    }
#ifdef __linux__
    retval = ioctl (port_fd, PPCLAIM);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_parport_open: could not claim port access (%s)\n",
             strerror (errno));
        retval = close (port_fd);
        if (retval == -1)
            DBG (1, "sane_geniusvp2_parport_open: could not close port (%s)\n",
                 strerror (errno));
        return 1;
    }
    retval = ioctl (port_fd, PPGETMODES, &port_feature);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_parport_open: warning: could not get port capabilities "
             "(%s), assuming EPP is present\n", strerror (errno));
    }
    else if (!(port_feature & PARPORT_MODE_EPP))
    {
        DBG (1, "sane_geniusvp2_parport_open: this port does not support EPP mode, "
             "please enable EPP or EPP+ECP mode\n");
	retval = ioctl (port_fd, PPRELEASE);
        if (retval == -1)
            DBG (1, "sane_geniusvp2_parport_open: could not release port (%s)\n",
                 strerror (errno));
        retval = close (port_fd);
        if (retval == -1)
            DBG (1, "sane_geniusvp2_parport_open: could not close port (%s)\n",
                 strerror (errno));
        return 1;
    }
#elif defined (__FreeBSD__) || defined(__FreeBSD_kernel__)
    port_feature = PPB_EPP;
    retval = ioctl (port_fd, PPISECR, &port_feature);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_parport_open: could not switch port to EPP mode, " 
	     "please enable EPP or EPP+ECP mode (%s)\n", strerror (errno));
	return 1;
    }
#endif

    return 0;
}

int
sane_geniusvp2_parport_close (void)
{
    int retval;
#ifdef __linux__
    const int compat = IEEE1284_MODE_COMPAT;

    retval = ioctl (port_fd, PPNEGOT, &compat);
#elif defined (__FreeBSD__) || defined(__FreeBSD_kernel__)
    const int compat = PPB_COMPATIBLE;

    retval = ioctl (port_fd, PPISECR, &compat);
    if (retval == -1)
        DBG (1, "sane_geniusvp2_parport_close: warning: could not negotiate "
             "compatibility mode (%s)\n", strerror (errno));
#endif
#ifdef __linux__
    retval = ioctl (port_fd, PPRELEASE);
    if (retval == -1)
        DBG (1, "sane_geniusvp2_parport_close: could not release port (%s)\n",
             strerror (errno));
#endif    
    retval = close (port_fd);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_parport_close: could not close port (%s)\n",
             strerror (errno));
        return 1;
    }

    return 0;
}

int
sane_geniusvp2_epp_read (unsigned char *data, int size)
{
    int returned;

    while (size > 0)
    {
        returned = read (port_fd, data, size);
        if (returned < 0)
        {
            DBG (1, "sane_geniusvp2_epp_read: %s\n", strerror (errno));
            return 1;
        }
        data += returned;
        size -= returned;
    }

    return 0;
}

int
sane_geniusvp2_epp_write (unsigned char *data, int size)
{
    int written;

    while (size > 0)
    {
        written = write (port_fd, data, size);
        if (written < 0)
        {
            DBG (1, "sane_geniusvp2_epp_write: %s\n", strerror (errno));
            return 1;
        }
        data += written;
        size -= written;
    }

    return 0;
}

#ifdef __linux__
int
sane_geniusvp2_epp_set_mode (int mode)
{
    int retval;

    mode |= IEEE1284_MODE_EPP;
    retval = ioctl (port_fd, PPSETMODE, &mode);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_epp_set_mode: %s\n", strerror (errno));
        return 1;
    }

    return 0;
}
#elif defined (__FreeBSD__) || defined(__FreeBSD_kernel__)
int sane_geniusvp2_epp_write_addr (unsigned char addr)
{
    int retval;

    retval = ioctl (port_fd, PPISEPPA, &addr);
    if (retval == -1)
    {
        DBG (1, "sane_geniusvp2_epp_write_addr: %s\n", strerror (errno));
        return 1;
    }

    return 0;

}
#endif

int
sane_geniusvp2_pp_write (unsigned long int line, unsigned char data)
{
    switch (line)
    {
    case PP_DATA:
        return ioctl (port_fd, PPWDATA, &data);
        break;

    case PP_CONTROL:
        return ioctl (port_fd, PPWCONTROL, &data);
        break;
    }

    return 0;
}

int
sane_geniusvp2_pp_read (unsigned long int line, unsigned char *data)
{
    switch (line)
    {
    case PP_DATA:
        return ioctl (port_fd, PPRDATA, data);
        break;

    case PP_STATUS:
        return ioctl (port_fd, PPRSTATUS, data);
        break;

    case PP_CONTROL:
        return ioctl (port_fd, PPRCONTROL, data);
        break;
    }

    return 0;
}
