/*      $NetBSD: ss_scanjet.c,v 1.54 2016/11/20 15:37:19 mlelstv Exp $  */

/*
* Copyright (c) 1995 Kenneth Stailey.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by Kenneth Stailey.
* 4. The name of the author may not be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
* special functions for the HP ScanJet IIc and IIcx
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ss_scanjet.c,v 1.54 2016/11/20 15:37:19 mlelstv Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/conf.h>           /* for cdevsw */
#include <sys/scanio.h>
#include <sys/kernel.h>

#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsi_scanner.h>
#include <dev/scsipi/scsipiconf.h>
#include <dev/scsipi/scsipi_base.h>
#include <dev/scsipi/ssvar.h>

#define SCANJET_RETRIES 4

static int      scanjet_get_params(struct ss_softc *);
static int      scanjet_set_params(struct ss_softc *, struct scan_io *);
static int      scanjet_trigger_scanner(struct ss_softc *);
static int      scanjet_read(struct ss_softc *, struct buf *);

/* only used internally */
static int      scanjet_ctl_write(struct ss_softc *, char *, u_int);
static int      scanjet_ctl_read(struct ss_softc *, char *, u_int);
static int      scanjet_set_window(struct ss_softc *);
static int      scanjet_compute_sizes(struct ss_softc *);

/* structure for the special handlers */
static struct ss_special scanjet_special = {
       scanjet_set_params,
       scanjet_trigger_scanner,
       scanjet_get_params,
       NULL,                   /* no special minphys */
       scanjet_read,           /* scsi 6-byte read */
       NULL,                   /* no "rewind" code (yet?) */
       NULL,                   /* no adf support right now */
       NULL                    /* no adf support right now */
};

/* scanjet_attach: attach special functions to ss */
void
scanjet_attach(struct ss_softc *ss, struct scsipibus_attach_args *sa)
{
       int error;

       SC_DEBUG(ss->sc_periph, SCSIPI_DB1, ("scanjet_attach: start\n"));
       ss->sio.scan_scanner_type = 0;

       printf("%s: ", device_xname(ss->sc_dev));

       /* first, check the model (which determines nothing yet) */
       if (!memcmp(sa->sa_inqbuf.product, "C1750A", 6)) {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet IIc");
       } else if (!memcmp(sa->sa_inqbuf.product, "C2500A", 6)) {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet IIcx");
       } else if (!memcmp(sa->sa_inqbuf.product, "C2520A", 6)) {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet 4c");
       } else if (!memcmp(sa->sa_inqbuf.product, "C1130A", 6)) {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet 4p");
       } else if (!memcmp(sa->sa_inqbuf.product, "C5110A", 6)) {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet 5p");
       } else {
               ss->sio.scan_scanner_type = HP_SCANJET_IIC;
               printf("HP ScanJet (unknown model)");
       }

       SC_DEBUG(ss->sc_periph, SCSIPI_DB1,
           ("scanjet_attach: scanner_type = %d\n",
           ss->sio.scan_scanner_type));

       /* now install special handlers */
       ss->special = &scanjet_special;

       /* populate the scanio struct with legal values */
       ss->sio.scan_width              = 1200;
       ss->sio.scan_height             = 1200;
       ss->sio.scan_x_resolution       = 100;
       ss->sio.scan_y_resolution       = 100;
       ss->sio.scan_x_origin           = 0;
       ss->sio.scan_y_origin           = 0;
       ss->sio.scan_brightness         = 128;
       ss->sio.scan_contrast           = 128;
       ss->sio.scan_quality            = 100;
       ss->sio.scan_image_mode         = SIM_GRAYSCALE;

       error = scanjet_set_window(ss);
       if (error) {
               printf(" set_window failed\n");
               return;
       }
       error = scanjet_compute_sizes(ss);
       if (error) {
               printf(" compute_sizes failed\n");
               return;
       }
       printf("\n");
}

static int
scanjet_get_params(struct ss_softc *ss)
{
       return 0;
}

/*
* check the parameters if the scanjet is capable of fulfilling it
* but don't send the command to the scanner in case the user wants
* to change parameters by more than one call
*/
static int
scanjet_set_params(struct ss_softc *ss, struct scan_io *sio)
{
       int error;

#if 0
       /*
        * if the scanner is triggered, then rewind it
        */
       if (ss->flags & SSF_TRIGGERED) {
               error = scanjet_rewind_scanner(ss);
               if (error)
                       return error;
       }
#endif

       /* size constraints... */
       if (sio->scan_width == 0                                 ||
           sio->scan_x_origin + sio->scan_width > 10200 || /* 8.5" */
           sio->scan_height == 0                                ||
           sio->scan_y_origin + sio->scan_height > 16800)  /* 14" */
               return EINVAL;

       /* resolution (dpi)... */
       if (sio->scan_x_resolution < 100 ||
           sio->scan_x_resolution > 400 ||
           sio->scan_y_resolution < 100 ||
           sio->scan_y_resolution > 400)
               return EINVAL;

       switch (sio->scan_image_mode) {
       case SIM_BINARY_MONOCHROME:
       case SIM_DITHERED_MONOCHROME:
       case SIM_GRAYSCALE:
       case SIM_COLOR:
               break;
       default:
               return EINVAL;
       }

       /* change ss_softc to the new values, but save ro-variables */
       sio->scan_scanner_type = ss->sio.scan_scanner_type;
       memcpy(&ss->sio, sio, sizeof(struct scan_io));

       error = scanjet_set_window(ss);
       if (error) {
               uprintf("%s: set_window failed\n", device_xname(ss->sc_dev));
               return error;
       }
       error = scanjet_compute_sizes(ss);
       if (error) {
               uprintf("%s: compute_sizes failed\n", device_xname(ss->sc_dev));
               return error;
       }
       return 0;
}

/*
* trigger the scanner to start a scan operation
* this includes sending the mode- and window-data,
* and starting the scanner
*/
static int
scanjet_trigger_scanner(struct ss_softc *ss)
{
       char escape_codes[20];
       int error;

       error = scanjet_set_window(ss);
       if (error) {
               uprintf("%s: set_window failed\n", device_xname(ss->sc_dev));
               return error;
       }
       error = scanjet_compute_sizes(ss);
       if (error) {
               uprintf("%s: compute_sizes failed\n", device_xname(ss->sc_dev));
               return error;
       }

       /* send "trigger" operation */
       strlcpy(escape_codes, "\033*f0S", sizeof(escape_codes));
       error = scanjet_ctl_write(ss, escape_codes, strlen(escape_codes));
       if (error) {
               uprintf("%s: trigger_scanner failed\n",
                   device_xname(ss->sc_dev));
               return error;
       }
       return 0;
}

static int
scanjet_read(struct ss_softc *ss, struct buf *bp)
{
       struct scsi_rw_scanner cmd;
       struct scsipi_xfer *xs;
       struct scsipi_periph *periph = ss->sc_periph;
       int error __diagused;

       /* Fill out the scsi command */
       memset(&cmd, 0, sizeof(cmd));
       cmd.opcode = READ;

       /*
        * Handle "fixed-block-mode" tape drives by using the
        * block count instead of the length.
        */
       _lto3b(bp->b_bcount, cmd.len);

       /* go ask the adapter to do all this for us */
       xs = scsipi_make_xs_locked(periph,
           (struct scsipi_generic *)&cmd, sizeof(cmd),
           (u_char *)bp->b_data, bp->b_bcount,
           SCANJET_RETRIES, 100000, bp,
           XS_CTL_NOSLEEP | XS_CTL_ASYNC | XS_CTL_DATA_IN);
       if (xs == NULL) {
               /*
                * out of memory. Keep this buffer in the queue, and
                * retry later.
                */
               callout_reset(&ss->sc_callout, hz / 2, ssrestart, periph);
               return 0;
       }
#ifdef DIAGNOSTIC
       if (bufq_get(ss->buf_queue) != bp)
               panic("ssstart(): dequeued wrong buf");
#else
       bufq_get(ss->buf_queue);
#endif
       error = scsipi_execute_xs(xs);
       /* with a scsipi_xfer preallocated, scsipi_command can't fail */
       KASSERT(error == 0);
       ss->sio.scan_window_size -= bp->b_bcount;
#if 0
       if (ss->sio.scan_window_size < 0)
               ss->sio.scan_window_size = 0;
#endif
       return 0;
}


/* Do a synchronous write.  Used to send control messages. */
static int
scanjet_ctl_write(struct ss_softc *ss, char *tbuf, u_int size)
{
       struct scsi_rw_scanner cmd;
       int flags;

       flags = 0;
       if ((ss->flags & SSF_AUTOCONF) != 0)
               flags |= XS_CTL_DISCOVERY;

       memset(&cmd, 0, sizeof(cmd));
       cmd.opcode = WRITE;
       _lto3b(size, cmd.len);

       return scsipi_command(ss->sc_periph,
           (void *)&cmd, sizeof(cmd), (void *)tbuf, size, 0, 100000, NULL,
           flags | XS_CTL_DATA_OUT);
}


/* Do a synchronous read.  Used to read responses to control messages. */
static int
scanjet_ctl_read(struct ss_softc *ss, char *tbuf, u_int size)
{
       struct scsi_rw_scanner cmd;
       int flags;

       flags = 0;
       if ((ss->flags & SSF_AUTOCONF) != 0)
               flags |= XS_CTL_DISCOVERY;

       memset(&cmd, 0, sizeof(cmd));
       cmd.opcode = READ;
       _lto3b(size, cmd.len);

       return scsipi_command(ss->sc_periph,
           (void *)&cmd, sizeof(cmd), (void *)tbuf, size, 0, 100000, NULL,
           flags | XS_CTL_DATA_IN);
}


#ifdef SCANJETDEBUG
static void
show_es(char *es)
{
       char *p = es;

       while (*p) {
               if (*p == '\033')
                       printf("[Esc]");
               else
                       printf("%c", *p);
               ++p;
       }
       printf("\n");
}
#endif

/*
* simulate SCSI_SET_WINDOW for ScanJets
*/
static int
scanjet_set_window(struct ss_softc *ss)
{
       char escape_codes[128], *p, *ep;

       p = escape_codes;
       ep = &escape_codes[128];

       p += snprintf(p, ep - p, "\033*f%ldP", ss->sio.scan_width / 4);
       p += snprintf(p, ep - p, "\033*f%ldQ", ss->sio.scan_height / 4);
       p += snprintf(p, ep - p, "\033*f%ldX", ss->sio.scan_x_origin / 4);
       p += snprintf(p, ep - p, "\033*f%ldY", ss->sio.scan_y_origin / 4);
       p += snprintf(p, ep - p, "\033*a%dR", ss->sio.scan_x_resolution);
       p += snprintf(p, ep - p, "\033*a%dS", ss->sio.scan_y_resolution);

       switch (ss->sio.scan_image_mode) {
       case SIM_BINARY_MONOCHROME:
               ss->sio.scan_bits_per_pixel = 1;
               /* use "line art" mode */
               strlcpy(p, "\033*a0T", ep - p);
               p += strlen(p);
               /* make image data be "min-is-white ala PBM */
               strlcpy(p, "\033*a0I", ep - p);
               p += strlen(p);
               break;
       case SIM_DITHERED_MONOCHROME:
               ss->sio.scan_bits_per_pixel = 1;
               /* use dithered mode */
               strlcpy(p, "\033*a3T", ep - p);
               p += strlen(p);
               /* make image data be "min-is-white ala PBM */
               strlcpy(p, "\033*a0I", ep - p);
               p += strlen(p);
               break;
       case SIM_GRAYSCALE:
               ss->sio.scan_bits_per_pixel = 8;
               /* use grayscale mode */
               strlcpy(p, "\033*a4T", ep - p);
               p += strlen(p);
               /* make image data be "min-is-black ala PGM */
               strlcpy(p, "\033*a1I", ep - p);
               p += strlen(p);
               break;
       case SIM_COLOR:
               ss->sio.scan_bits_per_pixel = 24;
               /* use RGB color mode */
               strlcpy(p, "\033*a5T", ep - p);
               p += strlen(p);
               /* make image data be "min-is-black ala PPM */
               strlcpy(p, "\033*a1I", ep - p);
               p += strlen(p);
               /* use pass-through matrix (disable NTSC) */
               strlcpy(p, "\033*u2T", ep - p);
               p += strlen(p);
               break;
       }

       p += snprintf(p, ep - p, "\033*a%dG", ss->sio.scan_bits_per_pixel);
       p += snprintf(p, ep - p, "\033*a%dL",
           (int)(ss->sio.scan_brightness) - 128);
       p += snprintf(p, ep - p, "\033*a%dK",
           (int)(ss->sio.scan_contrast) - 128);

       return scanjet_ctl_write(ss, escape_codes, p - escape_codes);
}

static int
scanjet_compute_sizes(struct ss_softc *ss)
{
       int error;
       static const char *wfail = "%s: interrogate write failed\n";
       static const char *rfail = "%s: interrogate read failed\n";
       static const char *dfail = "%s: bad data returned\n";
       char escape_codes[20];
       char response[20];
       char *p;

       /*
        * Deal with the fact that the HP ScanJet IIc uses 1/300" not 1/1200"
        * as its base unit of measurement.  PINT uses 1/1200" (yes I know
        * ScanJet II's use decipoints as well but 1200 % 720 != 0)
        */
       ss->sio.scan_width = (ss->sio.scan_width + 3) & 0xfffffffc;
       ss->sio.scan_height = (ss->sio.scan_height + 3) & 0xfffffffc;

       switch (ss->sio.scan_image_mode) {
       case SIM_BINARY_MONOCHROME:
       case SIM_DITHERED_MONOCHROME:
               /* bytes wide */
               strlcpy(escape_codes, "\033*s1025E", sizeof(escape_codes));
               break;
       case SIM_GRAYSCALE:
       case SIM_COLOR:
               /* pixels wide */
               strlcpy(escape_codes, "\033*s1024E", sizeof(escape_codes));
               break;
       }
       error = scanjet_ctl_write(ss, escape_codes, strlen(escape_codes));
       if (error) {
               uprintf(wfail, device_xname(ss->sc_dev));
               return error;
       }
       error = scanjet_ctl_read(ss, response, 20);
       if (error) {
               uprintf(rfail, device_xname(ss->sc_dev));
               return error;
       }
       p = strchr(response, 'd');
       if (p == 0) {
               uprintf(dfail, device_xname(ss->sc_dev));
               return EIO;
       }
       ss->sio.scan_pixels_per_line = strtoul(p + 1, NULL, 10);
       if (ss->sio.scan_image_mode < SIM_GRAYSCALE)
               ss->sio.scan_pixels_per_line *= 8;

       /* pixels high */
       strlcpy(escape_codes, "\033*s1026E", sizeof(escape_codes));
       error = scanjet_ctl_write(ss, escape_codes, strlen(escape_codes));
       if (error) {
               uprintf(wfail, device_xname(ss->sc_dev));
               return error;
       }
       error = scanjet_ctl_read(ss, response, 20);
       if (error) {
               uprintf(rfail, device_xname(ss->sc_dev));
               return error;
       }
       p = strchr(response, 'd');
       if (p == 0) {
               uprintf(dfail, device_xname(ss->sc_dev));
               return EIO;
       }
       ss->sio.scan_lines = strtoul(p + 1, NULL, 10);

       ss->sio.scan_window_size = ss->sio.scan_lines *
           ((ss->sio.scan_pixels_per_line * ss->sio.scan_bits_per_pixel) / 8);

       return 0;
}