1996-02-02 19:27:57 +00:00
|
|
|
/*
|
|
|
|
* FreeBSD Connectix QuickCam parallel-port camera video capture driver.
|
|
|
|
* Copyright (c) 1996, Paul Traina.
|
|
|
|
*
|
|
|
|
* This driver is based in part on the Linux QuickCam driver which is
|
|
|
|
* Copyright (c) 1996, Thomas Davis.
|
|
|
|
*
|
|
|
|
* QuickCam(TM) is a registered trademark of Connectix Inc.
|
|
|
|
* Use this driver at your own risk, it is not warranted by
|
|
|
|
* Connectix or the authors.
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
* in this position and unchanged.
|
|
|
|
* 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. The name of the author may not be used to endorse or promote products
|
|
|
|
* derived from this software withough 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 "qcam.h"
|
|
|
|
#if NQCAM > 0
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/kernel.h>
|
|
|
|
#include <sys/conf.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/uio.h>
|
|
|
|
#include <sys/malloc.h>
|
|
|
|
#include <sys/devconf.h>
|
|
|
|
#include <sys/errno.h>
|
|
|
|
#ifdef DEVFS
|
|
|
|
#include <sys/devfsext.h>
|
|
|
|
#endif /* DEVFS */
|
|
|
|
|
|
|
|
#include <machine/cpu.h>
|
|
|
|
#include <machine/clock.h>
|
|
|
|
#include <machine/qcam.h>
|
|
|
|
|
|
|
|
#include <i386/isa/qcamreg.h>
|
|
|
|
#include <i386/isa/isa.h>
|
|
|
|
#include <i386/isa/isa_device.h>
|
|
|
|
|
|
|
|
int qcam_debug = 1;
|
|
|
|
|
|
|
|
static struct qcam_softc {
|
|
|
|
u_char *buffer; /* frame buffer */
|
|
|
|
u_char *buffer_end; /* end of frame buffer */
|
|
|
|
u_int flags;
|
|
|
|
u_int iobase;
|
|
|
|
int unit; /* device */
|
|
|
|
void (*scanner)(struct qcam_softc *);
|
|
|
|
|
|
|
|
int init_req; /* initialization required */
|
|
|
|
int x_size; /* pixels */
|
|
|
|
int y_size; /* pixels */
|
|
|
|
int x_origin; /* ?? units */
|
|
|
|
int y_origin; /* ?? units */
|
|
|
|
int zoom; /* 0=none, 1=1.5x, 2=2x */
|
|
|
|
int bpp; /* 4 or 6 */
|
|
|
|
u_char xferparms; /* calcualted transfer params */
|
|
|
|
u_char contrast;
|
|
|
|
u_char brightness;
|
|
|
|
u_char whitebalance;
|
|
|
|
|
|
|
|
struct kern_devconf kdc; /* kernel config database */
|
|
|
|
|
|
|
|
#ifdef DEVFS
|
|
|
|
void *devfs_token;
|
|
|
|
#endif
|
|
|
|
} qcam_softc[NQCAM];
|
|
|
|
|
|
|
|
static struct kern_devconf kdc_qcam_template = {
|
|
|
|
0, 0, 0, /* filled in by dev_attach() */
|
|
|
|
"qcam", /* kdc_name */
|
|
|
|
0, /* kdc_unit */
|
|
|
|
{ /* kdc_md */
|
|
|
|
MDDT_ISA, /* mddc_devtype */
|
|
|
|
0, /* mddc_flags */
|
|
|
|
"tty" /* mddc_imask[4] */
|
|
|
|
},
|
|
|
|
isa_generic_externalize, /* kdc_externalize */
|
|
|
|
0, /* kdc_internalize */
|
|
|
|
0, /* kdc_goaway */
|
|
|
|
ISA_EXTERNALLEN, /* kdc_datalen */
|
|
|
|
&kdc_isa0, /* kdc_parent */
|
|
|
|
0, /* kdc_parentdata */
|
|
|
|
DC_UNCONFIGURED, /* kdc_state */
|
|
|
|
"", /* kdc_description */
|
|
|
|
DC_CLS_MISC /* class */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* flags in softc */
|
|
|
|
#define QC_OPEN 0x01 /* device open */
|
|
|
|
#define QC_ALIVE 0x02 /* probed and attached */
|
|
|
|
#define QC_BIDIR_HW 0x04 /* bidir parallel port */
|
|
|
|
|
|
|
|
#define QC_MAXFRAMEBUFSIZE (QC_MAX_XSIZE*QC_MAX_YSIZE)
|
|
|
|
|
|
|
|
static const u_char qcam_zoommode[3][3] = {
|
|
|
|
{ QC_XFER_WIDE, QC_XFER_WIDE, QC_XFER_WIDE },
|
|
|
|
{ QC_XFER_NARROW, QC_XFER_WIDE, QC_XFER_WIDE },
|
|
|
|
{ QC_XFER_TIGHT, QC_XFER_NARROW, QC_XFER_WIDE }
|
|
|
|
};
|
|
|
|
|
|
|
|
#define UNIT(dev) minor(dev)
|
|
|
|
|
|
|
|
static int qcam_probe(struct isa_device *id);
|
|
|
|
static int qcam_attach(struct isa_device *id);
|
|
|
|
|
|
|
|
struct isa_driver qcamdriver =
|
|
|
|
{qcam_probe, qcam_attach, "qcam"};
|
|
|
|
|
|
|
|
static d_open_t qcam_open;
|
|
|
|
static d_close_t qcam_close;
|
|
|
|
static d_read_t qcam_read;
|
|
|
|
static d_ioctl_t qcam_ioctl;
|
|
|
|
|
|
|
|
#define CDEV_MAJOR 73 /* XXX change this! */
|
|
|
|
|
|
|
|
static struct cdevsw qcam_cdevsw =
|
|
|
|
{ qcam_open, qcam_close, qcam_read, nowrite,
|
|
|
|
qcam_ioctl, nostop, nullreset, nodevtotty,
|
|
|
|
noselect, nommap, nostrategy, "qcam",
|
|
|
|
NULL, -1 };
|
|
|
|
|
|
|
|
#define read_data(P) inb((P))
|
|
|
|
#define read_data_word(P) inw((P))
|
|
|
|
#define read_status(P) inb((P)+1)
|
|
|
|
#define write_data(P, V) outb((P)+0, (V))
|
|
|
|
#define write_status(P, V) outb((P)+1, (V))
|
|
|
|
#define write_control(P, V) outb((P)+2, (V))
|
|
|
|
|
|
|
|
#define QC_TIMEOUT 10000 /* timeout on reads */
|
|
|
|
|
|
|
|
#define READ_STATUS_BYTE_HIGH(P, V) { \
|
|
|
|
u_int timeout = QC_TIMEOUT; \
|
|
|
|
do { (V) = read_status((P)); \
|
|
|
|
} while (!(((V) & 0x08)) && timeout-- > 0); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define READ_STATUS_BYTE_LOW(P, V) { \
|
|
|
|
u_int timeout = QC_TIMEOUT; \
|
|
|
|
do { (V) = read_status((P)); \
|
|
|
|
} while (((V) & 0x08) && timeout-- > 0); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define READ_DATA_WORD_HIGH(P, V) { \
|
|
|
|
u_int timeout = QC_TIMEOUT; \
|
|
|
|
do { (V) = read_data_word((P)); \
|
|
|
|
} while (!(((V) & 0x01)) && timeout-- > 0); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define READ_DATA_WORD_LOW(P, V) { \
|
|
|
|
u_int timeout = QC_TIMEOUT; \
|
|
|
|
do { (V) = read_data_word((P)); \
|
|
|
|
} while (((V) & 0x01) && timeout-- > 0); \
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static int
|
|
|
|
sendbyte (u_int port, int value)
|
|
|
|
{
|
|
|
|
u_char s1, s2;
|
|
|
|
|
|
|
|
write_data(port, value);
|
|
|
|
DELAY(100);
|
|
|
|
write_data(port, value);
|
|
|
|
DELAY(100);
|
|
|
|
write_data(port, value);
|
|
|
|
|
|
|
|
write_control(port, 0x06);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, s1);
|
|
|
|
|
|
|
|
write_control(port, 0x0e);
|
|
|
|
READ_STATUS_BYTE_LOW(port, s2);
|
|
|
|
|
|
|
|
return (s1 & 0xf0) | (s2 >> 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
send_command (u_int port, int cmd, int value)
|
|
|
|
{
|
|
|
|
if (sendbyte(port, cmd) != cmd)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (sendbyte(port, value) != value)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0; /* success */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_reset (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
register u_int iobase = qs->iobase;
|
|
|
|
|
|
|
|
write_control(iobase, 0x20);
|
|
|
|
write_data (iobase, 0x75);
|
|
|
|
|
|
|
|
if (read_data(iobase) != 0x75)
|
|
|
|
qs->flags |= QC_BIDIR_HW; /* bidirectional parallel port */
|
|
|
|
else
|
|
|
|
qs->flags &= ~QC_BIDIR_HW;
|
|
|
|
|
|
|
|
write_control(iobase, 0x0b);
|
|
|
|
DELAY(250);
|
|
|
|
write_control(iobase, 0x0e);
|
|
|
|
DELAY(250);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_waitfor_bi (u_int port)
|
|
|
|
{
|
|
|
|
u_char s1, s2;
|
|
|
|
|
|
|
|
write_control(port, 0x28);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, s1);
|
|
|
|
|
|
|
|
write_control(port, 0x2f);
|
|
|
|
READ_STATUS_BYTE_LOW(port, s2);
|
|
|
|
|
|
|
|
return (s1 & 0xf0) | (s2 >> 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_bi_4bit (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
static int first_time = 1;
|
|
|
|
|
|
|
|
if (first_time) {
|
|
|
|
first_time = 0;
|
|
|
|
printf("qcam%d: bidirectional 4bpp transfers not implemented\n",
|
|
|
|
qs->unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_bi_6bit (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
u_char *p;
|
|
|
|
u_int word, port;
|
|
|
|
|
|
|
|
port = qs->iobase; /* for speed */
|
|
|
|
|
|
|
|
qcam_waitfor_bi(port); /* wait for ready */
|
|
|
|
|
|
|
|
for (p = qs->buffer; p < qs->buffer_end; ) {
|
|
|
|
write_control(port, 0x26);
|
|
|
|
READ_DATA_WORD_HIGH(port, word);
|
|
|
|
|
|
|
|
/* this can be optimized _significantly_ */
|
|
|
|
word = (((word & 0xff00) >> 3) | (word & 0x00ff)) << 1;
|
|
|
|
word = ((word & 0x00ff) >> 2) | (word & 0xff00);
|
|
|
|
*p++ = 63 - (word & 0xff);
|
|
|
|
*p++ = 63 - ((word >> 8) & 0xff);
|
|
|
|
|
|
|
|
write_control(port, 0x2f);
|
|
|
|
READ_DATA_WORD_LOW(port, word);
|
|
|
|
|
|
|
|
/* this can be optimized _significantly_ */
|
|
|
|
word = (((word & 0xff00) >> 3) | (word & 0x00ff)) << 1;
|
|
|
|
word = ((word & 0x00ff) >> 2) | (word & 0xff00);
|
|
|
|
|
|
|
|
*p++ = 63 - (word & 0xff);
|
|
|
|
*p++ = 63 - ((word >> 8) & 0xff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_uni_4bit (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
u_char *p;
|
|
|
|
u_int word, port;
|
|
|
|
|
|
|
|
port = qs->iobase;
|
|
|
|
|
|
|
|
for (p = qs->buffer; p < qs->buffer_end; ) {
|
|
|
|
write_control(port, 0x06);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, word);
|
|
|
|
|
|
|
|
*p++ = 16 - ((word & 0xf0) >> 4);
|
|
|
|
|
|
|
|
write_control(port, 0x0e);
|
|
|
|
READ_STATUS_BYTE_LOW(port, word);
|
|
|
|
|
|
|
|
*p++ = 16 - ((word & 0xf0) >> 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_uni_6bit (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
u_char *p;
|
|
|
|
u_int word1, word2, word3, port;
|
|
|
|
|
|
|
|
port = qs->iobase;
|
|
|
|
|
|
|
|
for (p = qs->buffer; p < qs->buffer_end; ) {
|
|
|
|
write_control(port, 0x06);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, word1);
|
|
|
|
|
|
|
|
word2 = word1 & 0xf0;
|
|
|
|
|
|
|
|
write_control(port, 0x0e);
|
|
|
|
READ_STATUS_BYTE_LOW(port, word1);
|
|
|
|
|
|
|
|
word2 |= (word1 & 0xf0) >> 4;
|
|
|
|
*p++ = 63 - ((word2 >> 2) & 0x3f);
|
|
|
|
|
|
|
|
write_control(port, 0x06);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, word1);
|
|
|
|
|
|
|
|
word3 = word2;
|
|
|
|
word2 = word1 & 0xf0;
|
|
|
|
|
|
|
|
write_control(port, 0x0e);
|
|
|
|
READ_STATUS_BYTE_LOW(port, word1);
|
|
|
|
|
|
|
|
word2 |= (word1 & 0xf0) >> 4;
|
|
|
|
*p++ = 63 - (((word3 & 0x03) << 4) | (word2 >> 4));
|
|
|
|
|
|
|
|
write_control(port, 0x06);
|
|
|
|
READ_STATUS_BYTE_HIGH(port, word1);
|
|
|
|
|
|
|
|
word3 = word2;
|
|
|
|
word2 = word1 & 0xf0;
|
|
|
|
|
|
|
|
write_control(port, 0x0e);
|
|
|
|
READ_STATUS_BYTE_LOW(port, word1);
|
|
|
|
|
|
|
|
word2 |= (word1 & 0xf0) >> 4;
|
|
|
|
*p++ = 63 - (((word3 & 0x0f) << 2) | (word2 >> 6));
|
|
|
|
*p++ = 63 - (word2 & 0x3f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_xferparms (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
int bidir;
|
|
|
|
|
|
|
|
qs->xferparms = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX the && qs->bpp==6 is a temporary hack because we don't
|
1996-02-02 20:37:51 +00:00
|
|
|
* have code for doing 4bpp bidirectional transfers yet.
|
1996-02-02 19:27:57 +00:00
|
|
|
*/
|
1996-02-02 20:37:51 +00:00
|
|
|
bidir = (qs->flags & QC_BIDIR_HW) && (qs->bpp == 6);
|
1996-02-02 19:27:57 +00:00
|
|
|
|
|
|
|
if (bidir)
|
|
|
|
qs->xferparms |= QC_XFER_BIDIR;
|
|
|
|
|
|
|
|
if (qs->bpp == 6) {
|
|
|
|
qs->xferparms |= QC_XFER_6BPP;
|
|
|
|
qs->scanner = bidir ? qcam_bi_6bit : qcam_uni_6bit;
|
|
|
|
} else {
|
|
|
|
qs->scanner = bidir ? qcam_bi_4bit : qcam_uni_4bit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qs->x_size > 160 || qs->y_size > 120) {
|
|
|
|
qs->xferparms |= qcam_zoommode[0][qs->zoom];
|
|
|
|
} else if (qs->x_size > 80 || qs->y_size > 60) {
|
|
|
|
qs->xferparms |= qcam_zoommode[1][qs->zoom];
|
|
|
|
} else
|
|
|
|
qs->xferparms |= qcam_zoommode[2][qs->zoom];
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (qcam_debug)
|
|
|
|
printf("qcam%d: [(%d,%d), %sdir, %dbpp, %d zoom] = 0x%x\n",
|
|
|
|
qs->unit, qs->x_size, qs->y_size,
|
|
|
|
bidir ? "bi" : "uni", qs->bpp, qs->zoom,
|
|
|
|
qs->xferparms);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_init (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
int x_size = (qs->bpp == 4) ? qs->x_size / 2 : qs->x_size / 4;
|
|
|
|
|
|
|
|
qcam_xferparms(qs);
|
|
|
|
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, qs->brightness);
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, 1);
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, 1);
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, qs->brightness);
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, qs->brightness);
|
|
|
|
send_command(qs->iobase, QC_BRIGHTNESS, qs->brightness);
|
|
|
|
send_command(qs->iobase, QC_XSIZE, x_size);
|
|
|
|
send_command(qs->iobase, QC_YSIZE, qs->y_size);
|
|
|
|
send_command(qs->iobase, QC_YORG, qs->y_origin);
|
|
|
|
send_command(qs->iobase, QC_XORG, qs->x_origin);
|
|
|
|
send_command(qs->iobase, QC_CONTRAST, qs->contrast);
|
|
|
|
send_command(qs->iobase, QC_WHITEBALANCE, qs->whitebalance);
|
|
|
|
|
|
|
|
if (qs->buffer)
|
|
|
|
qs->buffer_end = qs->buffer +
|
|
|
|
min((qs->x_size*qs->y_size), QC_MAXFRAMEBUFSIZE);
|
|
|
|
|
|
|
|
qs->init_req = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_scan (struct qcam_softc *qs)
|
|
|
|
{
|
|
|
|
register u_int iobase = qs->iobase;
|
|
|
|
|
|
|
|
if (qs->init_req)
|
|
|
|
qcam_init(qs);
|
|
|
|
|
|
|
|
send_command(iobase, QC_BRIGHTNESS, qs->brightness);
|
|
|
|
send_command(iobase, QC_XFERMODE, qs->xferparms);
|
|
|
|
|
|
|
|
if (qs->scanner)
|
|
|
|
(*qs->scanner)(qs);
|
|
|
|
|
|
|
|
write_control(iobase, 0x0f);
|
|
|
|
write_control(iobase, 0x0b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_registerdev (struct isa_device *id, const char *descr)
|
|
|
|
{
|
|
|
|
struct kern_devconf *kdc = &qcam_softc[id->id_unit].kdc;
|
|
|
|
|
|
|
|
*kdc = kdc_qcam_template; /* byte-copy template */
|
|
|
|
|
|
|
|
kdc->kdc_unit = id->id_unit;
|
|
|
|
kdc->kdc_parentdata = id;
|
|
|
|
kdc->kdc_description = descr;
|
|
|
|
|
|
|
|
dev_attach(kdc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_probe (struct isa_device *devp)
|
|
|
|
{
|
|
|
|
u_char reg, last;
|
|
|
|
int i, transitions = 0;
|
|
|
|
|
|
|
|
switch (devp->id_iobase) { /* don't probe weird ports */
|
|
|
|
case IO_LPT1:
|
|
|
|
case IO_LPT2:
|
|
|
|
case IO_LPT3:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printf("qcam%d: ignoring non-standard port 0x%x\n",
|
|
|
|
devp->id_unit, devp->id_iobase);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write 0's to control and data ports */
|
|
|
|
write_control(devp->id_iobase, 0x20);
|
|
|
|
write_control(devp->id_iobase, 0x0b);
|
|
|
|
write_control(devp->id_iobase, 0x0e);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Attempt a non-destructive probe for the QuickCam.
|
|
|
|
* Current models appear to toggle the upper 4 bits of
|
|
|
|
* the status register at approximately 5-10 Hz.
|
|
|
|
*
|
|
|
|
* Be aware that this isn't the way that Connectix detects the
|
|
|
|
* camera (they send a reset and try to handshake), but this
|
|
|
|
* way is safe.
|
|
|
|
*/
|
|
|
|
last = reg = read_status(devp->id_iobase);
|
|
|
|
|
|
|
|
for (i = 0; i < QC_PROBELIMIT; i++) {
|
|
|
|
|
|
|
|
reg = read_status(devp->id_iobase) & 0xf0;
|
|
|
|
|
|
|
|
if (reg != last) /* if we got a toggle, count it */
|
|
|
|
transitions++;
|
|
|
|
|
|
|
|
last = reg;
|
|
|
|
DELAY(100000); /* 100ms */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transitions <= QC_PROBECNTLOW || transitions >= QC_PROBECNTHI) {
|
|
|
|
if (bootverbose)
|
|
|
|
printf("qcam%d: not found, probed %d, got %d transitions\n",
|
|
|
|
devp->id_unit, i, transitions);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
qcam_registerdev(devp, "QuickCam video input");
|
|
|
|
return 1; /* found */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcam_default (struct qcam_softc *qs) {
|
|
|
|
qs->contrast = QC_DEF_CONTRAST;
|
|
|
|
qs->brightness = QC_DEF_BRIGHTNESS;
|
|
|
|
qs->whitebalance = QC_DEF_WHITEBALANCE;
|
|
|
|
qs->x_size = QC_DEF_XSIZE;
|
|
|
|
qs->y_size = QC_DEF_YSIZE;
|
|
|
|
qs->x_origin = QC_DEF_XORG;
|
|
|
|
qs->y_origin = QC_DEF_YORG;
|
|
|
|
qs->bpp = QC_DEF_BPP;
|
|
|
|
qs->zoom = QC_DEF_ZOOM;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_attach (struct isa_device *devp)
|
|
|
|
{
|
|
|
|
struct qcam_softc *qs = &qcam_softc[devp->id_unit];
|
|
|
|
|
|
|
|
qs->iobase = devp->id_iobase;
|
|
|
|
qs->unit = devp->id_unit;
|
|
|
|
qs->kdc.kdc_state = DC_IDLE;
|
|
|
|
qs->flags |= QC_ALIVE;
|
|
|
|
|
|
|
|
qcam_default(qs);
|
|
|
|
qcam_xferparms(qs);
|
|
|
|
|
|
|
|
#ifdef DEVFS
|
|
|
|
{
|
|
|
|
char name[32];
|
|
|
|
sprintf(name,"qcam%d", qs->unit);
|
|
|
|
qs->devfs_token = devfs_add_devsw("/", name, &qcam_cdevsw, qs->unit,
|
|
|
|
DV_CHR, 0, 0, 0600);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_open (dev_t dev, int flags, int fmt, struct proc *p)
|
|
|
|
{
|
|
|
|
struct qcam_softc *qs = (struct qcam_softc *)&qcam_softc[UNIT(dev)];
|
|
|
|
|
|
|
|
if (!(qs->flags & QC_ALIVE))
|
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
if (qs->flags & QC_OPEN)
|
|
|
|
return EBUSY;
|
|
|
|
|
|
|
|
qs->buffer_end = qs->buffer = malloc(QC_MAXFRAMEBUFSIZE, M_DEVBUF,
|
|
|
|
M_WAITOK);
|
|
|
|
if (!qs->buffer)
|
|
|
|
return ENOMEM;
|
|
|
|
|
|
|
|
qcam_reset(qs);
|
|
|
|
qcam_default(qs);
|
|
|
|
qcam_init(qs);
|
|
|
|
|
|
|
|
qs->flags |= QC_OPEN;
|
|
|
|
qs->kdc.kdc_state = DC_BUSY;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_close (dev_t dev, int flags, int fmt, struct proc *p)
|
|
|
|
{
|
|
|
|
struct qcam_softc *qs = (struct qcam_softc *)&qcam_softc[UNIT(dev)];
|
|
|
|
|
|
|
|
if (qs->buffer) {
|
|
|
|
free(qs->buffer, M_DEVBUF);
|
|
|
|
qs->buffer = NULL;
|
|
|
|
qs->buffer_end = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
qs->flags &= ~QC_OPEN;
|
|
|
|
qs->kdc.kdc_state = DC_IDLE;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_read (dev_t dev, struct uio *uio, int ioflag)
|
|
|
|
{
|
|
|
|
struct qcam_softc *qs = (struct qcam_softc *)&qcam_softc[UNIT(dev)];
|
|
|
|
int bytes, bufsize;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
/* if we've seeked back to 0, that's our signal to scan */
|
|
|
|
if (uio->uio_offset == 0)
|
|
|
|
qcam_scan(qs);
|
|
|
|
|
|
|
|
bufsize = qs->buffer_end - qs->buffer;
|
|
|
|
if (uio->uio_offset > bufsize)
|
|
|
|
return EIO;
|
|
|
|
|
|
|
|
bytes = min(uio->uio_resid, (bufsize - uio->uio_offset));
|
|
|
|
error = uiomove(qs->buffer + uio->uio_offset, bytes, uio);
|
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
|
|
|
|
return 0; /* success */
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_ioctl (dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
|
|
|
|
{
|
|
|
|
struct qcam_softc *qs = (struct qcam_softc *)&qcam_softc[UNIT(dev)];
|
|
|
|
struct qcam *info= (struct qcam *)data;
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
return(EINVAL);
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
|
|
|
case QC_GET:
|
|
|
|
info->qc_version = QC_IOCTL_VERSION;
|
|
|
|
info->qc_xsize = qs->x_size;
|
|
|
|
info->qc_ysize = qs->y_size;
|
|
|
|
info->qc_xorigin = qs->x_origin;
|
|
|
|
info->qc_yorigin = qs->y_origin;
|
|
|
|
info->qc_bpp = qs->bpp;
|
|
|
|
info->qc_zoom = qs->zoom;
|
|
|
|
info->qc_brightness = qs->brightness;
|
|
|
|
info->qc_whitebalance = qs->whitebalance;
|
|
|
|
info->qc_contrast = qs->contrast;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case QC_SET:
|
|
|
|
/*
|
|
|
|
* sanity check parameters passed in by user
|
|
|
|
* we're extra paranoid right now because the API
|
|
|
|
* is in flux
|
|
|
|
*/
|
|
|
|
if (info->qc_xsize > QC_MAX_XSIZE ||
|
|
|
|
info->qc_ysize > QC_MAX_YSIZE ||
|
|
|
|
info->qc_xorigin > QC_MAX_XSIZE ||
|
|
|
|
info->qc_yorigin > QC_MAX_YSIZE ||
|
|
|
|
(info->qc_bpp != 4 && info->qc_bpp != 6) ||
|
|
|
|
info->qc_zoom > QC_ZOOM_200 ||
|
|
|
|
info->qc_brightness > UCHAR_MAX ||
|
|
|
|
info->qc_whitebalance > UCHAR_MAX ||
|
|
|
|
info->qc_contrast > UCHAR_MAX)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
/* version check */
|
|
|
|
if (info->qc_version != QC_IOCTL_VERSION)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
qs->x_size = info->qc_xsize;
|
|
|
|
qs->y_size = info->qc_ysize;
|
|
|
|
qs->x_origin = info->qc_xorigin;
|
|
|
|
qs->y_origin = info->qc_yorigin;
|
|
|
|
qs->bpp = info->qc_bpp;
|
|
|
|
qs->zoom = info->qc_zoom;
|
|
|
|
qs->brightness = info->qc_brightness;
|
|
|
|
qs->whitebalance = info->qc_whitebalance;
|
|
|
|
qs->contrast = info->qc_contrast;
|
|
|
|
|
|
|
|
/* request initialization before next scan pass */
|
|
|
|
qs->init_req = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return ENOTTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
*-------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef ACTUALLY_LKM_NOT_KERNEL
|
|
|
|
/*
|
|
|
|
* Loadable QuickCam driver stubs
|
|
|
|
* XXX This isn't quite working yet, but the template work is done.
|
|
|
|
* XXX do not attempt to use this driver as a LKM (yet)
|
|
|
|
*/
|
|
|
|
#include <sys/exec.h>
|
|
|
|
#include <sys/sysent.h>
|
|
|
|
#include <sys/lkm.h>
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Construct lkm_dev structures (see lkm.h)
|
|
|
|
*/
|
|
|
|
|
|
|
|
MOD_DEV(qcam, LM_DT_CHAR, CDEV_MAJOR, &qcam_cdevsw);
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_load (struct lkm_table *lkmtp, int cmd)
|
|
|
|
{
|
|
|
|
/* we need to call attach here with sane parameters */
|
|
|
|
return 0; /* nothing need be done */
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcam_unload (struct lkm_table *lkmtp, int cmd)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < NQCAM; i++)
|
|
|
|
if (qcam_softc[i].flags & QC_OPEN)
|
|
|
|
return EBUSY;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
qcam_mod (struct lkm_table *lkmtp, int cmd, int ver)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (ver != LKM_VERSION)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case LKM_E_LOAD:
|
|
|
|
err = qcam_load(lkmtp, cmd);
|
|
|
|
break;
|
|
|
|
case LKM_E_UNLOAD:
|
|
|
|
err = qcam_unload(lkmtp, cmd);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* register the cdevsw entry */
|
|
|
|
lkmtp->private.lkm_dev = &qcam_module;
|
|
|
|
return lkmdispatch(lkmtp, cmd);
|
|
|
|
}
|
|
|
|
#endif /* LKM */
|
|
|
|
|
|
|
|
/*
|
|
|
|
*-------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the dynamic cdevsw hooks.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
qcam_drvinit (void *unused)
|
|
|
|
{
|
|
|
|
static int qcam_devsw_installed = 0;
|
|
|
|
dev_t dev;
|
|
|
|
|
|
|
|
if (!qcam_devsw_installed) {
|
|
|
|
dev = makedev(CDEV_MAJOR, 0);
|
|
|
|
cdevsw_add(&dev,&qcam_cdevsw, NULL);
|
|
|
|
qcam_devsw_installed++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSINIT(qcamdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,qcam_drvinit,NULL)
|
|
|
|
|
|
|
|
#endif /* NQCAM */
|