/*	$OpenBSD: ss.c,v 1.43 2001/06/22 14:35:43 deraadt Exp $	*/
/*	$NetBSD: ss.c,v 1.10 1996/05/05 19:52:55 christos Exp $	*/

/*
 * Copyright (c) 1995, 1997 Kenneth Stailey.  All rights reserved.
 *   modified for configurable scanner support by Joachim Koenig
 *
 * 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.
 */

#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/proc.h>
#include <sys/user.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/scanio.h>

#include <scsi/scsi_all.h>
#include <scsi/scsi_scanner.h>
#include <scsi/scsiconf.h>
#include <scsi/ssvar.h>

#include <scsi/ss_mustek.h>

#define SSMODE(z)	( minor(z)       & 0x03)
#define SSUNIT(z)	((minor(z) >> 4)       )

/*
 * If the mode is 3 (e.g. minor = 3,7,11,15)
 * then the device has been openned to set defaults
 * This mode does NOT ALLOW I/O, only ioctls
 */
#define MODE_REWIND	0
#define MODE_NONREWIND	1
#define MODE_CONTROL	3

struct quirkdata {
	char *name;
	u_int quirks;
#define SS_Q_WINDOW_DESC_LEN	0x0001 /* needs special WDL */
#define SS_Q_BRIGHTNESS		0x0002 /* needs special value for brightness */
#define SS_Q_REV_BRIGHTNESS	0x0004 /* reverse brightness control in s/w */
#define SS_Q_THRESHOLD		0x0008 /* needs special value for threshold */
#define SS_Q_MONO_THRESHOLD	0x0010 /* same as SS_Q_THRESHOLD but only
					* for monochrome image data */
#define SS_Q_CONTRAST		0x0020 /* needs special value for contrast */
#define SS_Q_REV_CONTRAST	0x0040 /* reverse contrast control in s/w */
#define SS_Q_HALFTONE		0x0080 /* uses non-zero halftone */
#define SS_Q_SET_RIF		0x0100 /* set RIF bit */
#define SS_Q_PADDING_TYPE	0x0200 /* does not truncate to byte boundary */
#define SS_Q_BIT_ORDERING	0x0400 /* needs non-zero bit ordering */
	long window_descriptor_length;
	u_int8_t brightness;
	u_int8_t threshold;
	u_int8_t contrast;
	u_int8_t halftone_pattern[2];
	int pad_type;
	long bit_ordering;
	u_int8_t scanner_type;
	/*
	 * To enable additional scanner options, point vendor_unique_sw
	 * at a function that adds more stuff to the SET_WINDOW parameters.
	 */
	int	(*vendor_unique_sw)__P((struct ss_softc *, struct scan_io *,
					struct scsi_set_window *, void *));
	/* 
	 * If the scanner requires use of GET_BUFFER_STATUS before READ
	 * it can be called from ss_minphys().
	 */
	void	(*special_minphys)__P((struct ss_softc *, struct buf *));

	/*
	 * 
	 */
	int	(*compute_sizes)__P((void));
};

struct ss_quirk_inquiry_pattern {
	struct scsi_inquiry_pattern pattern;
	struct quirkdata quirkdata;
};

struct  quirkdata ss_gen_quirks = {
	"generic", 0, 0, 0, 0, 0,
	{0, 0}, 0, 0, GENERIC_SCSI2,
	NULL, NULL, NULL
};

void    ssstrategy __P((struct buf *));
void    ssstart __P((void *));
void	ssminphys __P((struct buf *));

void	ss_identify_scanner __P((struct ss_softc *, struct scsi_inquiry_data*));
int	ss_set_window __P((struct ss_softc *, struct scan_io *));

int	ricoh_is410_sw __P((struct ss_softc *, struct scan_io *,
			    struct scsi_set_window *, void *));
int	umax_uc630_sw __P((struct ss_softc *, struct scan_io *,
			   struct scsi_set_window *, void *));
#ifdef NOTYET	/* for ADF support  */
int	fujitsu_m3096g_sw __P((struct ss_softc *, struct scan_io *,
			       struct scsi_set_window *, void *));
#endif

void	get_buffer_status __P((struct ss_softc *, struct buf *));

/*
 * WDL:
 *
 *  Ricoh IS-50 & IS-410 insist on 320 (even it transfer len is less.)
 *  Ricoh FS-1 accepts 256 (I haven't tested other values.)
 *  UMAX UC-630 accepts 46 (I haven't tested other values.)
 *  Fujitsu M3096G wants 40 <= x <= 248 (tested OK at 40 & 64.)
 */

struct ss_quirk_inquiry_pattern ss_quirk_patterns[] = {
	{{T_SCANNER, T_FIXED,
	 "ULTIMA  ", "AT3     1.60    ", "    "}, {
		 "Ultima AT3",
		 SS_Q_HALFTONE |
		 SS_Q_PADDING_TYPE,
		 0, 0, 0, 0, { 3, 0 }, 0, 0,
		 ULTIMA_AT3,
		 NULL, NULL, NULL
	 }},
	{{T_SCANNER, T_FIXED,
	 "ULTIMA  ", "A6000C PLUS     ", "    "}, {
		 "Ultima A6000C",
		 SS_Q_HALFTONE |
		 SS_Q_PADDING_TYPE,
		 0, 0, 0, 0, { 3, 0 }, 0, 0,
		 ULTIMA_AC6000C,
		 NULL, NULL, NULL
	 }},
	{{T_SCANNER, T_FIXED,
	 "RICOH   ", "IS50            ", "    "}, {
		 "Ricoh IS-50",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_REV_BRIGHTNESS |
		 SS_Q_THRESHOLD |
		 SS_Q_REV_CONTRAST |
		 SS_Q_HALFTONE |
		 SS_Q_BIT_ORDERING,
		 320, 0, 0, 0, { 2, 0x0a }, 0, 7,
		 RICOH_IS50,
		 ricoh_is410_sw, get_buffer_status, NULL
	 }},
	{{T_SCANNER, T_FIXED,
	 "RICOH   ", "IS410           ", "    "}, {
		 "Ricoh IS-410",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_THRESHOLD |
		 SS_Q_HALFTONE |
		 SS_Q_BIT_ORDERING,
		 320, 0, 0, 0, { 2, 0x0a }, 0, 7,
		 RICOH_IS410,
		 ricoh_is410_sw, get_buffer_status, NULL
	 }},
	{{T_SCANNER, T_FIXED,	       /* Ricoh IS-410 OEMed by IBM */
	 "IBM     ", "2456-001        ", "    "}, {
		 "IBM 2456",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_THRESHOLD |
		 SS_Q_HALFTONE |
		 SS_Q_BIT_ORDERING,
		 320, 0, 0, 0, { 2, 0x0a }, 0, 7,
		 RICOH_IS410,
		 ricoh_is410_sw, get_buffer_status, NULL
	 }},
	{{T_SCANNER, T_FIXED,
	 "UMAX    ", "UC630           ", "    "}, {
		 "UMAX UC-630",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_HALFTONE,
		 0x2e, 0, 0, 0, { 0, 1 }, 0, 0,
		 UMAX_UC630,
		 umax_uc630_sw, NULL, NULL
	 }},
	{{T_SCANNER, T_FIXED,
	 "UMAX    ", "UG630           ", "    "}, {
		 "UMAX UG-630",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_HALFTONE,
		 0x2e, 0, 0, 0, { 0, 1 }, 0, 0,
		 UMAX_UG630,
		 umax_uc630_sw, NULL, NULL
	 }},
#ifdef NOTYET			/* ADF version */
	{{T_SCANNER, T_FIXED,
	 "FUJITSU ", "M3096Gm         ", "    "}, {
		 "Fujitsu M3096G",
		 SS_Q_WINDOW_DESC_LEN |
		 SS_Q_BRIGHTNESS |
		 SS_Q_MONO_THRESHOLD |
		 SS_Q_HALFTONE |
		 SS_Q_SET_RIF |
		 SS_Q_PADDING_TYPE,
		 64, 0, 0, 0, { 0, 1 }, 0, 0,
		 FUJITSU_M3096G,
		 fujistsu_m3096g_sw, NULL, NULL
	 }},
#else				/* flatbed-only version */
	{{T_SCANNER, T_FIXED,
	 "FUJITSU ", "M3096Gm         ", "    "}, {
		 "Fujitsu M3096G",
		 SS_Q_BRIGHTNESS |
		 SS_Q_MONO_THRESHOLD |
		 SS_Q_CONTRAST |
		 SS_Q_HALFTONE |
		 SS_Q_PADDING_TYPE,
		 0, 0, 0, 0, { 0, 1 }, 0, 0,
		 FUJITSU_M3096G,
		 NULL, NULL, NULL
	 }},
#endif
};
       

int ssmatch __P((struct device *, void *, void *));
void ssattach __P((struct device *, struct device *, void *));

struct cfattach ss_ca = {
	sizeof(struct ss_softc), ssmatch, ssattach
};

struct cfdriver ss_cd = {
	NULL, "ss", DV_DULL
};

struct scsi_device ss_switch = {
	NULL,
	ssstart,
	NULL,
	NULL,
};

struct scsi_inquiry_pattern ss_patterns[] = {
	{T_SCANNER, T_FIXED,
	 "",         "",                 ""},
	{T_SCANNER, T_REMOV,
	 "",         "",                 ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C1750A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C1790A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C2500A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C2570A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C2520A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C1130A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C5110A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C6290A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C5190A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C7190A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C6270A          ", ""},
	{T_PROCESSOR, T_FIXED,
	 "HP      ", "C7670A          ", ""},
};

int
ssmatch(parent, match, aux)
	struct device *parent;
	void *match, *aux;
{
	struct scsibus_attach_args *sa = aux;
	int priority;

	(void)scsi_inqmatch(sa->sa_inqbuf,
	    (caddr_t)ss_patterns, sizeof(ss_patterns)/sizeof(ss_patterns[0]),
	    sizeof(ss_patterns[0]), &priority);
	return (priority);
}

/*
 * The routine called by the low level scsi routine when it discovers
 * A device suitable for this driver
 * If it is a know special, call special attach routine to install
 * special handlers into the ss_softc structure
 */
void
ssattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct ss_softc *ss = (void *)self;
	struct scsibus_attach_args *sa = aux;
	struct scsi_link *sc_link = sa->sa_sc_link;

	SC_DEBUG(sc_link, SDEV_DB2, ("ssattach: "));

	/*
	 * Store information needed to contact our base driver
	 */
	ss->sc_link = sc_link;
	sc_link->device = &ss_switch;
	sc_link->device_softc = ss;
	sc_link->openings = 1;

	if (!bcmp(sa->sa_inqbuf->vendor, "MUSTEK", 6))
		mustek_attach(ss, sa);
	else if (!bcmp(sa->sa_inqbuf->vendor, "HP      ", 8))
		scanjet_attach(ss, sa);
	else
		ss_identify_scanner(ss, sa->sa_inqbuf);

	/*
	 * 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;

	/* XXX fill in the rest of the scan_io struct by calling the
	   compute_sizes routine */

	/*
	 * Set up the buf queue for this device
	 */
	ss->buf_queue.b_active = 0;
	ss->buf_queue.b_actf = 0;
	ss->buf_queue.b_actb = &ss->buf_queue.b_actf;
}

void
ss_identify_scanner(ss, inqbuf)
	struct ss_softc *ss;
	struct scsi_inquiry_data *inqbuf;
{
	struct ss_quirk_inquiry_pattern *finger;
	int priority;
	/*
	 * look for non-standard scanners with help of the quirk table
	 * and install functions for special handling
	 */
	finger = (struct ss_quirk_inquiry_pattern *)scsi_inqmatch(inqbuf,
	    (caddr_t)ss_quirk_patterns,
	    sizeof(ss_quirk_patterns)/sizeof(ss_quirk_patterns[0]),
	    sizeof(ss_quirk_patterns[0]), &priority);
	if (priority != 0) {
		ss->quirkdata = &finger->quirkdata;
		if (ss->quirkdata->special_minphys != NULL) {
			ss->special.minphys = ss->quirkdata->special_minphys;
		}
		ss->sio.scan_scanner_type = ss->quirkdata->scanner_type;
		printf("\n%s: %s\n", ss->sc_dev.dv_xname, ss->quirkdata->name);
	} else {
		printf("\n%s: generic scanner\n", ss->sc_dev.dv_xname);
		bzero(&ss_gen_quirks, sizeof(ss_gen_quirks));
		ss->quirkdata = &ss_gen_quirks;
		ss->sio.scan_scanner_type = GENERIC_SCSI2;
	}
}

/*
 * open the device.
 */
int
ssopen(dev, flag, mode, p)
	dev_t dev;
	int flag;
	int mode;
	struct proc *p;
{
	int unit;
	u_int ssmode;
	int error = 0;
	struct ss_softc *ss;
	struct scsi_link *sc_link;

	unit = SSUNIT(dev);
	if (unit >= ss_cd.cd_ndevs)
		return (ENXIO);
	ss = ss_cd.cd_devs[unit];
	if (!ss)
		return (ENXIO);

	ssmode = SSMODE(dev);
	sc_link = ss->sc_link;

	SC_DEBUG(sc_link, SDEV_DB1, ("open: dev=0x%x (unit %d (of %d))\n", dev,
	    unit, ss_cd.cd_ndevs));

	if (sc_link->flags & SDEV_OPEN) {
		printf("%s: already open\n", ss->sc_dev.dv_xname);
		return (EBUSY);
	}

	/*
	 * Catch any unit attention errors.
	 *
	 * SCSI_IGNORE_MEDIA_CHANGE: when you have an ADF, some scanners
	 * consider paper to be a changeable media
	 *
	 */
	error = scsi_test_unit_ready(sc_link,
	    SCSI_IGNORE_MEDIA_CHANGE | SCSI_IGNORE_ILLEGAL_REQUEST |
	    (ssmode == MODE_CONTROL ? SCSI_IGNORE_NOT_READY : 0));
	if (error)
		goto bad;

	sc_link->flags |= SDEV_OPEN;	/* unit attn are now errors */

	/*
	 * If the mode is 3 (e.g. minor = 3,7,11,15)
	 * then the device has been opened to set defaults
	 * This mode does NOT ALLOW I/O, only ioctls
	 */
	if (ssmode == MODE_CONTROL)
		return (0);

	SC_DEBUG(sc_link, SDEV_DB2, ("open complete\n"));
	return (0);

bad:
	sc_link->flags &= ~SDEV_OPEN;
	return (error);
}

/*
 * close the device.. only called if we are the LAST
 * occurence of an open device
 */
int
ssclose(dev, flag, mode, p)
	dev_t dev;
	int flag;
	int mode;
	struct proc *p;
{
	struct ss_softc *ss = ss_cd.cd_devs[SSUNIT(dev)];
	int error;

	SC_DEBUG(ss->sc_link, SDEV_DB1, ("closing\n"));

	if (SSMODE(dev) == MODE_REWIND) {
		if (ss->special.rewind_scanner) {
			/* call special handler to rewind/abort scan */
			error = (ss->special.rewind_scanner)(ss);
			if (error)
				return (error);
		} else {
			/* XXX add code to restart a SCSI2 scanner, if any */
		}
		ss->sio.scan_window_size = 0;
		ss->flags &= ~SSF_TRIGGERED;
	}
	ss->sc_link->flags &= ~SDEV_OPEN;

	return (0);
}

/*
 * trim the size of the transfer if needed,
 * called by physio
 * basically the smaller of our min and the scsi driver's
 * minphys
 */
void
ssminphys(bp)
	struct buf *bp;
{
	register struct ss_softc *ss = ss_cd.cd_devs[SSUNIT(bp->b_dev)];

	(ss->sc_link->adapter->scsi_minphys)(bp);

	/*
	 * trim the transfer further for special devices this is
	 * because some scanners only read multiples of a line at a
	 * time, also some cannot disconnect, so the read must be
	 * short enough to happen quickly
	 */
	if (ss->special.minphys)
		(ss->special.minphys)(ss, bp);
}

/*
 * Do a read on a device for a user process.
 * Prime scanner at start of read, check uio values, call ssstrategy
 * via physio for the actual transfer.
 */
int
ssread(dev, uio, flag)
	dev_t dev;
	struct uio *uio;
	int flag;
{
	struct ss_softc *ss = ss_cd.cd_devs[SSUNIT(dev)];
	int error;

	/* if the scanner has not yet been started, do it now */
	if (!(ss->flags & SSF_TRIGGERED)) {
		if (ss->special.trigger_scanner) {
			error = (ss->special.trigger_scanner)(ss);
			if (error)
				return (error);
		} else {
			struct scsi_start_stop trigger_cmd;
			bzero(&trigger_cmd, sizeof(trigger_cmd));
			trigger_cmd.opcode = START_STOP;
			trigger_cmd.how = SSS_START;
			scsi_scsi_cmd(ss->sc_link,
				(struct scsi_generic *)&trigger_cmd,
				sizeof(trigger_cmd), 0, 0, 4, 5000, NULL, 0);
		}
		ss->flags |= SSF_TRIGGERED;
	}

	return (physio(ssstrategy, NULL, dev, B_READ, ssminphys, uio));
}

/*
 * Actually translate the requested transfer into one the physical
 * driver can understand The transfer is described by a buf and will
 * include only one physical transfer.
 */
void
ssstrategy(bp)
	struct buf *bp;
{
	struct ss_softc *ss = ss_cd.cd_devs[SSUNIT(bp->b_dev)];
	struct buf *dp;
	int s;

	SC_DEBUG(ss->sc_link, SDEV_DB1,
	    ("ssstrategy %ld bytes @ blk %d\n", bp->b_bcount, bp->b_blkno));

	if (bp->b_bcount > ss->sio.scan_window_size)
		bp->b_bcount = ss->sio.scan_window_size;

	/*
	 * If it's a null transfer, return immediatly
	 */
	if (bp->b_bcount == 0)
		goto done;

	s = splbio();

	/*
	 * Place it in the queue of activities for this scanner
	 * at the end (a bit silly because we only have on user..)
	 * (but it could fork() or dup())
	 */
	dp = &ss->buf_queue;
	bp->b_actf = NULL;
	bp->b_actb = dp->b_actb;
	*dp->b_actb = bp;
	dp->b_actb = &bp->b_actf;

	/*
	 * Tell the device to get going on the transfer if it's
	 * not doing anything, otherwise just wait for completion
	 * (All a bit silly if we're only allowing 1 open but..)
	 */
	ssstart(ss);

	splx(s);
	return;
	bp->b_flags |= B_ERROR;
done:
	/*
	 * Correctly set the buf to indicate a completed xfer
	 */
	bp->b_resid = bp->b_bcount;
	biodone(bp);
}

/*
 * ssstart looks to see if there is a buf waiting for the device
 * and that the device is not already busy. If both are true,
 * It dequeues the buf and creates a scsi command to perform the
 * transfer required. The transfer request will call scsi_done
 * on completion, which will in turn call this routine again
 * so that the next queued transfer is performed.
 * The bufs are queued by the strategy routine (ssstrategy)
 *
 * This routine is also called after other non-queued requests
 * have been made of the scsi driver, to ensure that the queue
 * continues to be drained.
 * ssstart() is called at splbio
 */
void
ssstart(v)
	void *v;
{
	struct ss_softc *ss = v;
	struct scsi_link *sc_link = ss->sc_link;
	register struct buf *bp, *dp;
	struct scsi_r_scanner read_cmd;
	int flags;

	SC_DEBUG(sc_link, SDEV_DB2, ("ssstart "));
	/*
	 * See if there is a buf to do and we are not already
	 * doing one
	 */
	while (sc_link->openings > 0) {
		/* if a special awaits, let it proceed first */
		if (sc_link->flags & SDEV_WAITING) {
			sc_link->flags &= ~SDEV_WAITING;
			wakeup((caddr_t)sc_link);
			return;
		}

		/*
		 * See if there is a buf with work for us to do..
		 */
		dp = &ss->buf_queue;
		if ((bp = dp->b_actf) == NULL)
			return;
		if ((dp = bp->b_actf) != NULL)
			dp->b_actb = bp->b_actb;
		else
			ss->buf_queue.b_actb = bp->b_actb;
		*bp->b_actb = dp;

		if (ss->special.read) {
			(ss->special.read)(ss, bp);
		} else {
			/* generic scsi2 scanner read */
			bzero(&read_cmd, sizeof(read_cmd));
			read_cmd.opcode = READ_BIG;
			_lto3b(bp->b_bcount, read_cmd.len);
			flags = SCSI_DATA_IN;
			/*
			 * go ask the adapter to do all this for us
			 */
			if (scsi_scsi_cmd(sc_link, (struct scsi_generic *)
			    &read_cmd, sizeof(read_cmd), (u_char *) bp->b_data,
			    bp->b_bcount, 0, 100000, bp, flags | SCSI_NOSLEEP))
				printf("%s: not queued\n", ss->sc_dev.dv_xname);
		}
	}
}

/*
 * Perform special action on behalf of the user;
 * knows about the internals of this device
 */
int
ssioctl(dev, cmd, addr, flag, p)
	dev_t dev;
	u_long cmd;
	caddr_t addr;
	int flag;
	struct proc *p;
{
	struct ss_softc *ss = ss_cd.cd_devs[SSUNIT(dev)];
	int error = 0;
	struct scan_io *sio;

	switch (cmd) {
	case SCIOCGET:
		/* call special handler, if any */
		if (ss->special.get_params) {
			error = (ss->special.get_params)(ss);
			if (error)
				return (error);
		}
		bcopy(&ss->sio, addr, sizeof(struct scan_io));
		break;
	case SCIOCSET:
		sio = (struct scan_io *)addr;

		/* call special handler, if any */
		if (ss->special.set_params) {
			error = (ss->special.set_params)(ss, sio);
			if (error)
				return (error);
		} else {
			/* XXX add routine to validate paramters */
			ss_set_window(ss, sio);
		}
		break;
	case SCIOCRESTART:
		/* call special handler, if any */
		if (ss->special.rewind_scanner ) {
			error = (ss->special.rewind_scanner)(ss);
			if (error)
				return (error);
		} else
			/* XXX add code for SCSI2 scanner, if any */
			return (EOPNOTSUPP);
		ss->flags &= ~SSF_TRIGGERED;
		break;
	case SCIOC_USE_ADF:
		/* XXX add Automatic Document Feeder Support */
		return (EOPNOTSUPP);
	default:
		if (SSMODE(dev) != MODE_CONTROL)
			return (ENOTTY);
		return (scsi_do_safeioctl(ss->sc_link, dev, cmd, addr,
		    flag, p));
	}
	return (error);
}

int
ss_set_window(ss, sio)
	struct ss_softc *ss;
	struct scan_io *sio;
{
	struct scsi_set_window	window_cmd;
	struct {
		struct scsi_window_data	window_data;
		/* vendor_unique must provide enough space for worst case
		 * (currently Ricoh IS-410.)  40 + 280 = 320 which is the size
		 * of its window descriptor length
		 */
		u_int8_t vendor_unique[280];
	} wd;
#define window_data   wd.window_data
#define vendor_unique wd.vendor_unique
	struct scsi_link	*sc_link = ss->sc_link;

	/*
	 * The CDB for SET WINDOW goes in here.
	 * The two structures that follow are sent via data out.
	 */
	bzero(&window_cmd, sizeof(window_cmd));
	window_cmd.opcode = SET_WINDOW;
	_lto3l(sizeof(window_data), window_cmd.len);

	bzero(&window_data, sizeof(window_data));
	if (ss->quirkdata->quirks & SS_Q_WINDOW_DESC_LEN)
		_lto2l(ss->quirkdata->window_descriptor_length,
		    window_data.window_desc_len);
	else
		_lto2l(40L, window_data.window_desc_len);

	/* start of SET_WINDOW parameter block */

	/* leave window id at zero */
	/* leave auto bit at zero */
	_lto2l(sio->scan_x_resolution, window_data.x_res);
	_lto2l(sio->scan_y_resolution, window_data.y_res);
	_lto4l(sio->scan_x_origin, window_data.x_org);
	_lto4l(sio->scan_y_origin, window_data.y_org);
	_lto4l(sio->scan_width,  window_data.width);
	_lto4l(sio->scan_height, window_data.length);

	if (ss->quirkdata->quirks & SS_Q_REV_BRIGHTNESS)
		window_data.brightness = 256 - sio->scan_brightness;
	else if (ss->quirkdata->quirks & SS_Q_BRIGHTNESS)
		window_data.brightness = ss->quirkdata->brightness;
	else
		window_data.brightness = sio->scan_brightness;

	/*
	 * threshold: Default is to follow brightness.
	 * If SS_Q_MONO_THRESHOLD is set then the quirkdata contains a special
	 * value to be used instead of default when image data is monochrome.
	 * Otherwise if SS_Q_THRESHOLD is set then the quirkdata contains
	 * the threshold to always use.
	 * Both SS_Q_MONO_THRESHOLD and SS_Q_THRESHOLD should not be set at
	 * the same time.
	 */
	if (ss->quirkdata->quirks & SS_Q_MONO_THRESHOLD) {
		if (sio->scan_image_mode == SIM_BINARY_MONOCHROME ||
		    sio->scan_image_mode == SIM_DITHERED_MONOCHROME)
			window_data.threshold = ss->quirkdata->threshold;
		else
			window_data.threshold = sio->scan_brightness;
	} else if (ss->quirkdata->quirks & SS_Q_THRESHOLD)
		window_data.threshold = ss->quirkdata->threshold;
	else
		window_data.threshold = sio->scan_brightness;

	if (ss->quirkdata->quirks & SS_Q_REV_CONTRAST)
		window_data.contrast = 256 - sio->scan_contrast;
	else if (ss->quirkdata->quirks & SS_Q_CONTRAST)
		window_data.contrast = ss->quirkdata->contrast;
	else
		window_data.contrast = sio->scan_contrast;

	switch (sio->scan_image_mode) {
	case SIM_RED:
	case SIM_GREEN:
	case SIM_BLUE:
		window_data.image_comp = SIM_GRAYSCALE;
		break;
	default:
		window_data.image_comp = sio->scan_image_mode;
	}

	window_data.bits_per_pixel = sio->scan_bits_per_pixel;

	if (ss->quirkdata->quirks & SS_Q_HALFTONE) {
		window_data.halftone_pattern[0] =
			ss->quirkdata->halftone_pattern[0];
		window_data.halftone_pattern[1] = 
			ss->quirkdata->halftone_pattern[1];
	} /* else leave halftone set to zero. */

	if (ss->quirkdata->quirks & SS_Q_SET_RIF)
		window_data.rif = 1;

	if (ss->quirkdata->quirks & SS_Q_PADDING_TYPE)
		window_data.pad_type = ss->quirkdata->pad_type;
	else
		window_data.pad_type = 3; /* 3 = truncate to byte boundary */

	if (ss->quirkdata->quirks & SS_Q_BIT_ORDERING)
		_lto2l(ss->quirkdata->bit_ordering, window_data.bit_ordering);
	/* else leave bit_ordering set to zero. */

	/* leave compression type & argument set to zero. */

#undef window_data

	if (ss->quirkdata->vendor_unique_sw != NULL)
		return ((*ss->quirkdata->vendor_unique_sw)(ss, sio,
		    &window_cmd, (void*)&wd));
	else
		/* send the command to the scanner */
		return (scsi_scsi_cmd(sc_link,
		    (struct scsi_generic *)&window_cmd,
		    sizeof(window_cmd), (u_char *) &wd.window_data,
		    (ss->quirkdata->quirks & SS_Q_WINDOW_DESC_LEN) ?
		    ss->quirkdata->window_descriptor_length : 40,
		    4, 5000, NULL, SCSI_DATA_OUT));
}

int
ricoh_is410_sw(ss, sio, wcmd, vwd)
	struct ss_softc *ss;
	struct scan_io *sio;
	struct scsi_set_window *wcmd;
	void *vwd;
{
	struct ricoh_is410_window_data {
		struct scsi_window_data	window_data;
		u_int8_t res1;
		u_int8_t res2;
		u_int8_t mrif:1; /* reverse image format (grayscale negative) */
		u_int8_t filtering:3;
		u_int8_t gamma_id:4;
	} *rwd = (struct ricoh_is410_window_data*)vwd;
	struct scsi_link *sc_link = ss->sc_link;

	rwd->mrif = 1;		/* force grayscale to match PGM */

	/* send the command to the scanner */
	return (scsi_scsi_cmd(sc_link, (struct scsi_generic *)wcmd,
	    sizeof(struct scsi_set_window), (u_char *)rwd,
	    sizeof(struct ricoh_is410_window_data), 4, 5000, NULL,
	    SCSI_DATA_OUT));
}

int
umax_uc630_sw(ss, sio, wcmd, vwd)
	struct ss_softc *ss;
	struct scan_io *sio;
	struct scsi_set_window *wcmd;
	void *vwd;
{
	struct umax_uc630_window_data {
		struct scsi_window_data	window_data;
		u_int8_t speed;
		u_int8_t select_color;
		u_int8_t highlight;
		u_int8_t shadow;
		u_int8_t paper_length[2];
	} *uwd = (struct umax_uc630_window_data*)vwd;
	struct scsi_link *sc_link = ss->sc_link;

	uwd->speed = 1;		/* speed: fastest speed that doesn't smear */
	switch (sio->scan_image_mode) {	/* UMAX has three-pass color. */
	case SIM_RED:			/* This selects which filter to use. */
		uwd->select_color = 0x80;
		break;
	case SIM_GREEN:
		uwd->select_color = 0x40;
		break;
	case SIM_BLUE:
		uwd->select_color = 0x20;
		break;
	}
	uwd->highlight = 50;		/* 50 = highest; 0 = lowest */
	/* leave shadow set to zero. */
	/* XXX paper length is for ADF */

	/* send the command to the scanner */
	return (scsi_scsi_cmd(sc_link, (struct scsi_generic *)wcmd,
	    sizeof(struct scsi_set_window), (u_char *)uwd,
	    sizeof(struct umax_uc630_window_data), 4, 5000, NULL,
	    SCSI_DATA_OUT));
}

#ifdef NOTYET /* for ADF support */
int
fujitsu_m3096g_sw(ss, sio, wcmd, vwd)
	struct ss_softc *ss;
	struct scan_io *sio;
	struct scsi_set_window *wcmd;
	void *vwd;
{
	struct fujitsu_m3096g_window_data {
		struct scsi_window_data	window_data;
		u_int8_t id;
		u_int8_t res1;
		u_int8_t outline;
		u_int8_t emphasis;
		u_int8_t mixed;
		u_int8_t mirroring;
		u_int8_t res2[5];
		u_int8_t subwindow_list[2];
		u_int8_t paper_size_std:2;
		u_int8_t res3:1;
		u_int8_t paper_orientaton:1;
		u_int8_t paper_size_type:4;
/* defines for Paper Size Type: */
#define FUJITSU_PST_A3			0x03
#define FUJITSU_PST_A4			0x04
#define FUJITSU_PST_A5			0x05
#define FUJITSU_PST_DOUBLE_LETTER	0x06
#define FUJITSU_PST_LETTER		0x07
#define FUJITSU_PST_B4			0x0C
#define FUJITSU_PST_B5			0x0D
#define FUJITSU_PST_LEGAL		0x0F
		u_int8_t paper_width_x[4];
		u_int8_t paper_width_y[4];
		u_int8_t res4[2];
	} *fwd = (struct fujitsu_m3096g_window_data*)vwd;
	struct scsi_link *sc_link = ss->sc_link;

	/* send the command to the scanner */
	return (scsi_scsi_cmd(sc_link, (struct scsi_generic *)wcmd,
	    sizeof(struct scsi_set_window), (u_char *)fwd,
	    sizeof(struct fujitsu_m3096g_window_data), 4, 5000, NULL,
	    SCSI_DATA_OUT));
}
#endif

void
get_buffer_status(ss, bp)
	struct ss_softc *ss;
	struct buf *bp;
{
	struct scsi_get_buffer_status gbs_cmd;
	struct scsi_link *sc_link = ss->sc_link;
	struct {
		u_int8_t stat_len[3];
		u_int8_t res1;
		u_int8_t window_id;
		u_int8_t res2;
		u_int8_t tgt_accept_buf_len[3];
		u_int8_t tgt_send_buf_len[3];
	} buf_sz_retn;
	int flags;

	bzero(&gbs_cmd, sizeof(gbs_cmd));
	gbs_cmd.opcode = GET_BUFFER_STATUS;
	_lto2b(12, gbs_cmd.len);
	flags = SCSI_DATA_IN;

	if (scsi_scsi_cmd(sc_link, (struct scsi_generic *) &gbs_cmd,
	    sizeof(gbs_cmd), (u_char *) &buf_sz_retn, sizeof(buf_sz_retn),
	    0, 100000, bp, flags | SCSI_NOSLEEP)) {
		printf("%s: not queued\n", ss->sc_dev.dv_xname);
	}
	bp->b_bcount = MIN(_3btol(buf_sz_retn.tgt_send_buf_len), bp->b_bcount);
}

#ifdef NOTYET
int
umax_compute_sizes(ss)
	struct ss_softc *ss;
{
	ss->sio.scan_lines = ;
	ss->sio.scan_window_size = ;
}

int
calc_umax_row_len(dpi, ww)
	int dpi;
	int ww;
{
	int st[301];
	int i;
	int rowB = 0;

	for (i = 1; i <= 300; i++)
		st[i] = 1;

	for (i = 1; i <= 300 - dpi; i++)
		st[i * 300 / (300 - dpi)] = 0;

	for (i = 1; i <= (ww % 1200) / 4; i++) {
		if (st[i])
			rowB++;
	}

	return ((ww / 1200) * dpi + rowB);
}
#endif