📄 qcamvc_pp.c
字号:
/* * * Connectix USB QuickCam VC Video Camera driver * * (C) Copyright 2001 De Marchi Daniele * (C) Copyright 2004 Terry Mohan * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * Jan 2001 This driver develop started on the linux * kernel 2.4.0. * */#undef _QCAMVC_DEBUG_ //#define _QCAMVC_DEBUG_ #undef QCAMVC_PARPORT_DMA#define QCAMVC_PARPORT_DMA 1 /* comment this out to remove ECP DMA support */#define CONFIG_PARPORT_PC #include <linux/config.h>#include <linux/version.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/mm.h>#include <linux/spinlock.h>#include <linux/pci.h>#include <asm/dma.h> #include <linux/parport.h>#include <linux/parport_pc.h>#include <linux/delay.h>#include <linux/sched.h>#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,3))#ifdef CONFIG_KMOD#include <linux/kmod.h>#endif#else#include <linux/kmod.h>#endif#include "qcamvc.h"struct pp_cam_entry{ struct parport *port; struct pardevice *pdev; int open_count;};/* Magic numbers for defining port-device mappings */#define QCAMVC_PARPORT_UNSPEC -4#define QCAMVC_PARPORT_AUTO -3#define QCAMVC_PARPORT_OFF -2#define QCAMVC_PARPORT_NONE -1#ifdef MODULEstatic int parport_nr[PARPORT_MAX] = {[0 ... PARPORT_MAX - 1] = QCAMVC_PARPORT_UNSPEC};static char *parport[PARPORT_MAX] = {NULL,};MODULE_AUTHOR("De Marchi Daniele <demarchidaniele@libero.it>");MODULE_DESCRIPTION("Connectix QuickCam VC(PP) Driver");MODULE_LICENSE("GPL");MODULE_PARM(parport, "1-" __MODULE_STRING(PARPORT_MAX) "s");MODULE_PARM_DESC(parport, "'auto' or a list of parallel port numbers. Just like lp.");#elsestatic int parport_nr[PARPORT_MAX] __initdata = {[0 ... PARPORT_MAX - 1] = QCAMVC_PARPORT_UNSPEC};static int parport_ptr = 0;#endif/*****************************************************************************//* *//* Lowlevel driver for Connectix QuickCam VC(PP) *//* *//*****************************************************************************/static int qcamvc_open(void *privdata);static int qcamvc_close(void *privdata);static size_t qcamvc_get_reg(void *privdata, unsigned char reg, void *buffer, size_t len);static int qcamvc_set_reg(void *privdata, unsigned char reg, void *buffer, size_t len);static size_t qcamvc_read(void *privdata, void *buffer, size_t len);static int qcamvc_write(void *privdata, void *buffer, size_t len);static int qcamvc_stream_start(void *privdata);static int qcamvc_stream_stop(void *privdata);static size_t qcamvc_stream_read(void *privdata, void *buffer, size_t len);static struct qcamvc_camera_ops qcamvc_pp_ops = { qcamvc_open, qcamvc_close, qcamvc_write, qcamvc_read, qcamvc_set_reg, qcamvc_get_reg, qcamvc_stream_start, qcamvc_stream_stop, qcamvc_stream_read, THIS_MODULE,};static LIST_HEAD(cam_list);static spinlock_t cam_list_lock_pp;static unsigned short ppmode = IEEE1284_MODE_ECP;#ifdef QCAMVC_PARPORT_DMA/* forward declarations for IEEE1284 ECP DMA read support */static int change_mode(struct parport *, int );static void frob_econtrol (struct parport *, unsigned char, unsigned char);/* ----------------------------------------*//* Parallel Port DMA Code from CPiA driver *//* ----------------------------------------*//* DMA support here thanks to: Blaise Gassend <blaise@gassend.com> *//*ECP parport lowlevel IEEE1284 DMA read support: */#define ECR_PS2 01#define ECR_ECP 03/* this an adaptation of code from parport_pc_ecp_read_block_pio() and * parport_pc_fifo_write_block_dma() */static size_t qcamvc_ecp_read_block_dma (struct parport *port, void *bufv, size_t length, int flags){ int r = 0; unsigned long dmaflag; size_t left = length; const struct parport_pc_private *priv = port->physport->private_data; dma_addr_t dma_addr, dma_handle = 0; size_t maxlen = 0x10000; /* max 64k per DMA transfer */ char *buf = bufv; unsigned long start = (unsigned long) buf; unsigned long end = (unsigned long) buf + length - 1; size_t count = length; if (length == 0) { DBG ("(length == 0)"); return 0; } /* Special case: a timeout of zero means we cannot call schedule(). */ if (!port->cad->timeout) { DBG ("cannot call schedule()"); return parport_ieee1284_ecp_read_data (port, buf, length, flags); } port = port->physport; if (port->ieee1284.phase != IEEE1284_PH_REV_IDLE) { /* change to reverse-idle phase (must be in forward-idle) */ /* Event 38: Set nAutoFd low (also make sure nStrobe is high) */ parport_frob_control (port, PARPORT_CONTROL_STROBE |PARPORT_CONTROL_AUTOFD, PARPORT_CONTROL_AUTOFD); parport_pc_data_reverse (port); /* Must be in PS2 mode */ udelay (5); /* Event 39: Set nInit low to initiate bus reversal */ parport_frob_control (port, PARPORT_CONTROL_INIT, 0); /* Event 40: Wait for nAckReverse (PError) to go low */ if ( (r = parport_wait_peripheral (port, PARPORT_STATUS_PAPEROUT, 0)) ) { printk (KERN_DEBUG "%s: PE timeout Event 40 (%d) " "in ecp_read_block_dma\n", port->name, r); return 0; } port->ieee1284.phase = IEEE1284_PH_REV_DATA; parport_frob_control (port, PARPORT_CONTROL_STROBE |PARPORT_CONTROL_AUTOFD, PARPORT_CONTROL_AUTOFD); if (change_mode (port, ECR_ECP)) /* ECP FIFO */ printk (KERN_DEBUG "%s: Warning change_mode ECR_ECP failed\n", port->name); /* the first byte must be collected manually */ /* Event 43: Wait for nAck to go low */ if (parport_wait_peripheral (port, PARPORT_STATUS_ACK, 0)) { /* timed out while reading -- no data */ printk (KERN_DEBUG "DMA read timed out (initial byte)\n"); goto out_no_data; }#if 0 /* this is active in parport_pc_ecp_read_block_pio */ /* read byte */ *buf++ = inb (DATA (port)); left--;#endif /* Event 44: nAutoFd (HostAck) goes high to acknowledge */ parport_pc_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); #if 0 /* this is commented out in parport_pc_ecp_read_block_pio */ /* Event 45: Wait for nAck to go high */ if (parport_wait_peripheral (port, PARPORT_STATUS_ACK, PARPORT_STATUS_ACK)) { /* timed out while waiting for peripheral to respond to ack */ printk ("ECP DMA read timed out (waiting for nAck)\n"); /* keep hold of the byte we've got already */ goto out_no_data; }#endif } #if 0 /* active in parport_pc_ecp_read_block_pio */ /* Event 46: nAutoFd (HostAck) goes low to accept more data */ parport_pc_frob_control (port, PARPORT_CONTROL_AUTOFD, PARPORT_CONTROL_AUTOFD);#endif if (!left) { DBG ("(!left)\n"); goto out_no_data; } /* now do the DMA transfer (adapted from parport_pc_fifo_write_block_dma) */ if (end < MAX_DMA_ADDRESS) { /* If it would cross a 64k boundary, cap it at the end. */ if ((start ^ end) & ~0xffffUL) maxlen = 0x10000 - (start & 0xffff); dma_addr = dma_handle = pci_map_single(priv->dev, (void *)buf, length, PCI_DMA_FROMDEVICE); } else { /* above 16 MB we use a bounce buffer as ISA-DMA is not possible */ maxlen = PAGE_SIZE; /* sizeof(priv->dma_buf) */ dma_addr = priv->dma_handle; } DBG ("DMA buffer is ready\n"); /* We don't want to be interrupted every character. */ parport_pc_disable_irq (port); /* set nErrIntrEn and serviceIntr */ frob_econtrol (port, (1<<4) | (1<<2), (1<<4) | (1<<2)); /* Reverse mode. */ parport_pc_data_reverse (port); /* Must be in PS2 mode */ while (left) {#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) long expire = jiffies + port->physport->cad->timeout;#else unsigned long expire = jiffies + port->physport->cad->timeout;#endif //DBG ("start of while %i left\n", left); count = left; if (count > maxlen) count = maxlen; dmaflag = claim_dma_lock(); disable_dma(port->dma); clear_dma_ff(port->dma); set_dma_mode(port->dma, DMA_MODE_READ); set_dma_addr(port->dma, dma_addr); set_dma_count(port->dma, count); /* Set DMA mode */ frob_econtrol (port, 1<<3, 1<<3); /* Clear serviceIntr */ frob_econtrol (port, 1<<2, 0); enable_dma(port->dma); release_dma_lock(dmaflag); /* Wait for interrupt. */ //DBG ("about to wait\n");false_alarm: if (parport_wait_event (port, HZ) < 0) { DBG ("(parport_wait_event (port, HZ) < 0)\n"); break; } if (!time_before (jiffies, expire)) { /* Timed out. */ DBG("DMA read timed out\n"); break; } /* Is serviceIntr set? */ if (!(inb (ECONTROL (port)) & (1<<2))) {#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) if (current->need_resched) schedule ();#else cond_resched();#endif DBG("false_alarm\n"); goto false_alarm; } dmaflag = claim_dma_lock(); disable_dma(port->dma); clear_dma_ff(port->dma); count -= get_dma_residue(port->dma); release_dma_lock(dmaflag);#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) if (current->need_resched) /* Can't yield the port. */ schedule ();#else cond_resched(); /* Can't yield the port. */#endif /* update for possible DMA residue ! */ DBG ("read %i in loop\n", count); if (dma_handle) dma_addr += count; else memcpy(buf, priv->dma_buf, count); buf += count; left -= count; } frob_econtrol (port, 1<<2, 1<<2); /* Turn off DMA mode */ frob_econtrol (port, 1<<3, 0); if (dma_handle) pci_unmap_single(priv->dev, dma_handle, length, PCI_DMA_TODEVICE); out_no_data: if (change_mode (port, ECR_PS2)) /* ECP FIFO */ printk (KERN_DEBUG "%s: Warning change_mode ECR_PS2 failed\n", port->name); /* Go to forward idle mode to shut the peripheral up (event 47). */ parport_frob_control (port, PARPORT_CONTROL_INIT | PARPORT_CONTROL_AUTOFD, PARPORT_CONTROL_INIT | PARPORT_CONTROL_AUTOFD); /* event 49: PError goes high */ if ( (r = parport_wait_peripheral (port, PARPORT_STATUS_PAPEROUT, PARPORT_STATUS_PAPEROUT)) ) { printk (KERN_DEBUG "%s: PE timeout FWDIDLE (%d) in ecp_read_block_dma\n", port->name, r); } else { parport_data_forward (port); port->ieee1284.phase = IEEE1284_PH_FWD_IDLE; } //DBG ("returning %i bytes read, endseen = %d\n", length - left, endseen); return length - left;}#endif /*QCAMVC_PARPORT_DMA */static int qcamvc_open(void *privdata){ struct pp_cam_entry *cam = (struct pp_cam_entry *)privdata; if (cam == NULL) return -EINVAL; if(cam->open_count == 0) { if (parport_claim_or_block(cam->pdev)) { printk(KERN_ERR "failed to claim the port\n"); return -EBUSY; } parport_negotiate(cam->port, IEEE1284_MODE_COMPAT); parport_data_forward(cam->port); parport_write_control(cam->port, PARPORT_CONTROL_SELECT); udelay(50); parport_write_control(cam->port, PARPORT_CONTROL_SELECT|PARPORT_CONTROL_INIT); } ++cam->open_count; return 0;}static int qcamvc_close(void *privdata){ struct pp_cam_entry *cam = privdata; if(!cam || !cam->open_count) return 0; if (--cam->open_count == 0) { parport_release(cam->pdev); } return 0;}static int qcamvc_stream_start(void *privdata){ int ret = 0; struct pp_cam_entry *cam = privdata; if(!cam || !cam->open_count) return 0; struct parport *parport = cam->port; parport_pc_data_reverse (parport); /* Must be in PS2 mode */ return ret;}static int qcamvc_stream_stop(void *privdata){ int ret = 0; return ret;}static size_t qcamvc_stream_read(void *privdata, void *buffer, size_t len){ struct pp_cam_entry *cam = privdata; if(!cam || !cam->open_count) return 0; struct parport *parport = cam->port; //unsigned char a = 0x0; /*if(parport_negotiate(parport, IEEE1284_DATA | ppmode) != 0) { printk(KERN_ERR "QuickCamVC: qcamvc_stream_read1: ieee1284 failed negotiation\n"); return -1; } parport_write(parport, (void *) &a, 1);*/ #ifdef QCAMVC_PARPORT_DMA if (parport->dma != PARPORT_DMA_NONE) { int read_bytes = 0, chunks = 0; size_t chunksize = len; u8 *buf = buffer, *buf_end = buffer + len; size_t datasize, bufsize; while (read_bytes < len) {#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) if (current->need_resched) schedule ();#else cond_resched();#endif bufsize = chunksize; if(bufsize > (size_t)(buf_end - buf)) bufsize = (size_t)(buf_end - buf); datasize = qcamvc_ecp_read_block_dma(parport, buf, bufsize, 0); if (datasize <= 0) { //printk("datasize <= 0\n"); break; } chunks++; buf += datasize; read_bytes += (int) datasize; } return read_bytes; }#endif unsigned long counter; int read, read_bytes = 0; for (counter=0;counter<len;counter++) {#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) if (current->need_resched) schedule ();#else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -