Commit 6406f3a9 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge kroah.com:/home/linux/linux/BK/bleeding-2.5

into kroah.com:/home/linux/linux/BK/gregkh-2.5
parents 828bb5f4 b6f5eb6a
...@@ -255,6 +255,15 @@ MODULE PARAMETERS: ...@@ -255,6 +255,15 @@ MODULE PARAMETERS:
might be necessary if your camera has a custom lens assembly. This has might be necessary if your camera has a custom lens assembly. This has
no effect with video capture devices. no effect with video capture devices.
NAME: ov518_color
TYPE: integer (Boolean)
DEFAULT: 0 (off)
DESC: Enable OV518 color support. This is off by default since it doesn't
work most of the time. If you want to try it, you must also load
ov518_decomp with the "nouv=0" parameter. If you get improper colors or
diagonal lines through the image, restart your video app and try again.
Repeat as necessary.
WORKING FEATURES: WORKING FEATURES:
o Color streaming/capture at most widths and heights that are multiples of 8. o Color streaming/capture at most widths and heights that are multiples of 8.
o Monochrome (use force_palette=1 to enable) o Monochrome (use force_palette=1 to enable)
......
...@@ -94,10 +94,11 @@ HandSpring Visor, Palm USB, and Cli ...@@ -94,10 +94,11 @@ HandSpring Visor, Palm USB, and Cli
Compaq iPAQ, HP Jornada and Casio EM500 driver Compaq iPAQ, HP Jornada and Casio EM500 driver
This driver can be used to connect to Compaq iPAQ, HP Jornada and Casio EM500 This driver can be used to connect to Compaq iPAQ, HP Jornada and Casio EM500
PDAs running Windows CE 3.0 or PocketPC 2002 using a USB cable/cradle. It PDAs running Windows CE 3.0 or PocketPC 2002 using a USB cable/cradle.
has been tested only on the Compaq H3135, but is rumoured to work on It's very likely that every device supported by ActiveSync USB works with this
with the H3600 and later models as well as the Jornada 548 and 568. driver. The driver supports the Compaq iPAQ, Jornada 548/568 and the Casio
With minor modifications, it may work for other CE based handhelds too. EM500 out of the box. For others, please use module parameters to specify
the product and vendor id. e.g. modprobe ipaq vendor=0x3f0 product=0x1125
The driver presents a serial interface (usually on /dev/ttyUSB0) over The driver presents a serial interface (usually on /dev/ttyUSB0) over
which one may run ppp and establish a TCP/IP link to the iPAQ. Once this which one may run ppp and establish a TCP/IP link to the iPAQ. Once this
...@@ -105,36 +106,12 @@ Compaq iPAQ, HP Jornada and Casio EM500 driver ...@@ -105,36 +106,12 @@ Compaq iPAQ, HP Jornada and Casio EM500 driver
significant advantage of using USB is speed - you can get 73 to 113 significant advantage of using USB is speed - you can get 73 to 113
kbytes/sec for download/upload to the iPAQ. kbytes/sec for download/upload to the iPAQ.
The driver works intermittently with the usb-uhci driver but quite This driver is only one of a set of components required to utilize
reliably with the uhci driver. However, performance is much better the USB connection. Please visit http://synce.sourceforge.net which
with usb-uhci. It does not seem to work with ohci at all. contains the necessary packages and a simple step-by-step howto.
You must setup hotplug to invoke pppd as soon as the iPAQ is connected. Once connected, you can use Win CE programs like ftpView, Pocket Outlook
A ppp script like the one below should be kept in the file from the iPAQ and xcerdisp, synce utilities from the Linux side.
/etc/hotplug/usb/ipaq Remember to chmod +x. Make sure there are no
options in /etc/ppp/options or ~/.ppprc which conflict with the ones
given below.
#!/bin/bash
MYIP=linux.box.ip
REMOTEIP=ipaq.ip
MYDNS=my.dns.server
killall -9 pppd
/usr/sbin/pppd /dev/ttyUSB0 \
connect "/usr/sbin/chat -v TIMEOUT 60 CLIENT 'CLIENTSERVER\c'" \
nocrtscts local debug passive $MYIP:$REMOTEIP ms-dns $MYDNS noauth \
proxyarp
You must also download and install asyncd from http://synce.sourceforge.net
This is required to emulate keep-alive packets which are exchanged by
ActiveSync and the iPAQ.
On connecting the cable, you should see the usual "Device Connected",
"User Authenticated" messages flash by on your iPAQ. Once connected,
you can use Win CE programs like ftpView, Pocket Outlook from the iPAQ
and xcerdisp, synce utilities from the Linux side. Remember to enable IP
forwarding.
To use Pocket IE, follow the instructions given at To use Pocket IE, follow the instructions given at
http://www.tekguru.co.uk/EM500/usbtonet.htm to achieve the same thing http://www.tekguru.co.uk/EM500/usbtonet.htm to achieve the same thing
......
...@@ -34,7 +34,7 @@ static inline int dev_hotplug(struct device *dev, const char *action) ...@@ -34,7 +34,7 @@ static inline int dev_hotplug(struct device *dev, const char *action)
{ {
return 0; return 0;
} }
static int class_hotplug(struct device *dev, const char *action) static inline int class_hotplug(struct device *dev, const char *action)
{ {
return 0; return 0;
} }
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
#define BUFFER_SIZE 1024 /* should be enough memory for the env */ #define BUFFER_SIZE 1024 /* should be enough memory for the env */
#define NUM_ENVP 32 /* number of env pointers */ #define NUM_ENVP 32 /* number of env pointers */
static char prefix [] = "devices"; /* /sys/devices/... */
static int do_hotplug (struct device *dev, char *argv1, const char *action, static int do_hotplug (struct device *dev, char *argv1, const char *action,
int (* hotplug) (struct device *, char **, int, char *, int)) int (* hotplug) (struct device *, char **, int, char *, int))
{ {
...@@ -72,7 +74,7 @@ static int do_hotplug (struct device *dev, char *argv1, const char *action, ...@@ -72,7 +74,7 @@ static int do_hotplug (struct device *dev, char *argv1, const char *action,
} }
dev_length = get_devpath_length (dev); dev_length = get_devpath_length (dev);
dev_length += strlen("root"); dev_length += strlen(prefix);
dev_path = kmalloc (dev_length, GFP_KERNEL); dev_path = kmalloc (dev_length, GFP_KERNEL);
if (!dev_path) { if (!dev_path) {
kfree (buffer); kfree (buffer);
...@@ -80,7 +82,7 @@ static int do_hotplug (struct device *dev, char *argv1, const char *action, ...@@ -80,7 +82,7 @@ static int do_hotplug (struct device *dev, char *argv1, const char *action,
return -ENOMEM; return -ENOMEM;
} }
memset (dev_path, 0x00, dev_length); memset (dev_path, 0x00, dev_length);
strcpy (dev_path, "root"); strcpy (dev_path, prefix);
fill_devpath (dev, dev_path, dev_length); fill_devpath (dev, dev_path, dev_length);
argv [0] = hotplug_path; argv [0] = hotplug_path;
......
...@@ -38,18 +38,6 @@ config USB_DEVICEFS ...@@ -38,18 +38,6 @@ config USB_DEVICEFS
Most users want to say Y here. Most users want to say Y here.
config USB_LONG_TIMEOUT
bool "Long timeout for slow-responding devices (some MGE Ellipse UPSes)"
depends on USB
help
This option makes the standard time out a bit longer. Basically,
some devices are just slow to respond, so this makes usb more
patient. There should be no harm in selecting this, but it is
needed for some MGE Ellipse UPSes.
If you have an MGE Ellipse UPS, or you see timeouts in HID
transactions, say Y; otherwise say N.
config USB_BANDWIDTH config USB_BANDWIDTH
bool "Enforce USB bandwidth allocation (EXPERIMENTAL)" bool "Enforce USB bandwidth allocation (EXPERIMENTAL)"
depends on USB && EXPERIMENTAL depends on USB && EXPERIMENTAL
......
...@@ -53,6 +53,7 @@ struct async { ...@@ -53,6 +53,7 @@ struct async {
struct dev_state *ps; struct dev_state *ps;
struct task_struct *task; struct task_struct *task;
unsigned int signr; unsigned int signr;
unsigned int intf;
void *userbuffer; void *userbuffer;
void *userurb; void *userurb;
struct urb *urb; struct urb *urb;
...@@ -273,23 +274,47 @@ static void async_completed(struct urb *urb) ...@@ -273,23 +274,47 @@ static void async_completed(struct urb *urb)
} }
} }
static void destroy_all_async(struct dev_state *ps) static void destroy_async (struct dev_state *ps, struct list_head *list)
{ {
struct async *as; struct async *as;
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&ps->lock, flags); spin_lock_irqsave(&ps->lock, flags);
while (!list_empty(&ps->async_pending)) { while (!list_empty(list)) {
as = list_entry(ps->async_pending.next, struct async, asynclist); as = list_entry(list->next, struct async, asynclist);
list_del_init(&as->asynclist); list_del_init(&as->asynclist);
spin_unlock_irqrestore(&ps->lock, flags); spin_unlock_irqrestore(&ps->lock, flags);
/* usb_unlink_urb calls the completion handler with status == -ENOENT */ /* usb_unlink_urb calls the completion handler with status == -ENOENT */
usb_unlink_urb(as->urb); usb_unlink_urb(as->urb);
spin_lock_irqsave(&ps->lock, flags); spin_lock_irqsave(&ps->lock, flags);
} }
spin_unlock_irqrestore(&ps->lock, flags); spin_unlock_irqrestore(&ps->lock, flags);
while ((as = async_getcompleted(ps))) while ((as = async_getcompleted(ps)))
free_async(as); free_async(as);
}
static void destroy_async_on_interface (struct dev_state *ps, unsigned int intf)
{
struct async *as;
struct list_head *p, hitlist;
unsigned long flags;
INIT_LIST_HEAD(&hitlist);
spin_lock_irqsave(&ps->lock, flags);
for (p = ps->async_pending.next; p != &ps->async_pending; ) {
as = list_entry(p, struct async, asynclist);
p = p->next;
if (as->intf == intf)
list_move_tail(&as->asynclist, &hitlist);
}
spin_unlock_irqrestore(&ps->lock, flags);
destroy_async(ps, &hitlist);
}
extern __inline__ void destroy_all_async(struct dev_state *ps)
{
destroy_async(ps, &ps->async_pending);
} }
/* /*
...@@ -751,7 +776,7 @@ static int proc_submiturb(struct dev_state *ps, void *arg) ...@@ -751,7 +776,7 @@ static int proc_submiturb(struct dev_state *ps, void *arg)
struct async *as; struct async *as;
struct usb_ctrlrequest *dr = NULL; struct usb_ctrlrequest *dr = NULL;
unsigned int u, totlen, isofrmlen; unsigned int u, totlen, isofrmlen;
int ret, interval = 0; int ret, interval = 0, intf = -1;
if (copy_from_user(&uurb, arg, sizeof(uurb))) if (copy_from_user(&uurb, arg, sizeof(uurb)))
return -EFAULT; return -EFAULT;
...@@ -763,9 +788,9 @@ static int proc_submiturb(struct dev_state *ps, void *arg) ...@@ -763,9 +788,9 @@ static int proc_submiturb(struct dev_state *ps, void *arg)
if (uurb.signr != 0 && (uurb.signr < SIGRTMIN || uurb.signr > SIGRTMAX)) if (uurb.signr != 0 && (uurb.signr < SIGRTMIN || uurb.signr > SIGRTMAX))
return -EINVAL; return -EINVAL;
if (!(uurb.type == USBDEVFS_URB_TYPE_CONTROL && (uurb.endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) { if (!(uurb.type == USBDEVFS_URB_TYPE_CONTROL && (uurb.endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) {
if ((ret = findintfep(ps->dev, uurb.endpoint)) < 0) if ((intf = findintfep(ps->dev, uurb.endpoint)) < 0)
return ret; return intf;
if ((ret = checkintf(ps, ret))) if ((ret = checkintf(ps, intf)))
return ret; return ret;
} }
switch(uurb.type) { switch(uurb.type) {
...@@ -889,6 +914,7 @@ static int proc_submiturb(struct dev_state *ps, void *arg) ...@@ -889,6 +914,7 @@ static int proc_submiturb(struct dev_state *ps, void *arg)
else else
as->userbuffer = NULL; as->userbuffer = NULL;
as->signr = uurb.signr; as->signr = uurb.signr;
as->intf = intf;
as->task = current; as->task = current;
if (!(uurb.endpoint & USB_DIR_IN)) { if (!(uurb.endpoint & USB_DIR_IN)) {
if (copy_from_user(as->urb->transfer_buffer, uurb.buffer, as->urb->transfer_buffer_length)) { if (copy_from_user(as->urb->transfer_buffer, uurb.buffer, as->urb->transfer_buffer_length)) {
...@@ -1035,7 +1061,10 @@ static int proc_releaseinterface(struct dev_state *ps, void *arg) ...@@ -1035,7 +1061,10 @@ static int proc_releaseinterface(struct dev_state *ps, void *arg)
return -EFAULT; return -EFAULT;
if ((ret = findintfif(ps->dev, intf)) < 0) if ((ret = findintfif(ps->dev, intf)) < 0)
return ret; return ret;
return releaseintf(ps, intf); if ((ret = releaseintf(ps, intf)) < 0)
return ret;
destroy_async_on_interface (ps, intf);
return 0;
} }
static int proc_ioctl (struct dev_state *ps, void *arg) static int proc_ioctl (struct dev_state *ps, void *arg)
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
* - data used only by the HCD ... kmalloc is fine * - data used only by the HCD ... kmalloc is fine
* - async and periodic schedules, shared by HC and HCD ... these * - async and periodic schedules, shared by HC and HCD ... these
* need to use pci_pool or pci_alloc_consistent * need to use pci_pool or pci_alloc_consistent
* - driver buffers, read/written by HC ... single shot DMA mapped * - driver buffers, read/written by HC ... the hcd glue or the
* device driver provides us with dma addresses
* *
* There's also PCI "register" data, which is memory mapped. * There's also PCI "register" data, which is memory mapped.
* No memory seen by this driver is pagable. * No memory seen by this driver is pagable.
...@@ -41,95 +42,6 @@ static void ohci_hcd_free (struct usb_hcd *hcd) ...@@ -41,95 +42,6 @@ static void ohci_hcd_free (struct usb_hcd *hcd)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* Recover a TD/ED using its collision chain */
static inline void *
dma_to_ed_td (struct hash_list_t * entry, dma_addr_t dma)
{
struct hash_t * scan = entry->head;
while (scan && scan->dma != dma)
scan = scan->next;
return scan ? scan->virt : 0;
}
static inline struct td *
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma)
{
td_dma &= TD_MASK;
return (struct td *) dma_to_ed_td(&(hc->td_hash [TD_HASH_FUNC(td_dma)]),
td_dma);
}
// FIXME: when updating the hashtables this way, mem_flags is unusable...
/* Add a hash entry for a TD/ED; return true on success */
static inline int
hash_add_ed_td (
struct hash_list_t *entry,
void *virt,
dma_addr_t dma,
int mem_flags
)
{
struct hash_t * scan;
scan = (struct hash_t *) kmalloc (sizeof *scan, mem_flags);
if (!scan)
return 0;
if (!entry->tail) {
entry->head = entry->tail = scan;
} else {
entry->tail->next = scan;
entry->tail = scan;
}
scan->virt = virt;
scan->dma = dma;
scan->next = NULL;
return 1;
}
static inline int
hash_add_td (struct ohci_hcd *hc, struct td *td, int mem_flags)
{
return hash_add_ed_td (&(hc->td_hash [TD_HASH_FUNC (td->td_dma)]),
td, td->td_dma, mem_flags);
}
static inline void
hash_free_ed_td (struct hash_list_t *entry, void *virt)
{
struct hash_t *scan, *prev;
scan = prev = entry->head;
// Find and unlink hash entry
while (scan && scan->virt != virt) {
prev = scan;
scan = scan->next;
}
if (scan) {
if (scan == entry->head) {
if (entry->head == entry->tail)
entry->head = entry->tail = NULL;
else
entry->head = scan->next;
} else if (scan == entry->tail) {
entry->tail = prev;
prev->next = NULL;
} else
prev->next = scan->next;
kfree(scan);
}
}
static inline void
hash_free_td (struct ohci_hcd *hc, struct td * td)
{
hash_free_ed_td (&(hc->td_hash[TD_HASH_FUNC(td->td_dma)]), td);
}
static int ohci_mem_init (struct ohci_hcd *ohci) static int ohci_mem_init (struct ohci_hcd *ohci)
{ {
ohci->td_cache = pci_pool_create ("ohci_td", ohci->hcd.pdev, ohci->td_cache = pci_pool_create ("ohci_td", ohci->hcd.pdev,
...@@ -161,6 +73,21 @@ static void ohci_mem_cleanup (struct ohci_hcd *ohci) ...@@ -161,6 +73,21 @@ static void ohci_mem_cleanup (struct ohci_hcd *ohci)
} }
} }
/*-------------------------------------------------------------------------*/
/* ohci "done list" processing needs this mapping */
static inline struct td *
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma)
{
struct td *td;
td_dma &= TD_MASK;
td = hc->td_hash [TD_HASH_FUNC(td_dma)];
while (td && td->td_dma != td_dma)
td = td->td_hash;
return td;
}
/* TDs ... */ /* TDs ... */
static struct td * static struct td *
td_alloc (struct ohci_hcd *hc, int mem_flags) td_alloc (struct ohci_hcd *hc, int mem_flags)
...@@ -170,12 +97,17 @@ td_alloc (struct ohci_hcd *hc, int mem_flags) ...@@ -170,12 +97,17 @@ td_alloc (struct ohci_hcd *hc, int mem_flags)
td = pci_pool_alloc (hc->td_cache, mem_flags, &dma); td = pci_pool_alloc (hc->td_cache, mem_flags, &dma);
if (td) { if (td) {
int hash;
/* in case hc fetches it, make it look dead */
memset (td, 0, sizeof *td);
td->hwNextTD = cpu_to_le32 (dma);
td->td_dma = dma; td->td_dma = dma;
/* hash it for later reverse mapping */ /* hash it for later reverse mapping */
if (!hash_add_td (hc, td, mem_flags)) { hash = TD_HASH_FUNC (dma);
pci_pool_free (hc->td_cache, td, dma); td->td_hash = hc->td_hash [hash];
return NULL; hc->td_hash [hash] = td;
}
} }
return td; return td;
} }
...@@ -183,10 +115,18 @@ td_alloc (struct ohci_hcd *hc, int mem_flags) ...@@ -183,10 +115,18 @@ td_alloc (struct ohci_hcd *hc, int mem_flags)
static void static void
td_free (struct ohci_hcd *hc, struct td *td) td_free (struct ohci_hcd *hc, struct td *td)
{ {
hash_free_td (hc, td); struct td **prev = &hc->td_hash [TD_HASH_FUNC (td->td_dma)];
while (*prev && *prev != td)
prev = &(*prev)->td_hash;
if (*prev)
*prev = td->td_hash;
else
dev_dbg (*hc->hcd.controller, "bad hash for td %p\n", td);
pci_pool_free (hc->td_cache, td, td->td_dma); pci_pool_free (hc->td_cache, td, td->td_dma);
} }
/*-------------------------------------------------------------------------*/
/* EDs ... */ /* EDs ... */
static struct ed * static struct ed *
......
...@@ -111,6 +111,7 @@ struct td { ...@@ -111,6 +111,7 @@ struct td {
/* rest are purely for the driver's use */ /* rest are purely for the driver's use */
__u8 index; __u8 index;
struct ed *ed; struct ed *ed;
struct td *td_hash; /* dma-->td hashtable */
struct td *next_dl_td; struct td *next_dl_td;
struct urb *urb; struct urb *urb;
...@@ -320,23 +321,9 @@ typedef struct urb_priv { ...@@ -320,23 +321,9 @@ typedef struct urb_priv {
#define URB_DEL 1 #define URB_DEL 1
/* Hash struct used for TD/ED hashing */
struct hash_t {
void *virt;
dma_addr_t dma;
struct hash_t *next; // chaining for collision cases
};
/* List of TD/ED hash entries */
struct hash_list_t {
struct hash_t *head;
struct hash_t *tail;
};
#define TD_HASH_SIZE 64 /* power'o'two */ #define TD_HASH_SIZE 64 /* power'o'two */
// sizeof (struct td) ~= 64 == 2^6 ...
#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 5)) % TD_HASH_SIZE) #define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE)
/* /*
...@@ -373,7 +360,7 @@ struct ohci_hcd { ...@@ -373,7 +360,7 @@ struct ohci_hcd {
*/ */
struct pci_pool *td_cache; struct pci_pool *td_cache;
struct pci_pool *ed_cache; struct pci_pool *ed_cache;
struct hash_list_t td_hash [TD_HASH_SIZE]; struct td *td_hash [TD_HASH_SIZE];
/* /*
* driver state * driver state
......
...@@ -120,15 +120,26 @@ config USB_PWC ...@@ -120,15 +120,26 @@ config USB_PWC
tristate "USB Philips Cameras" tristate "USB Philips Cameras"
depends on USB && VIDEO_DEV depends on USB && VIDEO_DEV
---help--- ---help---
Say Y or M here if you want to use one of these Philips USB webcams: Say Y or M here if you want to use one of these Philips & OEM
PCA645, PCA646, PCVC675, PCVC680, PCVC690, PCVC730, PCVC740, or webcams:
the Askey VC010. The PCA635, PCVC665 and PCVC720 are not supported * Philips PCA645, PCA646
by this driver and never will be. * Philips PCVC675, PCVC680, PCVC690
* Philips PCVC730, PCVC740, PCVC750
This driver has an optional plugin, which is distributed as a binary * Askey VC010
module only. It contains code that allow you to use higher * Logitech QuickCam Pro 3000, 4000, 'Zoom' and 'Notebook'
resolutions and framerates but may not be distributed as source. * Samsung MPC-C10, MPC-C30
But even without this plugin you can these cams for most * Creative Webcam 5
* SOTECT Afina Eye
* Visionite VCS-UC300, VCS-UM100
The PCA635, PCVC665 and PCVC720 are not supported by this driver
and never will be, but the 665 and 720 are supported by other
drivers.
This driver has an optional plugin (called PWCX), which is
distributed as a binary module only. It contains code that allow you
to use higher resolutions and framerates but may not be distributed
as source. But even without this plugin you can these cams for most
applications. applications.
See <file:Documentation/usb/philips.txt> for more information and See <file:Documentation/usb/philips.txt> for more information and
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
/* /*
* Version Information * Version Information
*/ */
#define DRIVER_VERSION "v1.62 for Linux 2.5" #define DRIVER_VERSION "v1.63 for Linux 2.5"
#define EMAIL "mark@alpha.dyndns.org" #define EMAIL "mark@alpha.dyndns.org"
#define DRIVER_AUTHOR "Mark McClelland <mark@alpha.dyndns.org> & Bret Wallach \ #define DRIVER_AUTHOR "Mark McClelland <mark@alpha.dyndns.org> & Bret Wallach \
& Orion Sky Lawlor <olawlor@acm.org> & Kevin Moore & Charl P. Botha \ & Orion Sky Lawlor <olawlor@acm.org> & Kevin Moore & Charl P. Botha \
...@@ -121,6 +121,7 @@ static int backlight; ...@@ -121,6 +121,7 @@ static int backlight;
static int unit_video[OV511_MAX_UNIT_VIDEO]; static int unit_video[OV511_MAX_UNIT_VIDEO];
static int remove_zeros; static int remove_zeros;
static int mirror; static int mirror;
static int ov518_color;
MODULE_PARM(autobright, "i"); MODULE_PARM(autobright, "i");
MODULE_PARM_DESC(autobright, "Sensor automatically changes brightness"); MODULE_PARM_DESC(autobright, "Sensor automatically changes brightness");
...@@ -193,6 +194,8 @@ MODULE_PARM_DESC(remove_zeros, ...@@ -193,6 +194,8 @@ MODULE_PARM_DESC(remove_zeros,
"Remove zero-padding from uncompressed incoming data"); "Remove zero-padding from uncompressed incoming data");
MODULE_PARM(mirror, "i"); MODULE_PARM(mirror, "i");
MODULE_PARM_DESC(mirror, "Reverse image horizontally"); MODULE_PARM_DESC(mirror, "Reverse image horizontally");
MODULE_PARM(ov518_color, "i");
MODULE_PARM_DESC(ov518_color, "Enable OV518 color (experimental)");
MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC); MODULE_DESCRIPTION(DRIVER_DESC);
...@@ -259,6 +262,7 @@ static struct symbolic_list camlist[] = { ...@@ -259,6 +262,7 @@ static struct symbolic_list camlist[] = {
{ 100, "Lifeview RoboCam" }, { 100, "Lifeview RoboCam" },
{ 102, "AverMedia InterCam Elite" }, { 102, "AverMedia InterCam Elite" },
{ 112, "MediaForte MV300" }, /* or OV7110 evaluation kit */ { 112, "MediaForte MV300" }, /* or OV7110 evaluation kit */
{ 134, "Ezonics EZCam II" },
{ 192, "Webeye 2000B" }, { 192, "Webeye 2000B" },
{ 253, "Alpha Vision Tech. AlphaCam SE" }, { 253, "Alpha Vision Tech. AlphaCam SE" },
{ -1, NULL } { -1, NULL }
...@@ -293,6 +297,7 @@ static struct symbolic_list brglist[] = { ...@@ -293,6 +297,7 @@ static struct symbolic_list brglist[] = {
{ -1, NULL } { -1, NULL }
}; };
#if defined(CONFIG_VIDEO_PROC_FS)
static struct symbolic_list senlist[] = { static struct symbolic_list senlist[] = {
{ SEN_OV76BE, "OV76BE" }, { SEN_OV76BE, "OV76BE" },
{ SEN_OV7610, "OV7610" }, { SEN_OV7610, "OV7610" },
...@@ -308,6 +313,7 @@ static struct symbolic_list senlist[] = { ...@@ -308,6 +313,7 @@ static struct symbolic_list senlist[] = {
{ SEN_SAA7111A, "SAA7111A" }, { SEN_SAA7111A, "SAA7111A" },
{ -1, NULL } { -1, NULL }
}; };
#endif
/* URB error codes: */ /* URB error codes: */
static struct symbolic_list urb_errlist[] = { static struct symbolic_list urb_errlist[] = {
...@@ -320,20 +326,6 @@ static struct symbolic_list urb_errlist[] = { ...@@ -320,20 +326,6 @@ static struct symbolic_list urb_errlist[] = {
{ -1, NULL } { -1, NULL }
}; };
/**********************************************************************
* Prototypes
**********************************************************************/
static void ov51x_clear_snapshot(struct usb_ov511 *);
static inline int sensor_get_picture(struct usb_ov511 *,
struct video_picture *);
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
static int sensor_get_exposure(struct usb_ov511 *, unsigned char *);
static int ov51x_control_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static int ov51x_check_snapshot(struct usb_ov511 *);
#endif
/********************************************************************** /**********************************************************************
* Memory management * Memory management
**********************************************************************/ **********************************************************************/
...@@ -396,11 +388,19 @@ rvfree(void *mem, unsigned long size) ...@@ -396,11 +388,19 @@ rvfree(void *mem, unsigned long size)
* Based on the CPiA driver version 0.7.4 -claudio * Based on the CPiA driver version 0.7.4 -claudio
**********************************************************************/ **********************************************************************/
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) #if defined(CONFIG_VIDEO_PROC_FS)
static struct proc_dir_entry *ov511_proc_entry = NULL; static struct proc_dir_entry *ov511_proc_entry = NULL;
extern struct proc_dir_entry *video_proc_entry; extern struct proc_dir_entry *video_proc_entry;
/* Prototypes */
static void ov51x_clear_snapshot(struct usb_ov511 *);
static int sensor_get_picture(struct usb_ov511 *, struct video_picture *);
static int sensor_get_exposure(struct usb_ov511 *, unsigned char *);
static int ov51x_check_snapshot(struct usb_ov511 *);
static int ov51x_control_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static struct file_operations ov511_control_fops = { static struct file_operations ov511_control_fops = {
.ioctl = ov51x_control_ioctl, .ioctl = ov51x_control_ioctl,
}; };
...@@ -460,6 +460,8 @@ ov511_read_proc_info(char *page, char **start, off_t off, int count, int *eof, ...@@ -460,6 +460,8 @@ ov511_read_proc_info(char *page, char **start, off_t off, int count, int *eof,
symbolic(senlist, ov->sensor)); symbolic(senlist, ov->sensor));
out += sprintf(out, "packet_size : %d\n", ov->packet_size); out += sprintf(out, "packet_size : %d\n", ov->packet_size);
out += sprintf(out, "framebuffer : 0x%p\n", ov->fbuf); out += sprintf(out, "framebuffer : 0x%p\n", ov->fbuf);
out += sprintf(out, "packet_numbering: %d\n", ov->packet_numbering);
out += sprintf(out, "topology : %s\n", ov->usb_path);
len = out - page; len = out - page;
len -= off; len -= off;
...@@ -637,7 +639,12 @@ proc_ov511_destroy(void) ...@@ -637,7 +639,12 @@ proc_ov511_destroy(void)
remove_proc_entry("ov511", video_proc_entry); remove_proc_entry("ov511", video_proc_entry);
} }
#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */ #else
static inline void create_proc_ov511_cam(struct usb_ov511 *ov) { }
static inline void destroy_proc_ov511_cam(struct usb_ov511 *ov) { }
static inline void proc_ov511_create(void) { }
static inline void proc_ov511_destroy(void) { }
#endif /* #ifdef CONFIG_VIDEO_PROC_FS */
/********************************************************************** /**********************************************************************
* *
...@@ -1116,7 +1123,7 @@ i2c_w_mask(struct usb_ov511 *ov, ...@@ -1116,7 +1123,7 @@ i2c_w_mask(struct usb_ov511 *ov,
* when calling this. This should not be called from outside the i2c I/O * when calling this. This should not be called from outside the i2c I/O
* functions. * functions.
*/ */
static inline int static int
i2c_set_slave_internal(struct usb_ov511 *ov, unsigned char slave) i2c_set_slave_internal(struct usb_ov511 *ov, unsigned char slave)
{ {
int rc; int rc;
...@@ -1357,7 +1364,7 @@ ov51x_clear_snapshot(struct usb_ov511 *ov) ...@@ -1357,7 +1364,7 @@ ov51x_clear_snapshot(struct usb_ov511 *ov)
} }
} }
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) #if defined(CONFIG_VIDEO_PROC_FS)
/* Checks the status of the snapshot button. Returns 1 if it was pressed since /* Checks the status of the snapshot button. Returns 1 if it was pressed since
* it was last cleared, and zero in all other cases (including errors) */ * it was last cleared, and zero in all other cases (including errors) */
static int static int
...@@ -1970,7 +1977,7 @@ sensor_get_hue(struct usb_ov511 *ov, unsigned short *val) ...@@ -1970,7 +1977,7 @@ sensor_get_hue(struct usb_ov511 *ov, unsigned short *val)
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
static inline int static int
sensor_set_picture(struct usb_ov511 *ov, struct video_picture *p) sensor_set_picture(struct usb_ov511 *ov, struct video_picture *p)
{ {
int rc; int rc;
...@@ -2001,7 +2008,7 @@ sensor_set_picture(struct usb_ov511 *ov, struct video_picture *p) ...@@ -2001,7 +2008,7 @@ sensor_set_picture(struct usb_ov511 *ov, struct video_picture *p)
return 0; return 0;
} }
static inline int static int
sensor_get_picture(struct usb_ov511 *ov, struct video_picture *p) sensor_get_picture(struct usb_ov511 *ov, struct video_picture *p)
{ {
int rc; int rc;
...@@ -2032,7 +2039,7 @@ sensor_get_picture(struct usb_ov511 *ov, struct video_picture *p) ...@@ -2032,7 +2039,7 @@ sensor_get_picture(struct usb_ov511 *ov, struct video_picture *p)
return 0; return 0;
} }
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) #if defined(CONFIG_VIDEO_PROC_FS)
// FIXME: Exposure range is only 0x00-0x7f in interlace mode // FIXME: Exposure range is only 0x00-0x7f in interlace mode
/* Sets current exposure for sensor. This only has an effect if auto-exposure /* Sets current exposure for sensor. This only has an effect if auto-exposure
* is off */ * is off */
...@@ -2117,7 +2124,7 @@ sensor_get_exposure(struct usb_ov511 *ov, unsigned char *val) ...@@ -2117,7 +2124,7 @@ sensor_get_exposure(struct usb_ov511 *ov, unsigned char *val)
#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */ #endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */
/* Turns on or off the LED. Only has an effect with OV511+/OV518(+) */ /* Turns on or off the LED. Only has an effect with OV511+/OV518(+) */
static inline void static void
ov51x_led_control(struct usb_ov511 *ov, int enable) ov51x_led_control(struct usb_ov511 *ov, int enable)
{ {
PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); PDEBUG(4, " (%s)", enable ? "turn on" : "turn off");
...@@ -2198,7 +2205,7 @@ sensor_set_light_freq(struct usb_ov511 *ov, int freq) ...@@ -2198,7 +2205,7 @@ sensor_set_light_freq(struct usb_ov511 *ov, int freq)
* Unsupported: KS0127, KS0127B, SAA7111A * Unsupported: KS0127, KS0127B, SAA7111A
* Returns: 0 for success * Returns: 0 for success
*/ */
static inline int static int
sensor_set_banding_filter(struct usb_ov511 *ov, int enable) sensor_set_banding_filter(struct usb_ov511 *ov, int enable)
{ {
int rc; int rc;
...@@ -2226,7 +2233,7 @@ sensor_set_banding_filter(struct usb_ov511 *ov, int enable) ...@@ -2226,7 +2233,7 @@ sensor_set_banding_filter(struct usb_ov511 *ov, int enable)
* Unsupported: KS0127, KS0127B, SAA7111A * Unsupported: KS0127, KS0127B, SAA7111A
* Returns: 0 for success * Returns: 0 for success
*/ */
static inline int static int
sensor_set_auto_brightness(struct usb_ov511 *ov, int enable) sensor_set_auto_brightness(struct usb_ov511 *ov, int enable)
{ {
int rc; int rc;
...@@ -2254,7 +2261,7 @@ sensor_set_auto_brightness(struct usb_ov511 *ov, int enable) ...@@ -2254,7 +2261,7 @@ sensor_set_auto_brightness(struct usb_ov511 *ov, int enable)
* Unsupported: KS0127, KS0127B, SAA7111A * Unsupported: KS0127, KS0127B, SAA7111A
* Returns: 0 for success * Returns: 0 for success
*/ */
static inline int static int
sensor_set_auto_exposure(struct usb_ov511 *ov, int enable) sensor_set_auto_exposure(struct usb_ov511 *ov, int enable)
{ {
PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); PDEBUG(4, " (%s)", enable ? "turn on" : "turn off");
...@@ -2333,7 +2340,7 @@ sensor_set_backlight(struct usb_ov511 *ov, int enable) ...@@ -2333,7 +2340,7 @@ sensor_set_backlight(struct usb_ov511 *ov, int enable)
return 0; return 0;
} }
static inline int static int
sensor_set_mirror(struct usb_ov511 *ov, int enable) sensor_set_mirror(struct usb_ov511 *ov, int enable)
{ {
PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); PDEBUG(4, " (%s)", enable ? "turn on" : "turn off");
...@@ -2434,7 +2441,7 @@ mode_init_ov_sensor_regs(struct usb_ov511 *ov, int width, int height, ...@@ -2434,7 +2441,7 @@ mode_init_ov_sensor_regs(struct usb_ov511 *ov, int width, int height,
i2c_w(ov, 0x14, qvga?0x24:0x04); i2c_w(ov, 0x14, qvga?0x24:0x04);
break; break;
case SEN_OV6630: case SEN_OV6630:
i2c_w(ov, 0x14, qvga?0xa4:0x84); i2c_w(ov, 0x14, qvga?0xa0:0x80);
break; break;
default: default:
err("Invalid sensor"); err("Invalid sensor");
...@@ -2448,13 +2455,33 @@ mode_init_ov_sensor_regs(struct usb_ov511 *ov, int width, int height, ...@@ -2448,13 +2455,33 @@ mode_init_ov_sensor_regs(struct usb_ov511 *ov, int width, int height,
/* these aren't valid on the OV6620/OV7620/6630? */ /* these aren't valid on the OV6620/OV7620/6630? */
i2c_w_mask(ov, 0x0e, 0x40, 0x40); i2c_w_mask(ov, 0x0e, 0x40, 0x40);
} }
i2c_w_mask(ov, 0x13, 0x20, 0x20);
if (ov->sensor == SEN_OV6630 && ov->bridge == BRG_OV518
&& ov518_color) {
i2c_w_mask(ov, 0x12, 0x00, 0x10);
i2c_w_mask(ov, 0x13, 0x00, 0x20);
} else {
i2c_w_mask(ov, 0x13, 0x20, 0x20);
}
} else { } else {
if (ov->sensor == SEN_OV7610 || ov->sensor == SEN_OV76BE) { if (ov->sensor == SEN_OV7610 || ov->sensor == SEN_OV76BE) {
/* not valid on the OV6620/OV7620/6630? */ /* not valid on the OV6620/OV7620/6630? */
i2c_w_mask(ov, 0x0e, 0x00, 0x40); i2c_w_mask(ov, 0x0e, 0x00, 0x40);
} }
i2c_w_mask(ov, 0x13, 0x00, 0x20);
/* The OV518 needs special treatment. Although both the OV518
* and the OV6630 support a 16-bit video bus, only the 8 bit Y
* bus is actually used. The UV bus is tied to ground.
* Therefore, the OV6630 needs to be in 8-bit multiplexed
* output mode */
if (ov->sensor == SEN_OV6630 && ov->bridge == BRG_OV518
&& ov518_color) {
i2c_w_mask(ov, 0x12, 0x10, 0x10);
i2c_w_mask(ov, 0x13, 0x20, 0x20);
} else {
i2c_w_mask(ov, 0x13, 0x00, 0x20);
}
} }
/******** Clock programming ********/ /******** Clock programming ********/
...@@ -2781,8 +2808,29 @@ ov518_mode_init_regs(struct usb_ov511 *ov, ...@@ -2781,8 +2808,29 @@ ov518_mode_init_regs(struct usb_ov511 *ov,
reg_w(ov, 0x3d, 0); reg_w(ov, 0x3d, 0);
reg_w(ov, 0x3e, 0); reg_w(ov, 0x3e, 0);
reg_w(ov, 0x28, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80); if (ov->bridge == BRG_OV518 && ov518_color) {
reg_w(ov, 0x38, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80); /* OV518 needs U and V swapped */
i2c_w_mask(ov, 0x15, 0x00, 0x01);
if (mode == VIDEO_PALETTE_GREY) {
/* Set 16-bit input format (UV data are ignored) */
reg_w_mask(ov, 0x20, 0x00, 0x08);
/* Set 8-bit (4:0:0) output format */
reg_w_mask(ov, 0x28, 0x00, 0xf0);
reg_w_mask(ov, 0x38, 0x00, 0xf0);
} else {
/* Set 8-bit (YVYU) input format */
reg_w_mask(ov, 0x20, 0x08, 0x08);
/* Set 12-bit (4:2:0) output format */
reg_w_mask(ov, 0x28, 0x80, 0xf0);
reg_w_mask(ov, 0x38, 0x80, 0xf0);
}
} else {
reg_w(ov, 0x28, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80);
reg_w(ov, 0x38, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80);
}
hsegs = width / 16; hsegs = width / 16;
vsegs = height / 4; vsegs = height / 4;
...@@ -3074,7 +3122,7 @@ make_8x8(unsigned char *pIn, unsigned char *pOut, int w) ...@@ -3074,7 +3122,7 @@ make_8x8(unsigned char *pIn, unsigned char *pOut, int w)
} }
/* /*
* For RAW BW (YUV400) images, data shows up in 256 byte segments. * For RAW BW (YUV 4:0:0) images, data show up in 256 byte segments.
* The segments represent 4 squares of 8x8 pixels as follows: * The segments represent 4 squares of 8x8 pixels as follows:
* *
* 0 1 ... 7 64 65 ... 71 ... 192 193 ... 199 * 0 1 ... 7 64 65 ... 71 ... 192 193 ... 199
...@@ -3105,7 +3153,7 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame, ...@@ -3105,7 +3153,7 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame,
} }
/* /*
* For YUV4:2:0 images, the data shows up in 384 byte segments. * For YUV 4:2:0 images, the data show up in 384 byte segments.
* The first 64 bytes of each segment are U, the next 64 are V. The U and * The first 64 bytes of each segment are U, the next 64 are V. The U and
* V are arranged as follows: * V are arranged as follows:
* *
...@@ -3124,8 +3172,8 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame, ...@@ -3124,8 +3172,8 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame,
* ... ... ... * ... ... ...
* 56 57 ... 63 120 121 ... 127 ... 248 249 ... 255 * 56 57 ... 63 120 121 ... 127 ... 248 249 ... 255
* *
* Note that the U and V data in one segment represents a 16 x 16 pixel * Note that the U and V data in one segment represent a 16 x 16 pixel
* area, but the Y data represents a 32 x 8 pixel area. If the width is not an * area, but the Y data represent a 32 x 8 pixel area. If the width is not an
* even multiple of 32, the extra 8x8 blocks within a 32x8 block belong to the * even multiple of 32, the extra 8x8 blocks within a 32x8 block belong to the
* next horizontal stripe. * next horizontal stripe.
* *
...@@ -3133,7 +3181,7 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame, ...@@ -3133,7 +3181,7 @@ yuv400raw_to_yuv400p(struct ov511_frame *frame,
* verbatim, in order, into the frame. When used with vidcat -f ppm -s 640x480 * verbatim, in order, into the frame. When used with vidcat -f ppm -s 640x480
* this puts the data on the standard output and can be analyzed with the * this puts the data on the standard output and can be analyzed with the
* parseppm.c utility I wrote. That's a much faster way for figuring out how * parseppm.c utility I wrote. That's a much faster way for figuring out how
* this data is scrambled. * these data are scrambled.
*/ */
/* Converts from raw, uncompressed segments at pIn0 to a YUV420P frame at pOut0. /* Converts from raw, uncompressed segments at pIn0 to a YUV420P frame at pOut0.
...@@ -4285,7 +4333,6 @@ ov51x_v4l1_close(struct inode *inode, struct file *file) ...@@ -4285,7 +4333,6 @@ ov51x_v4l1_close(struct inode *inode, struct file *file)
} }
file->private_data = NULL; file->private_data = NULL;
return 0; return 0;
} }
...@@ -4752,7 +4799,7 @@ ov51x_v4l1_ioctl(struct inode *inode, struct file *file, ...@@ -4752,7 +4799,7 @@ ov51x_v4l1_ioctl(struct inode *inode, struct file *file,
return rc; return rc;
} }
static inline int static int
ov51x_v4l1_read(struct file *file, char *buf, size_t cnt, loff_t *ppos) ov51x_v4l1_read(struct file *file, char *buf, size_t cnt, loff_t *ppos)
{ {
struct video_device *vdev = file->private_data; struct video_device *vdev = file->private_data;
...@@ -4882,7 +4929,7 @@ ov51x_v4l1_read(struct file *file, char *buf, size_t cnt, loff_t *ppos) ...@@ -4882,7 +4929,7 @@ ov51x_v4l1_read(struct file *file, char *buf, size_t cnt, loff_t *ppos)
PDEBUG(4, "{copy} count used=%ld, new bytes_read=%ld", PDEBUG(4, "{copy} count used=%ld, new bytes_read=%ld",
count, frame->bytes_read); count, frame->bytes_read);
/* If all data has been read... */ /* If all data have been read... */
if (frame->bytes_read if (frame->bytes_read
>= get_frame_length(frame)) { >= get_frame_length(frame)) {
frame->bytes_read = 0; frame->bytes_read = 0;
...@@ -4966,7 +5013,7 @@ static struct video_device vdev_template = { ...@@ -4966,7 +5013,7 @@ static struct video_device vdev_template = {
.fops = &ov511_fops, .fops = &ov511_fops,
}; };
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) #if defined(CONFIG_VIDEO_PROC_FS)
static int static int
ov51x_control_ioctl(struct inode *inode, struct file *file, unsigned int cmd, ov51x_control_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long ularg) unsigned long ularg)
...@@ -5453,14 +5500,12 @@ ov6xx0_configure(struct usb_ov511 *ov) ...@@ -5453,14 +5500,12 @@ ov6xx0_configure(struct usb_ov511 *ov)
{ OV511_I2C_BUS, 0x4f, 0x04 }, { OV511_I2C_BUS, 0x4f, 0x04 },
// Do 50-53 have any effect? // Do 50-53 have any effect?
// Toggle 0x12[2] off and on here? // Toggle 0x12[2] off and on here?
{ OV511_DONE_BUS, 0x0, 0x00 }, { OV511_DONE_BUS, 0x0, 0x00 }, /* END MARKER */
}; };
/* This chip is undocumented so many of these are guesses. OK=verified,
* A=Added since 6620, U=unknown function (not a 6620 reg) */
static struct ov511_regvals aRegvalsNorm6x30[] = { static struct ov511_regvals aRegvalsNorm6x30[] = {
/*OK*/ { OV511_I2C_BUS, 0x12, 0x80 }, /* reset */ /*OK*/ { OV511_I2C_BUS, 0x12, 0x80 }, /* reset */
/*00?*/ { OV511_I2C_BUS, 0x11, 0x01 }, { OV511_I2C_BUS, 0x11, 0x00 },
/*OK*/ { OV511_I2C_BUS, 0x03, 0x60 }, /*OK*/ { OV511_I2C_BUS, 0x03, 0x60 },
/*0A?*/ { OV511_I2C_BUS, 0x05, 0x7f }, /* For when autoadjust is off */ /*0A?*/ { OV511_I2C_BUS, 0x05, 0x7f }, /* For when autoadjust is off */
{ OV511_I2C_BUS, 0x07, 0xa8 }, { OV511_I2C_BUS, 0x07, 0xa8 },
...@@ -5468,16 +5513,8 @@ ov6xx0_configure(struct usb_ov511 *ov) ...@@ -5468,16 +5513,8 @@ ov6xx0_configure(struct usb_ov511 *ov)
/*OK*/ { OV511_I2C_BUS, 0x0c, 0x24 }, /*OK*/ { OV511_I2C_BUS, 0x0c, 0x24 },
/*OK*/ { OV511_I2C_BUS, 0x0d, 0x24 }, /*OK*/ { OV511_I2C_BUS, 0x0d, 0x24 },
/*A*/ { OV511_I2C_BUS, 0x0e, 0x20 }, /*A*/ { OV511_I2C_BUS, 0x0e, 0x20 },
// /*24?*/ { OV511_I2C_BUS, 0x12, 0x28 }, /* Enable AGC */
// { OV511_I2C_BUS, 0x12, 0x24 }, /* Enable AGC */
// /*A*/ { OV511_I2C_BUS, 0x13, 0x21 },
// /*A*/ { OV511_I2C_BUS, 0x13, 0x25 }, /* Tristate Y and UV busses */
// /*04?*/ { OV511_I2C_BUS, 0x14, 0x80 }, // /*04?*/ { OV511_I2C_BUS, 0x14, 0x80 },
/* 0x16: 0x06 helps frame stability with moving objects */ { OV511_I2C_BUS, 0x16, 0x03 },
/*03?*/ { OV511_I2C_BUS, 0x16, 0x06 },
// /*OK*/ { OV511_I2C_BUS, 0x20, 0x30 }, /* Aperture correction enable */ // /*OK*/ { OV511_I2C_BUS, 0x20, 0x30 }, /* Aperture correction enable */
// 21 & 22? The suggested values look wrong. Go with default // 21 & 22? The suggested values look wrong. Go with default
/*A*/ { OV511_I2C_BUS, 0x23, 0xc0 }, /*A*/ { OV511_I2C_BUS, 0x23, 0xc0 },
...@@ -5490,49 +5527,34 @@ ov6xx0_configure(struct usb_ov511 *ov) ...@@ -5490,49 +5527,34 @@ ov6xx0_configure(struct usb_ov511 *ov)
/*OK*/ { OV511_I2C_BUS, 0x2a, 0x04 }, /* Disable framerate adjust */ /*OK*/ { OV511_I2C_BUS, 0x2a, 0x04 }, /* Disable framerate adjust */
// /*OK*/ { OV511_I2C_BUS, 0x2b, 0xac }, /* Framerate; Set 2a[7] first */ // /*OK*/ { OV511_I2C_BUS, 0x2b, 0xac }, /* Framerate; Set 2a[7] first */
// /*U*/ { OV511_I2C_BUS, 0x2c, 0xa0 },
{ OV511_I2C_BUS, 0x2d, 0x99 }, { OV511_I2C_BUS, 0x2d, 0x99 },
// /*A*/ { OV511_I2C_BUS, 0x33, 0x26 }, // Reserved bits on 6620 // /*A*/ { OV511_I2C_BUS, 0x33, 0x26 }, // Reserved bits on 6620
// /*d2?*/ { OV511_I2C_BUS, 0x34, 0x03 }, /* Max A/D range */ // /*d2?*/ { OV511_I2C_BUS, 0x34, 0x03 }, /* Max A/D range */
// /*U*/ { OV511_I2C_BUS, 0x36, 0x8f }, // May not be necessary
// /*U*/ { OV511_I2C_BUS, 0x37, 0x80 }, // May not be necessary
// /*8b?*/ { OV511_I2C_BUS, 0x38, 0x83 }, // /*8b?*/ { OV511_I2C_BUS, 0x38, 0x83 },
// /*40?*/ { OV511_I2C_BUS, 0x39, 0xc0 }, // 6630 adds bit 7 // /*40?*/ { OV511_I2C_BUS, 0x39, 0xc0 }, // 6630 adds bit 7
// { OV511_I2C_BUS, 0x3c, 0x39 }, /* Enable AEC mode changing */ // { OV511_I2C_BUS, 0x3c, 0x39 }, /* Enable AEC mode changing */
// { OV511_I2C_BUS, 0x3c, 0x3c }, /* Change AEC mode */ // { OV511_I2C_BUS, 0x3c, 0x3c }, /* Change AEC mode */
// { OV511_I2C_BUS, 0x3c, 0x24 }, /* Disable AEC mode changing */ // { OV511_I2C_BUS, 0x3c, 0x24 }, /* Disable AEC mode changing */
/*OK*/ { OV511_I2C_BUS, 0x3d, 0x80 }, { OV511_I2C_BUS, 0x3d, 0x80 },
// /*A*/ { OV511_I2C_BUS, 0x3f, 0x0e }, // /*A*/ { OV511_I2C_BUS, 0x3f, 0x0e },
// /*U*/ { OV511_I2C_BUS, 0x40, 0x00 },
// /*U*/ { OV511_I2C_BUS, 0x41, 0x00 },
// /*U*/ { OV511_I2C_BUS, 0x42, 0x80 },
// /*U*/ { OV511_I2C_BUS, 0x43, 0x3f },
// /*U*/ { OV511_I2C_BUS, 0x44, 0x80 },
// /*U*/ { OV511_I2C_BUS, 0x45, 0x20 },
// /*U*/ { OV511_I2C_BUS, 0x46, 0x20 },
// /*U*/ { OV511_I2C_BUS, 0x47, 0x80 },
// /*U*/ { OV511_I2C_BUS, 0x48, 0x7f },
// /*U*/ { OV511_I2C_BUS, 0x49, 0x00 },
/* These next two registers (0x4a, 0x4b) are undocumented. They /* These next two registers (0x4a, 0x4b) are undocumented. They
* control the color balance */ * control the color balance */
// /*OK?*/ { OV511_I2C_BUS, 0x4a, 0x80 }, // Check these // /*OK?*/ { OV511_I2C_BUS, 0x4a, 0x80 }, // Check these
// /*OK?*/ { OV511_I2C_BUS, 0x4b, 0x80 }, // /*OK?*/ { OV511_I2C_BUS, 0x4b, 0x80 },
// /*U*/ { OV511_I2C_BUS, 0x4c, 0xd0 }, { OV511_I2C_BUS, 0x4d, 0x10 }, /* U = 0.563u, V = 0.714v */
/*d2?*/ { OV511_I2C_BUS, 0x4d, 0x10 }, /* This reduces noise a bit */
/*c1?*/ { OV511_I2C_BUS, 0x4e, 0x40 }, /*c1?*/ { OV511_I2C_BUS, 0x4e, 0x40 },
/*04?*/ { OV511_I2C_BUS, 0x4f, 0x07 },
// /*U*/ { OV511_I2C_BUS, 0x50, 0xff }, /* UV average mode, color killer: strongest */
/*U*/ { OV511_I2C_BUS, 0x54, 0x23 }, { OV511_I2C_BUS, 0x4f, 0x07 },
// /*U*/ { OV511_I2C_BUS, 0x55, 0xff },
// /*U*/ { OV511_I2C_BUS, 0x56, 0x12 }, { OV511_I2C_BUS, 0x54, 0x23 }, /* Max AGC gain: 18dB */
/*U*/ { OV511_I2C_BUS, 0x57, 0x81 }, { OV511_I2C_BUS, 0x57, 0x81 }, /* (default) */
// /*U*/ { OV511_I2C_BUS, 0x58, 0x75 }, { OV511_I2C_BUS, 0x59, 0x01 }, /* AGC dark current comp: +1 */
/*U*/ { OV511_I2C_BUS, 0x59, 0x01 }, { OV511_I2C_BUS, 0x5a, 0x2c }, /* (undocumented) */
/*U*/ { OV511_I2C_BUS, 0x5a, 0x2c }, { OV511_I2C_BUS, 0x5b, 0x0f }, /* AWB chrominance levels */
/*U*/ { OV511_I2C_BUS, 0x5b, 0x0f }, // { OV511_I2C_BUS, 0x5c, 0x10 },
// /*U*/ { OV511_I2C_BUS, 0x5c, 0x10 }, { OV511_DONE_BUS, 0x0, 0x00 }, /* END MARKER */
{ OV511_DONE_BUS, 0x0, 0x00 },
}; };
PDEBUG(4, "starting sensor configuration"); PDEBUG(4, "starting sensor configuration");
...@@ -5553,16 +5575,19 @@ ov6xx0_configure(struct usb_ov511 *ov) ...@@ -5553,16 +5575,19 @@ ov6xx0_configure(struct usb_ov511 *ov)
return -1; return -1;
} }
if ((rc & 3) == 0) if ((rc & 3) == 0) {
ov->sensor = SEN_OV6630; ov->sensor = SEN_OV6630;
else if ((rc & 3) == 1) info("Sensor is an OV6630");
} else if ((rc & 3) == 1) {
ov->sensor = SEN_OV6620; ov->sensor = SEN_OV6620;
else if ((rc & 3) == 2) info("Sensor is an OV6620");
} else if ((rc & 3) == 2) {
ov->sensor = SEN_OV6630; ov->sensor = SEN_OV6630;
else if ((rc & 3) == 3) info("Sensor is an OV6630AE");
} else if ((rc & 3) == 3) {
ov->sensor = SEN_OV6630; ov->sensor = SEN_OV6630;
info("Sensor is an OV6630AF");
info("Sensor is an %s", symbolic(senlist, ov->sensor)); }
/* Set sensor-specific vars */ /* Set sensor-specific vars */
ov->maxwidth = 352; ov->maxwidth = 352;
...@@ -5922,7 +5947,7 @@ ov518_configure(struct usb_ov511 *ov) ...@@ -5922,7 +5947,7 @@ ov518_configure(struct usb_ov511 *ov)
{ OV511_REG_BUS, 0x5d, 0x03 }, { OV511_REG_BUS, 0x5d, 0x03 },
{ OV511_REG_BUS, 0x24, 0x9f }, { OV511_REG_BUS, 0x24, 0x9f },
{ OV511_REG_BUS, 0x25, 0x90 }, { OV511_REG_BUS, 0x25, 0x90 },
{ OV511_REG_BUS, 0x20, 0x00 }, /* Was 0x08 */ { OV511_REG_BUS, 0x20, 0x00 },
{ OV511_REG_BUS, 0x51, 0x04 }, { OV511_REG_BUS, 0x51, 0x04 },
{ OV511_REG_BUS, 0x71, 0x19 }, { OV511_REG_BUS, 0x71, 0x19 },
{ OV511_DONE_BUS, 0x0, 0x00 }, { OV511_DONE_BUS, 0x0, 0x00 },
...@@ -5935,7 +5960,7 @@ ov518_configure(struct usb_ov511 *ov) ...@@ -5935,7 +5960,7 @@ ov518_configure(struct usb_ov511 *ov)
{ OV511_REG_BUS, 0x5d, 0x03 }, { OV511_REG_BUS, 0x5d, 0x03 },
{ OV511_REG_BUS, 0x24, 0x9f }, { OV511_REG_BUS, 0x24, 0x9f },
{ OV511_REG_BUS, 0x25, 0x90 }, { OV511_REG_BUS, 0x25, 0x90 },
{ OV511_REG_BUS, 0x20, 0x60 }, /* Was 0x08 */ { OV511_REG_BUS, 0x20, 0x60 },
{ OV511_REG_BUS, 0x51, 0x02 }, { OV511_REG_BUS, 0x51, 0x02 },
{ OV511_REG_BUS, 0x71, 0x19 }, { OV511_REG_BUS, 0x71, 0x19 },
{ OV511_REG_BUS, 0x40, 0xff }, { OV511_REG_BUS, 0x40, 0xff },
...@@ -6151,6 +6176,11 @@ ov51x_probe(struct usb_interface *intf, ...@@ -6151,6 +6176,11 @@ ov51x_probe(struct usb_interface *intf,
ov->buf_state = BUF_NOT_ALLOCATED; ov->buf_state = BUF_NOT_ALLOCATED;
if (usb_make_path(dev, ov->usb_path, OV511_USB_PATH_LEN) < 0) {
err("usb_make_path error");
goto error_dealloc;
}
/* Allocate control transfer buffer. */ /* Allocate control transfer buffer. */
/* Must be kmalloc()'ed, for DMA compatibility */ /* Must be kmalloc()'ed, for DMA compatibility */
ov->cbuf = kmalloc(OV511_CBUF_SIZE, GFP_KERNEL); ov->cbuf = kmalloc(OV511_CBUF_SIZE, GFP_KERNEL);
...@@ -6212,20 +6242,16 @@ ov51x_probe(struct usb_interface *intf, ...@@ -6212,20 +6242,16 @@ ov51x_probe(struct usb_interface *intf,
goto error; goto error;
} }
info("Device registered on minor %d", ov->vdev.minor); info("Device at %s registered to minor %d", ov->usb_path,
ov->vdev.minor);
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
create_proc_ov511_cam(ov); create_proc_ov511_cam(ov);
#endif
dev_set_drvdata (&intf->dev, ov); dev_set_drvdata (&intf->dev, ov);
return 0; return 0;
error: error:
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
/* Safe to call even if entry doesn't exist */
destroy_proc_ov511_cam(ov); destroy_proc_ov511_cam(ov);
#endif
if (ov->cbuf) { if (ov->cbuf) {
down(&ov->cbuf_lock); down(&ov->cbuf_lock);
...@@ -6274,12 +6300,9 @@ ov51x_disconnect(struct usb_interface *intf) ...@@ -6274,12 +6300,9 @@ ov51x_disconnect(struct usb_interface *intf)
wake_up_interruptible(&ov->wq); wake_up_interruptible(&ov->wq);
ov->streaming = 0; ov->streaming = 0;
ov51x_unlink_isoc(ov); ov51x_unlink_isoc(ov);
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
destroy_proc_ov511_cam(ov); destroy_proc_ov511_cam(ov);
#endif
ov->dev = NULL; ov->dev = NULL;
...@@ -6396,9 +6419,7 @@ ov511_deregister_decomp_module(int ov518, int mmx) ...@@ -6396,9 +6419,7 @@ ov511_deregister_decomp_module(int ov518, int mmx)
static int __init static int __init
usb_ov511_init(void) usb_ov511_init(void)
{ {
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
proc_ov511_create(); proc_ov511_create();
#endif
if (usb_register(&ov511_driver) < 0) if (usb_register(&ov511_driver) < 0)
return -1; return -1;
...@@ -6414,9 +6435,7 @@ usb_ov511_exit(void) ...@@ -6414,9 +6435,7 @@ usb_ov511_exit(void)
usb_deregister(&ov511_driver); usb_deregister(&ov511_driver);
info("driver deregistered"); info("driver deregistered");
#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
proc_ov511_destroy(); proc_ov511_destroy();
#endif
} }
module_init(usb_ov511_init); module_init(usb_ov511_init);
......
...@@ -253,6 +253,9 @@ ...@@ -253,6 +253,9 @@
/* Control transfers use up to 4 bytes */ /* Control transfers use up to 4 bytes */
#define OV511_CBUF_SIZE 4 #define OV511_CBUF_SIZE 4
/* Size of usb_make_path() buffer */
#define OV511_USB_PATH_LEN 64
/* Bridge types */ /* Bridge types */
enum { enum {
BRG_UNKNOWN, BRG_UNKNOWN,
...@@ -450,6 +453,7 @@ struct usb_ov511 { ...@@ -450,6 +453,7 @@ struct usb_ov511 {
int customid; int customid;
char *desc; char *desc;
unsigned char iface; unsigned char iface;
char usb_path[OV511_USB_PATH_LEN];
/* Determined by sensor type */ /* Determined by sensor type */
int maxwidth; int maxwidth;
......
...@@ -256,8 +256,10 @@ static inline int set_video_mode_Nala(struct pwc_device *pdev, int size, int fra ...@@ -256,8 +256,10 @@ static inline int set_video_mode_Nala(struct pwc_device *pdev, int size, int fra
memcpy(buf, pEntry->mode, 3); memcpy(buf, pEntry->mode, 3);
ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 3); ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 3);
if (ret < 0) if (ret < 0) {
Debug("Failed to send video command... %d\n", ret);
return ret; return ret;
}
if (pEntry->compressed && pdev->decompressor != NULL) if (pEntry->compressed && pdev->decompressor != NULL)
pdev->decompressor->init(pdev->release, buf, pdev->decompress_data); pdev->decompressor->init(pdev->release, buf, pdev->decompress_data);
...@@ -1103,12 +1105,7 @@ int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value) ...@@ -1103,12 +1105,7 @@ int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value)
buf[0] = on_value; buf[0] = on_value;
buf[1] = off_value; buf[1] = off_value;
return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), return SendControlMsg(SET_STATUS_CTL, LED_FORMATTER, 2);
SET_STATUS_CTL,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
LED_FORMATTER,
pdev->vcinterface,
&buf, 2, HZ / 2);
} }
int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value) int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value)
...@@ -1122,13 +1119,7 @@ int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value) ...@@ -1122,13 +1119,7 @@ int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value)
return 0; return 0;
} }
ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), ret = RecvControlMsg(GET_STATUS_CTL, LED_FORMATTER, 2);
GET_STATUS_CTL,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
LED_FORMATTER,
pdev->vcinterface,
&buf, 2, HZ / 2);
if (ret < 0) if (ret < 0)
return ret; return ret;
*on_value = buf[0] * 100; *on_value = buf[0] * 100;
...@@ -1279,7 +1270,6 @@ static inline int pwc_get_dynamic_noise(struct pwc_device *pdev) ...@@ -1279,7 +1270,6 @@ static inline int pwc_get_dynamic_noise(struct pwc_device *pdev)
ret = RecvControlMsg(GET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1); ret = RecvControlMsg(GET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1);
if (ret < 0) if (ret < 0)
return ret; return ret;
//Debug("pwc_get_dynamic_noise = %d\n", buf);
return buf; return buf;
} }
...@@ -1363,12 +1353,10 @@ int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg) ...@@ -1363,12 +1353,10 @@ int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg)
case VIDIOCPWCPROBE: case VIDIOCPWCPROBE:
{ {
struct pwc_probe probe; struct pwc_probe *probe = arg;
strcpy(probe.name, pdev->vdev->name); strcpy(probe->name, pdev->vdev->name);
probe.type = pdev->type; probe->type = pdev->type;
if (copy_to_user(arg, &probe, sizeof(probe)))
ret = -EFAULT;
break; break;
} }
......
/* Linux driver for Philips webcam /* Linux driver for Philips webcam
USB and Video4Linux interface part. USB and Video4Linux interface part.
(C) 1999-2001 Nemosoft Unv. (C) 1999-2002 Nemosoft Unv.
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -42,32 +42,31 @@ ...@@ -42,32 +42,31 @@
- Alistar Moire: QuickCam 3000 Pro device/product ID - Alistar Moire: QuickCam 3000 Pro device/product ID
- Tony Hoyle: Creative Labs Webcam 5 device/product ID - Tony Hoyle: Creative Labs Webcam 5 device/product ID
- Mark Burazin: solving hang in VIDIOCSYNC when camera gets unplugged - Mark Burazin: solving hang in VIDIOCSYNC when camera gets unplugged
- Jk Fang: SOTEC device/product ID - Jk Fang: SOTEC Afina Eye ID
- Xavier Roche: QuickCam Pro 4000 ID
- Jens Knudsen: QuickCam Zoom ID
- J. Debert: QuickCam for Notebooks ID
*/ */
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/poll.h> #include <linux/poll.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/wrapper.h> #include <linux/wrapper.h>
#include <linux/mm.h>
#include <asm/io.h> #include <asm/io.h>
#include "pwc.h" #include "pwc.h"
#include "pwc-ioctl.h" #include "pwc-ioctl.h"
#include "pwc-uncompress.h" #include "pwc-uncompress.h"
#if !defined(MAP_NR)
#define MAP_NR(a) virt_to_page(a)
#endif
/* Function prototypes and driver templates */ /* Function prototypes and driver templates */
/* hotplug device table support */ /* hotplug device table support */
static struct usb_device_id pwc_device_table [] = { static struct usb_device_id pwc_device_table [] = {
{ USB_DEVICE(0x0471, 0x0302) }, { USB_DEVICE(0x0471, 0x0302) }, /* Philips models */
{ USB_DEVICE(0x0471, 0x0303) }, { USB_DEVICE(0x0471, 0x0303) },
{ USB_DEVICE(0x0471, 0x0304) }, { USB_DEVICE(0x0471, 0x0304) },
{ USB_DEVICE(0x0471, 0x0307) }, { USB_DEVICE(0x0471, 0x0307) },
...@@ -76,12 +75,17 @@ static struct usb_device_id pwc_device_table [] = { ...@@ -76,12 +75,17 @@ static struct usb_device_id pwc_device_table [] = {
{ USB_DEVICE(0x0471, 0x0310) }, { USB_DEVICE(0x0471, 0x0310) },
{ USB_DEVICE(0x0471, 0x0311) }, { USB_DEVICE(0x0471, 0x0311) },
{ USB_DEVICE(0x0471, 0x0312) }, { USB_DEVICE(0x0471, 0x0312) },
{ USB_DEVICE(0x069A, 0x0001) }, { USB_DEVICE(0x069A, 0x0001) }, /* Askey */
{ USB_DEVICE(0x046D, 0x08b0) }, { USB_DEVICE(0x046D, 0x08b0) }, /* Logitech QuickCam Pro 3000 */
{ USB_DEVICE(0x055D, 0x9000) }, { USB_DEVICE(0x046D, 0x08b1) }, /* Logitech QuickCam for Notebooks */
{ USB_DEVICE(0x046D, 0x08b2) }, /* Logitech QuickCam Pro 4000 */
{ USB_DEVICE(0x046D, 0x08b3) }, /* Logitech QuickCam Zoom */
{ USB_DEVICE(0x055D, 0x9000) }, /* Samsung */
{ USB_DEVICE(0x055D, 0x9001) }, { USB_DEVICE(0x055D, 0x9001) },
{ USB_DEVICE(0x041E, 0x400C) }, { USB_DEVICE(0x041E, 0x400C) }, /* Creative Webcam 5 */
{ USB_DEVICE(0x04CC, 0x8116) }, { USB_DEVICE(0x04CC, 0x8116) }, /* Afina Eye */
{ USB_DEVICE(0x0d81, 0x1910) }, /* Visionite */
{ USB_DEVICE(0x0d81, 0x1900) },
{ } { }
}; };
MODULE_DEVICE_TABLE(usb, pwc_device_table); MODULE_DEVICE_TABLE(usb, pwc_device_table);
...@@ -106,7 +110,7 @@ static int default_fbufs = 3; /* Default number of frame buffers */ ...@@ -106,7 +110,7 @@ static int default_fbufs = 3; /* Default number of frame buffers */
static int default_mbufs = 2; /* Default number of mmap() buffers */ static int default_mbufs = 2; /* Default number of mmap() buffers */
int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX; int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX;
static int power_save = 0; static int power_save = 0;
static int led_on = 1, led_off = 0; /* defaults to LED that is on while in use */ static int led_on = 100, led_off = 0; /* defaults to LED that is on while in use */
int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */ int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */
static struct { static struct {
int type; int type;
...@@ -167,7 +171,7 @@ static struct video_device pwc_template = { ...@@ -167,7 +171,7 @@ static struct video_device pwc_template = {
succeeded. The pwc_device struct links back to both structures. succeeded. The pwc_device struct links back to both structures.
When a device is unplugged while in use it will be removed from the When a device is unplugged while in use it will be removed from the
list of known USB devices; I also de-register as a V4L device, but list of known USB devices; I also de-register it as a V4L device, but
unfortunately I can't free the memory since the struct is still in use unfortunately I can't free the memory since the struct is still in use
by the file descriptor. This free-ing is then deferend until the first by the file descriptor. This free-ing is then deferend until the first
opportunity. Crude, but it works. opportunity. Crude, but it works.
...@@ -240,7 +244,7 @@ static int pwc_allocate_buffers(struct pwc_device *pdev) ...@@ -240,7 +244,7 @@ static int pwc_allocate_buffers(struct pwc_device *pdev)
int i; int i;
void *kbuf; void *kbuf;
Trace(TRACE_MEMORY, "Entering allocate_buffers(%p).\n", pdev); Trace(TRACE_MEMORY, ">> pwc_allocate_buffers(pdev = 0x%p)\n", pdev);
if (pdev == NULL) if (pdev == NULL)
return -ENXIO; return -ENXIO;
...@@ -315,7 +319,9 @@ static int pwc_allocate_buffers(struct pwc_device *pdev) ...@@ -315,7 +319,9 @@ static int pwc_allocate_buffers(struct pwc_device *pdev)
for (; i < MAX_IMAGES; i++) for (; i < MAX_IMAGES; i++)
pdev->image_ptr[i] = NULL; pdev->image_ptr[i] = NULL;
Trace(TRACE_MEMORY, "Leaving pwc_allocate_buffers().\n"); kbuf = NULL;
Trace(TRACE_MEMORY, "<< pwc_allocate_buffers()\n");
return 0; return 0;
} }
...@@ -369,6 +375,7 @@ static void pwc_free_buffers(struct pwc_device *pdev) ...@@ -369,6 +375,7 @@ static void pwc_free_buffers(struct pwc_device *pdev)
rvfree(pdev->image_data, default_mbufs * pdev->len_per_image); rvfree(pdev->image_data, default_mbufs * pdev->len_per_image);
} }
pdev->image_data = NULL; pdev->image_data = NULL;
Trace(TRACE_MEMORY, "Leaving free_buffers().\n"); Trace(TRACE_MEMORY, "Leaving free_buffers().\n");
} }
...@@ -570,12 +577,10 @@ static inline void pwc_next_image(struct pwc_device *pdev) ...@@ -570,12 +577,10 @@ static inline void pwc_next_image(struct pwc_device *pdev)
pdev->fill_image = (pdev->fill_image + 1) % default_mbufs; pdev->fill_image = (pdev->fill_image + 1) % default_mbufs;
} }
/* 2001-10-14: The YUV420 is still there, but you can only set it from within /* 2002-10-11: YUV420P is the only palette remaining. */
a program (YUV420P being the default) */
static int pwc_set_palette(struct pwc_device *pdev, int pal) static int pwc_set_palette(struct pwc_device *pdev, int pal)
{ {
if ( pal == VIDEO_PALETTE_YUV420 if ( pal == VIDEO_PALETTE_YUV420P
|| pal == VIDEO_PALETTE_YUV420P
#if PWC_DEBUG #if PWC_DEBUG
|| pal == VIDEO_PALETTE_RAW || pal == VIDEO_PALETTE_RAW
#endif #endif
...@@ -613,7 +618,7 @@ static void pwc_isoc_handler(struct urb *urb) ...@@ -613,7 +618,7 @@ static void pwc_isoc_handler(struct urb *urb)
} }
#endif #endif
if (urb->status == -ENOENT || urb->status == -ECONNRESET) { if (urb->status == -ENOENT || urb->status == -ECONNRESET) {
Trace(TRACE_OPEN, "pwc_isoc_handler(): URB unlinked.\n"); Trace(TRACE_OPEN, "pwc_isoc_handler(): URB (%p) unlinked %ssynchronuously.\n", urb, urb->status == -ENOENT ? "" : "a");
return; return;
} }
if (urb->status != -EINPROGRESS && urb->status != 0) { if (urb->status != -EINPROGRESS && urb->status != 0) {
...@@ -686,9 +691,22 @@ static void pwc_isoc_handler(struct urb *urb) ...@@ -686,9 +691,22 @@ static void pwc_isoc_handler(struct urb *urb)
#if PWC_DEBUG #if PWC_DEBUG
Debug("Hyundai CMOS sensor bug. Dropping frame %d.\n", fbuf->sequence); Debug("Hyundai CMOS sensor bug. Dropping frame %d.\n", fbuf->sequence);
#endif #endif
pdev->drop_frames = 2; pdev->drop_frames += 2;
pdev->vframes_error++; pdev->vframes_error++;
} }
if ((ptr[0] ^ pdev->vmirror) & 0x01) {
if (ptr[0] & 0x01)
Info("Snapshot button pressed.\n");
else
Info("Snapshot button released.\n");
}
if ((ptr[0] ^ pdev->vmirror) & 0x02) {
if (ptr[0] & 0x02)
Info("Image is mirrored.\n");
else
Info("Image is normal.\n");
}
pdev->vmirror = ptr[0] & 0x03;
/* Sometimes the trailer of the 730 is still sent as a 4 byte packet /* Sometimes the trailer of the 730 is still sent as a 4 byte packet
after a short frame; this condition is filtered out specifically. A 4 byte after a short frame; this condition is filtered out specifically. A 4 byte
frame doesn't make sense anyway. frame doesn't make sense anyway.
...@@ -705,7 +723,7 @@ static void pwc_isoc_handler(struct urb *urb) ...@@ -705,7 +723,7 @@ static void pwc_isoc_handler(struct urb *urb)
/* In case we were instructed to drop the frame, do so silently. /* In case we were instructed to drop the frame, do so silently.
The buffer pointers are not updated either (but the counters are reset below). The buffer pointers are not updated either (but the counters are reset below).
*/ */
if (pdev->drop_frames) if (pdev->drop_frames > 0)
pdev->drop_frames--; pdev->drop_frames--;
else { else {
/* Check for underflow first */ /* Check for underflow first */
...@@ -741,17 +759,23 @@ static void pwc_isoc_handler(struct urb *urb) ...@@ -741,17 +759,23 @@ static void pwc_isoc_handler(struct urb *urb)
} /* .. flen < last_packet_size */ } /* .. flen < last_packet_size */
pdev->vlast_packet_size = flen; pdev->vlast_packet_size = flen;
} /* ..status == 0 */ } /* ..status == 0 */
#ifdef PWC_DEBUG #if PWC_DEBUG
/* This is normally not interesting to the user, unless you are really debugging something */ /* This is normally not interesting to the user, unless you are really debugging something */
else else {
Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst); static int iso_error = 0;
iso_error++;
if (iso_error < 20)
Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst);
}
#endif #endif
} }
if (awake) if (awake)
wake_up_interruptible(&pdev->frameq); wake_up_interruptible(&pdev->frameq);
urb->dev = pdev->udev; urb->dev = pdev->udev;
usb_submit_urb(urb, GFP_ATOMIC); i = usb_submit_urb(urb, GFP_ATOMIC);
if (i != 0)
Err("Error (%d) re-submitting urb in pwc_isoc_handler.\n", i);
} }
...@@ -762,7 +786,6 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -762,7 +786,6 @@ static int pwc_isoc_init(struct pwc_device *pdev)
int i, j, ret; int i, j, ret;
struct usb_host_interface *idesc; struct usb_host_interface *idesc;
int cur_alt;
if (pdev == NULL) if (pdev == NULL)
return -EFAULT; return -EFAULT;
...@@ -770,12 +793,11 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -770,12 +793,11 @@ static int pwc_isoc_init(struct pwc_device *pdev)
return 0; return 0;
pdev->vsync = 0; pdev->vsync = 0;
udev = pdev->udev; udev = pdev->udev;
/* Get the current alternate interface, adjust packet size */ /* Get the current alternate interface, adjust packet size */
if (!udev->actconfig) if (!udev->actconfig)
return -EFAULT; return -EFAULT;
cur_alt = udev->actconfig->interface[0].act_altsetting; idesc = &udev->actconfig->interface[0].altsetting[pdev->valternate];
idesc = &udev->actconfig->interface[0].altsetting[cur_alt];
if (!idesc) if (!idesc)
return -EFAULT; return -EFAULT;
...@@ -792,7 +814,13 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -792,7 +814,13 @@ static int pwc_isoc_init(struct pwc_device *pdev)
return -ENFILE; /* Odd error, that should be noticable */ return -ENFILE; /* Odd error, that should be noticable */
} }
/* Set alternate interface */
ret = 0; ret = 0;
Trace(TRACE_OPEN, "Setting alternate interface %d\n", pdev->valternate);
ret = usb_set_interface(pdev->udev, 0, pdev->valternate);
if (ret < 0)
return ret;
for (i = 0; i < MAX_ISO_BUFS; i++) { for (i = 0; i < MAX_ISO_BUFS; i++) {
urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL);
if (urb == NULL) { if (urb == NULL) {
...@@ -801,6 +829,7 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -801,6 +829,7 @@ static int pwc_isoc_init(struct pwc_device *pdev)
break; break;
} }
pdev->sbuf[i].urb = urb; pdev->sbuf[i].urb = urb;
Trace(TRACE_MEMORY, "Allocated URB at 0x%p\n", urb);
} }
if (ret) { if (ret) {
/* De-allocate in reverse order */ /* De-allocate in reverse order */
...@@ -812,8 +841,7 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -812,8 +841,7 @@ static int pwc_isoc_init(struct pwc_device *pdev)
} }
return ret; return ret;
} }
/* init URB structure */ /* init URB structure */
for (i = 0; i < MAX_ISO_BUFS; i++) { for (i = 0; i < MAX_ISO_BUFS; i++) {
urb = pdev->sbuf[i].urb; urb = pdev->sbuf[i].urb;
...@@ -829,7 +857,7 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -829,7 +857,7 @@ static int pwc_isoc_init(struct pwc_device *pdev)
urb->start_frame = 0; urb->start_frame = 0;
urb->number_of_packets = ISO_FRAMES_PER_DESC; urb->number_of_packets = ISO_FRAMES_PER_DESC;
for (j = 0; j < ISO_FRAMES_PER_DESC; j++) { for (j = 0; j < ISO_FRAMES_PER_DESC; j++) {
urb->iso_frame_desc[j].offset = j * pdev->vmax_packet_size; urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE;
urb->iso_frame_desc[j].length = pdev->vmax_packet_size; urb->iso_frame_desc[j].length = pdev->vmax_packet_size;
} }
} }
...@@ -840,11 +868,12 @@ static int pwc_isoc_init(struct pwc_device *pdev) ...@@ -840,11 +868,12 @@ static int pwc_isoc_init(struct pwc_device *pdev)
if (ret) if (ret)
Err("isoc_init() submit_urb %d failed with error %d\n", i, ret); Err("isoc_init() submit_urb %d failed with error %d\n", i, ret);
else else
Trace(TRACE_OPEN, "pwc_isoc_init(): URB submitted.\n"); Trace(TRACE_OPEN, "URB 0x%p submitted.\n", pdev->sbuf[i].urb);
} }
/* data should stream in now */ /* All is done... */
pdev->iso_init = 1; pdev->iso_init = 1;
Trace(TRACE_OPEN, "<< pwc_isoc_init()\n");
return 0; return 0;
} }
...@@ -852,21 +881,34 @@ static void pwc_isoc_cleanup(struct pwc_device *pdev) ...@@ -852,21 +881,34 @@ static void pwc_isoc_cleanup(struct pwc_device *pdev)
{ {
int i; int i;
Trace(TRACE_OPEN, ">> pwc_isoc_cleanup()\n");
if (pdev == NULL) if (pdev == NULL)
return; return;
if (!pdev->iso_init)
return; /* Unlinking ISOC buffers one by one */
for (i = 0; i < MAX_ISO_BUFS; i++) {
struct urb *urb;
urb = pdev->sbuf[i].urb;
if (urb != 0) {
if (pdev->iso_init) {
Trace(TRACE_MEMORY, "Unlinking URB %p\n", urb);
usb_unlink_urb(urb);
}
Trace(TRACE_MEMORY, "Freeing URB\n");
usb_free_urb(urb);
pdev->sbuf[i].urb = NULL;
}
}
/* Stop camera, but only if we are sure the camera is still there */ /* Stop camera, but only if we are sure the camera is still there */
if (!pdev->unplugged) if (!pdev->unplugged) {
Trace(TRACE_OPEN, "Setting alternate interface 0.\n");
usb_set_interface(pdev->udev, 0, 0); usb_set_interface(pdev->udev, 0, 0);
/* Unlinking ISOC buffers one by one */
for (i = MAX_ISO_BUFS - 1; i >= 0; i--) {
//pdev->sbuf[i].urb->next = NULL;
usb_unlink_urb(pdev->sbuf[i].urb);
usb_free_urb(pdev->sbuf[i].urb);
pdev->sbuf[i].urb = NULL;
} }
pdev->iso_init = 0; pdev->iso_init = 0;
Trace(TRACE_OPEN, "<< pwc_isoc_cleanup()\n");
} }
int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot) int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot)
...@@ -881,11 +923,10 @@ int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_f ...@@ -881,11 +923,10 @@ int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_f
ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot); ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot);
if (ret) /* That failed... restore old mode (we know that worked) */ if (ret) /* That failed... restore old mode (we know that worked) */
ret = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); ret = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot);
else /* Set (new) alternate interface */
ret = usb_set_interface(pdev->udev, 0, pdev->valternate);
if (!ret) if (!ret)
ret = pwc_isoc_init(pdev); if (pwc_isoc_init(pdev) < 0)
pdev->drop_frames = 1; /* try to avoid garbage during switch */ Info("Failed to restart ISOC transfer in pwc_try_video_mode.\n");
pdev->drop_frames++; /* try to avoid garbage during switch */
return ret; return ret;
} }
...@@ -921,7 +962,7 @@ static int pwc_video_open(struct inode *inode, struct file *file) ...@@ -921,7 +962,7 @@ static int pwc_video_open(struct inode *inode, struct file *file)
struct video_device *vdev = video_devdata(file); struct video_device *vdev = video_devdata(file);
struct pwc_device *pdev; struct pwc_device *pdev;
Trace(TRACE_OPEN, "video_open called(0x%p).\n", vdev); Trace(TRACE_OPEN, ">> video_open called(vdev = 0x%p).\n", vdev);
pdev = (struct pwc_device *)vdev->priv; pdev = (struct pwc_device *)vdev->priv;
if (pdev == NULL) if (pdev == NULL)
...@@ -932,10 +973,30 @@ static int pwc_video_open(struct inode *inode, struct file *file) ...@@ -932,10 +973,30 @@ static int pwc_video_open(struct inode *inode, struct file *file)
down(&pdev->modlock); down(&pdev->modlock);
if (!pdev->usb_init) { if (!pdev->usb_init) {
Trace(TRACE_OPEN, "Doing first time initialization.\n"); Trace(TRACE_OPEN, "Doing first time initialization.\n");
/* Reset camera */
if (usb_set_interface(pdev->udev, 0, 0))
Info("Failed to set alternate interface to 0.\n");
pdev->usb_init = 1; pdev->usb_init = 1;
if (pwc_trace & TRACE_OPEN) {
/* Query CMOS sensor type */
const char *sensor_type = NULL;
i = pwc_get_cmos_sensor(pdev);
switch(i) {
case -1: /* Unknown, show nothing */; break;
case 0x00: sensor_type = "Hyundai CMOS sensor"; break;
case 0x20: sensor_type = "Sony CCD sensor + TDA8787"; break;
case 0x2E: sensor_type = "Sony CCD sensor + Exas 98L59"; break;
case 0x2F: sensor_type = "Sony CCD sensor + ADI 9804"; break;
case 0x30: sensor_type = "Sharp CCD sensor + TDA8787"; break;
case 0x3E: sensor_type = "Sharp CCD sensor + Exas 98L59"; break;
case 0x3F: sensor_type = "Sharp CCD sensor + ADI 9804"; break;
case 0x40: sensor_type = "UPA 1021 sensor"; break;
case 0x100: sensor_type = "VGA sensor"; break;
case 0x101: sensor_type = "PAL MR sensor"; break;
default: sensor_type = "unknown type of sensor"; break;
}
if (sensor_type != NULL)
Info("This %s camera is equipped with a %s (%d).\n", pdev->vdev->name, sensor_type, i);
}
} }
/* Turn on camera */ /* Turn on camera */
...@@ -1000,12 +1061,6 @@ static int pwc_video_open(struct inode *inode, struct file *file) ...@@ -1000,12 +1061,6 @@ static int pwc_video_open(struct inode *inode, struct file *file)
return i; return i;
} }
i = usb_set_interface(pdev->udev, 0, pdev->valternate);
if (i) {
Trace(TRACE_OPEN, "Failed to set alternate interface = %d.\n", i);
up(&pdev->modlock);
return -EINVAL;
}
i = pwc_isoc_init(pdev); i = pwc_isoc_init(pdev);
if (i) { if (i) {
Trace(TRACE_OPEN, "Failed to init ISOC stuff = %d.\n", i); Trace(TRACE_OPEN, "Failed to init ISOC stuff = %d.\n", i);
...@@ -1023,7 +1078,7 @@ static int pwc_video_open(struct inode *inode, struct file *file) ...@@ -1023,7 +1078,7 @@ static int pwc_video_open(struct inode *inode, struct file *file)
if (pdev->decompressor != NULL) if (pdev->decompressor != NULL)
pdev->decompressor->lock(); pdev->decompressor->lock();
up(&pdev->modlock); up(&pdev->modlock);
Trace(TRACE_OPEN, "video_open() returning 0.\n"); Trace(TRACE_OPEN, "<< video_open() returns 0.\n");
return 0; return 0;
} }
...@@ -1034,15 +1089,12 @@ static int pwc_video_close(struct inode *inode, struct file *file) ...@@ -1034,15 +1089,12 @@ static int pwc_video_close(struct inode *inode, struct file *file)
struct pwc_device *pdev; struct pwc_device *pdev;
int i; int i;
Trace(TRACE_OPEN, "video_close called(0x%p).\n", vdev); Trace(TRACE_OPEN, ">> video_close called(vdev = 0x%p).\n", vdev);
pdev = (struct pwc_device *)vdev->priv; pdev = (struct pwc_device *)vdev->priv;
if (pdev->vopen == 0) if (pdev->vopen == 0)
Info("video_close() called on closed device?\n"); Info("video_close() called on closed device?\n");
/* Free isoc URBs */
pwc_isoc_cleanup(pdev);
/* Dump statistics, but only if a reasonable amount of frames were /* Dump statistics, but only if a reasonable amount of frames were
processed (to prevent endless log-entries in case of snap-shot processed (to prevent endless log-entries in case of snap-shot
programs) programs)
...@@ -1050,15 +1102,14 @@ static int pwc_video_close(struct inode *inode, struct file *file) ...@@ -1050,15 +1102,14 @@ static int pwc_video_close(struct inode *inode, struct file *file)
if (pdev->vframe_count > 20) if (pdev->vframe_count > 20)
Info("Closing video device: %d frames received, dumped %d frames, %d frames with errors.\n", pdev->vframe_count, pdev->vframes_dumped, pdev->vframes_error); Info("Closing video device: %d frames received, dumped %d frames, %d frames with errors.\n", pdev->vframe_count, pdev->vframes_dumped, pdev->vframes_error);
if (!pdev->unplugged) { /* Free isoc URBs, stop camera */
/* Normal close: stop isochronuous and interrupt endpoint */ pwc_isoc_cleanup(pdev);
Trace(TRACE_OPEN, "Normal close(): setting interface to 0.\n");
usb_set_interface(pdev->udev, 0, 0);
if (!pdev->unplugged) {
/* Turn LEDs off */ /* Turn LEDs off */
if (pwc_set_leds(pdev, 0, 0) < 0) if (pwc_set_leds(pdev, 0, 0) < 0)
Info("Failed to set LED on/off time..\n"); Info("Failed to set LED on/off time.\n");
/* Power down camere to save energy */ /* Power down camera to save energy */
if (power_save) { if (power_save) {
i = pwc_camera_power(pdev, 0); i = pwc_camera_power(pdev, 0);
if (i < 0) if (i < 0)
...@@ -1077,6 +1128,7 @@ static int pwc_video_close(struct inode *inode, struct file *file) ...@@ -1077,6 +1128,7 @@ static int pwc_video_close(struct inode *inode, struct file *file)
if (pdev->unplugged) if (pdev->unplugged)
wake_up(&pdev->remove_ok); wake_up(&pdev->remove_ok);
file->private_data = NULL; file->private_data = NULL;
Trace(TRACE_OPEN, "<< video_close()\n");
return 0; return 0;
} }
...@@ -1514,7 +1566,6 @@ static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -1514,7 +1566,6 @@ static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma)
Trace(TRACE_MEMORY, "mmap(0x%p, 0x%lx, %lu) called.\n", vdev, start, size); Trace(TRACE_MEMORY, "mmap(0x%p, 0x%lx, %lu) called.\n", vdev, start, size);
pdev = vdev->priv; pdev = vdev->priv;
/* FIXME - audit mmap during a read */
pos = (unsigned long)pdev->image_data; pos = (unsigned long)pdev->image_data;
while (size > 0) { while (size > 0) {
page = kvirt_to_pa(pos); page = kvirt_to_pa(pos);
...@@ -1547,14 +1598,14 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1547,14 +1598,14 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
int vendor_id, product_id, type_id; int vendor_id, product_id, type_id;
int i, hint; int i, hint;
int video_nr = -1; /* default: use next available device */ int video_nr = -1; /* default: use next available device */
char serial_number[30]; char serial_number[30], *name;
free_mem_leak(); free_mem_leak();
/* Check if we can handle this device */ /* Check if we can handle this device */
Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n", Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n",
udev->descriptor.idVendor, udev->descriptor.idProduct, udev->descriptor.idVendor, udev->descriptor.idProduct,
intf->altsetting->desc.bInterfaceNumber); intf->altsetting->desc.bInterfaceNumber);
/* the interfaces are probed one by one. We are only interested in the /* the interfaces are probed one by one. We are only interested in the
video interface (0) now. video interface (0) now.
...@@ -1570,38 +1621,47 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1570,38 +1621,47 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
switch (product_id) { switch (product_id) {
case 0x0302: case 0x0302:
Info("Philips PCA645VC USB webcam detected.\n"); Info("Philips PCA645VC USB webcam detected.\n");
name = "Philips 645 webcam";
type_id = 645; type_id = 645;
break; break;
case 0x0303: case 0x0303:
Info("Philips PCA646VC USB webcam detected.\n"); Info("Philips PCA646VC USB webcam detected.\n");
name = "Philips 646 webcam";
type_id = 646; type_id = 646;
break; break;
case 0x0304: case 0x0304:
Info("Askey VC010 type 2 USB webcam detected.\n"); Info("Askey VC010 type 2 USB webcam detected.\n");
name = "Askey VC010 webcam";
type_id = 646; type_id = 646;
break; break;
case 0x0307: case 0x0307:
Info("Philips PCVC675K (Vesta) USB webcam detected.\n"); Info("Philips PCVC675K (Vesta) USB webcam detected.\n");
name = "Philips 675 webcam";
type_id = 675; type_id = 675;
break; break;
case 0x0308: case 0x0308:
Info("Philips PCVC680K (Vesta Pro) USB webcam detected.\n"); Info("Philips PCVC680K (Vesta Pro) USB webcam detected.\n");
name = "Philips 680 webcam";
type_id = 680; type_id = 680;
break; break;
case 0x030C: case 0x030C:
Info("Philips PCVC690K (Vesta Pro Scan) USB webcam detected.\n"); Info("Philips PCVC690K (Vesta Pro Scan) USB webcam detected.\n");
name = "Philips 690 webcam";
type_id = 690; type_id = 690;
break; break;
case 0x0310: case 0x0310:
Info("Philips PCVC730K (ToUCam Fun) USB webcam detected.\n"); Info("Philips PCVC730K (ToUCam Fun) USB webcam detected.\n");
name = "Philips 730 webcam";
type_id = 730; type_id = 730;
break; break;
case 0x0311: case 0x0311:
Info("Philips PCVC740K (ToUCam Pro) USB webcam detected.\n"); Info("Philips PCVC740K (ToUCam Pro) USB webcam detected.\n");
name = "Philips 740 webcam";
type_id = 740; type_id = 740;
break; break;
case 0x0312: case 0x0312:
Info("Philips PCVC750K (ToUCam Pro Scan) USB webcam detected.\n"); Info("Philips PCVC750K (ToUCam Pro Scan) USB webcam detected.\n");
name = "Philips 750 webcam";
type_id = 750; type_id = 750;
break; break;
default: default:
...@@ -1613,6 +1673,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1613,6 +1673,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
switch(product_id) { switch(product_id) {
case 0x0001: case 0x0001:
Info("Askey VC010 type 1 USB webcam detected.\n"); Info("Askey VC010 type 1 USB webcam detected.\n");
name = "Askey VC010 webcam";
type_id = 645; type_id = 645;
break; break;
default: default:
...@@ -1623,9 +1684,25 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1623,9 +1684,25 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
else if (vendor_id == 0x046d) { else if (vendor_id == 0x046d) {
switch(product_id) { switch(product_id) {
case 0x08b0: case 0x08b0:
Info("Logitech QuickCam 3000 Pro detected.\n"); Info("Logitech QuickCam Pro 3000 USB webcam detected.\n");
name = "Logitech QuickCam Pro 3000";
type_id = 730; type_id = 730;
break; break;
case 0x08b1:
Info("Logitech QuickCam for Noteboos USB webcam detected.\n");
name = "Logitech QuickCam Notebook";
type_id = 740; /* ?? unknown sensor */
break;
case 0x08b2:
Info("Logitech QuickCam 4000 Pro USB webcam detected.\n");
name = "Logitech QuickCam Pro 4000";
type_id = 740; /* CCD sensor */
break;
case 0x08b3:
Info("Logitech QuickCam Zoom USB webcam detected.\n");
name = "Logitech QuickCam Zoom";
type_id = 740; /* ?? unknown sensor */
break;
default: default:
return -ENODEV; return -ENODEV;
break; break;
...@@ -1639,10 +1716,12 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1639,10 +1716,12 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
switch(product_id) { switch(product_id) {
case 0x9000: case 0x9000:
Info("Samsung MPC-C10 USB webcam detected.\n"); Info("Samsung MPC-C10 USB webcam detected.\n");
name = "Samsung MPC-C10";
type_id = 675; type_id = 675;
break; break;
case 0x9001: case 0x9001:
Info("Samsung MPC-C30 USB webcam detected.\n"); Info("Samsung MPC-C30 USB webcam detected.\n");
name = "Samsung MPC-C30";
type_id = 675; type_id = 675;
break; break;
default: default:
...@@ -1654,6 +1733,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1654,6 +1733,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
switch(product_id) { switch(product_id) {
case 0x400c: case 0x400c:
Info("Creative Labs Webcam 5 detected.\n"); Info("Creative Labs Webcam 5 detected.\n");
name = "Creative Labs Webcam 5";
type_id = 730; type_id = 730;
break; break;
default: default:
...@@ -1664,7 +1744,8 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1664,7 +1744,8 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
else if (vendor_id == 0x04cc) { else if (vendor_id == 0x04cc) {
switch(product_id) { switch(product_id) {
case 0x8116: case 0x8116:
Info("SOTEC CMS-001 USB webcam detected.\n"); Info("Sotec Afina Eye USB webcam detected.\n");
name = "Sotec Afina Eye";
type_id = 730; type_id = 730;
break; break;
default: default:
...@@ -1672,7 +1753,25 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1672,7 +1753,25 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
break; break;
} }
} }
else return -ENODEV; /* Not Philips, Askey, Logitech, Samsung, Creative or SOTEC, for sure. */ else if (vendor_id == 0x0d81) {
switch(product_id) {
case 0x1900:
Info("Visionite VCS-UC300 USB webcam detected.\n");
name = "Visionite VCS-UC300";
type_id = 740; /* CCD sensor */
break;
case 0x1910:
Info("Visionite VCS-UM100 USB webcam detected.\n");
name = "Visionite VCS-UM100";
type_id = 730; /* CMOS sensor */
break;
default:
return -ENODEV;
break;
}
}
else
return -ENODEV; /* Not any of the know types; but the list keeps growing. */
memset(serial_number, 0, 30); memset(serial_number, 0, 30);
usb_string(udev, udev->descriptor.iSerialNumber, serial_number, 29); usb_string(udev, udev->descriptor.iSerialNumber, serial_number, 29);
...@@ -1706,7 +1805,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1706,7 +1805,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
return -ENOMEM; return -ENOMEM;
} }
memcpy(vdev, &pwc_template, sizeof(pwc_template)); memcpy(vdev, &pwc_template, sizeof(pwc_template));
sprintf(vdev->name, "Philips %d webcam", pdev->type); strcpy(vdev->name, name);
SET_MODULE_OWNER(vdev); SET_MODULE_OWNER(vdev);
pdev->vdev = vdev; pdev->vdev = vdev;
vdev->priv = pdev; vdev->priv = pdev;
...@@ -1714,7 +1813,6 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id ...@@ -1714,7 +1813,6 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
pdev->release = udev->descriptor.bcdDevice; pdev->release = udev->descriptor.bcdDevice;
Trace(TRACE_PROBE, "Release: %04x\n", pdev->release); Trace(TRACE_PROBE, "Release: %04x\n", pdev->release);
/* Now search device_hint[] table for a match, so we can hint a node number. */ /* Now search device_hint[] table for a match, so we can hint a node number. */
for (hint = 0; hint < MAX_DEV_HINTS; hint++) { for (hint = 0; hint < MAX_DEV_HINTS; hint++) {
if (((device_hint[hint].type == -1) || (device_hint[hint].type == pdev->type)) && if (((device_hint[hint].type == -1) || (device_hint[hint].type == pdev->type)) &&
...@@ -1761,40 +1859,41 @@ static void usb_pwc_disconnect(struct usb_interface *intf) ...@@ -1761,40 +1859,41 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
dev_set_drvdata (&intf->dev, NULL); dev_set_drvdata (&intf->dev, NULL);
if (pdev == NULL) { if (pdev == NULL) {
Err("pwc_disconnect() Called without private pointer.\n"); Err("pwc_disconnect() Called without private pointer.\n");
goto out_err; goto disconnect_out;
} }
if (pdev->udev == NULL) { if (pdev->udev == NULL) {
Err("pwc_disconnect() already called for %p\n", pdev); Err("pwc_disconnect() already called for %p\n", pdev);
goto out_err; goto disconnect_out;
} }
if (pdev->udev != interface_to_usbdev(intf)) { if (pdev->udev != interface_to_usbdev(intf)) {
Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n"); Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n");
goto out_err; goto disconnect_out;
} }
#ifdef PWC_MAGIC #ifdef PWC_MAGIC
if (pdev->magic != PWC_MAGIC) { if (pdev->magic != PWC_MAGIC) {
Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n"); Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n");
goto out_err; goto disconnect_out;
} }
#endif #endif
pdev->unplugged = 1; pdev->unplugged = 1;
if (pdev->vdev != NULL) { if (pdev->vdev != NULL) {
video_unregister_device(pdev->vdev); Trace(TRACE_PROBE, "Unregistering video device.\n");
video_unregister_device(pdev->vdev);
if (pdev->vopen) { if (pdev->vopen) {
Info("Disconnected while device/video is open!\n"); Info("Disconnected while device/video is open!\n");
/* Wake up any processes that might be waiting for /* Wake up any processes that might be waiting for
a frame, let them return an error condition a frame, let them return an error condition
*/ */
wake_up(&pdev->frameq); wake_up(&pdev->frameq);
/* Wait until we get a 'go' from _close(). This used /* Wait until we get a 'go' from _close(). This used
to have a gigantic race condition, since we kfree() to have a gigantic race condition, since we kfree()
stuff here, but we have to wait until close() stuff here, but we have to wait until close()
is finished. is finished.
*/ */
Trace(TRACE_PROBE, "Sleeping on remove_ok.\n"); Trace(TRACE_PROBE, "Sleeping on remove_ok.\n");
add_wait_queue(&pdev->remove_ok, &wait); add_wait_queue(&pdev->remove_ok, &wait);
set_current_state(TASK_UNINTERRUPTIBLE); set_current_state(TASK_UNINTERRUPTIBLE);
...@@ -1808,26 +1907,25 @@ static void usb_pwc_disconnect(struct usb_interface *intf) ...@@ -1808,26 +1907,25 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
} }
else { else {
/* Normal disconnect; remove from available devices */ /* Normal disconnect; remove from available devices */
Trace(TRACE_PROBE, "Unregistering video device normally.\n");
kfree(pdev->vdev); kfree(pdev->vdev);
pdev->vdev = NULL; pdev->vdev = NULL;
} }
} }
disconnect_out:
/* search device_hint[] table if we occupy a slot, by any chance */ /* search device_hint[] table if we occupy a slot, by any chance */
for (hint = 0; hint < MAX_DEV_HINTS; hint++) for (hint = 0; hint < MAX_DEV_HINTS; hint++)
if (device_hint[hint].pdev == pdev) if (device_hint[hint].pdev == pdev)
device_hint[hint].pdev = NULL; device_hint[hint].pdev = NULL;
pdev->udev = NULL; pdev->udev = NULL;
out_err:
unlock_kernel(); unlock_kernel();
kfree(pdev); kfree(pdev);
} }
/* *grunt* We have to do atoi ourselves :-( */ /* *grunt* We have to do atoi ourselves :-( */
static int pwc_atoi(char *s) static int pwc_atoi(const char *s)
{ {
int k = 0; int k = 0;
...@@ -1872,7 +1970,7 @@ MODULE_PARM_DESC(leds, "LED on,off time in milliseconds"); ...@@ -1872,7 +1970,7 @@ MODULE_PARM_DESC(leds, "LED on,off time in milliseconds");
MODULE_PARM(dev_hint, "0-10s"); MODULE_PARM(dev_hint, "0-10s");
MODULE_PARM_DESC(dev_hint, "Device node hints"); MODULE_PARM_DESC(dev_hint, "Device node hints");
MODULE_DESCRIPTION("Philips USB webcam driver"); MODULE_DESCRIPTION("Philips & OEM USB webcam driver");
MODULE_AUTHOR("Nemosoft Unv. <nemosoft@smcc.demon.nl>"); MODULE_AUTHOR("Nemosoft Unv. <nemosoft@smcc.demon.nl>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -1882,11 +1980,12 @@ static int __init usb_pwc_init(void) ...@@ -1882,11 +1980,12 @@ static int __init usb_pwc_init(void)
char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" }; char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" };
Info("Philips PCA645/646 + PCVC675/680/690 + PCVC730/740/750 webcam module version " PWC_VERSION " loaded.\n"); Info("Philips PCA645/646 + PCVC675/680/690 + PCVC730/740/750 webcam module version " PWC_VERSION " loaded.\n");
Info("Also supports the Askey VC010, Logitech Quickcam 3000 Pro, Samsung MPC-C10 and MPC-C30, the Creative WebCam 5 and the SOTEC CMS-001.\n"); Info("Also supports the Askey VC010, various Logitech QuickCams, Samsung MPC-C10 and MPC-C30,\n");
Info("the Creative WebCam 5, SOTEC Afina Eye and Visionite VCS-UC300 and VCS-UM100.\n");
if (fps) { if (fps) {
if (fps < 5 || fps > 30) { if (fps < 4 || fps > 30) {
Err("Framerate out of bounds (5-30).\n"); Err("Framerate out of bounds (4-30).\n");
return -EINVAL; return -EINVAL;
} }
default_fps = fps; default_fps = fps;
...@@ -1938,9 +2037,9 @@ static int __init usb_pwc_init(void) ...@@ -1938,9 +2037,9 @@ static int __init usb_pwc_init(void)
if (power_save) if (power_save)
Info("Enabling power save on open/close.\n"); Info("Enabling power save on open/close.\n");
if (leds[0] >= 0) if (leds[0] >= 0)
led_on = leds[0] / 100; led_on = leds[0];
if (leds[1] >= 0) if (leds[1] >= 0)
led_off = leds[1] / 100; led_off = leds[1];
/* Big device node whoopla. Basicly, it allows you to assign a /* Big device node whoopla. Basicly, it allows you to assign a
device node (/dev/videoX) to a camera, based on its type device node (/dev/videoX) to a camera, based on its type
...@@ -1999,7 +2098,7 @@ static int __init usb_pwc_init(void) ...@@ -1999,7 +2098,7 @@ static int __init usb_pwc_init(void)
device_hint[i].serial_number[k] = '\0'; device_hint[i].serial_number[k] = '\0';
} }
} }
#ifdef PWC_DEBUG #if PWC_DEBUG
Debug("device_hint[%d]:\n", i); Debug("device_hint[%d]:\n", i);
Debug(" type : %d\n", device_hint[i].type); Debug(" type : %d\n", device_hint[i].type);
Debug(" serial# : %s\n", device_hint[i].serial_number); Debug(" serial# : %s\n", device_hint[i].serial_number);
......
/* Linux driver for Philips webcam /* Linux driver for Philips webcam
Various miscellaneous functions and tables. Various miscellaneous functions and tables.
(C) 1999-2001 Nemosoft Unv. (webcam@smcc.demon.nl) (C) 1999-2002 Nemosoft Unv. (webcam@smcc.demon.nl)
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
......
/* Linux driver for Philips webcam /* Linux driver for Philips webcam
Decompression frontend. Decompression frontend.
(C) 1999-2001 Nemosoft Unv. (webcam@smcc.demon.nl) (C) 1999-2002 Nemosoft Unv. (webcam@smcc.demon.nl)
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -77,7 +77,7 @@ int pwc_decompress(struct pwc_device *pdev) ...@@ -77,7 +77,7 @@ int pwc_decompress(struct pwc_device *pdev)
{ {
struct pwc_frame_buf *fbuf; struct pwc_frame_buf *fbuf;
int n, line, col, stride; int n, line, col, stride;
void *yuv, *image, *dst; void *yuv, *image;
u16 *src; u16 *src;
u16 *dsty, *dstu, *dstv; u16 *dsty, *dstu, *dstv;
...@@ -114,19 +114,6 @@ int pwc_decompress(struct pwc_device *pdev) ...@@ -114,19 +114,6 @@ int pwc_decompress(struct pwc_device *pdev)
to get the desired output format/size. to get the desired output format/size.
*/ */
switch (pdev->vpalette) { switch (pdev->vpalette) {
case VIDEO_PALETTE_YUV420:
/* Calculate byte offsets per line in image & view */
n = (pdev->image.x * 3) / 2;
col = (pdev->view.x * 3) / 2;
/* Offset into image */
dst = image + (pdev->view.x * pdev->offset.y + pdev->offset.x) * 3 / 2;
for (line = 0; line < pdev->image.y; line++) {
memcpy(dst, yuv, n);
yuv += n;
dst += col;
}
break;
case VIDEO_PALETTE_YUV420P: case VIDEO_PALETTE_YUV420P:
/* /*
* We do some byte shuffling here to go from the * We do some byte shuffling here to go from the
...@@ -163,17 +150,20 @@ int pwc_decompress(struct pwc_device *pdev) ...@@ -163,17 +150,20 @@ int pwc_decompress(struct pwc_device *pdev)
dstu += (stride >> 1); dstu += (stride >> 1);
} }
break; break;
default:
Err("Unsupported palette!");
break;
} }
} }
else { else {
/* Compressed; the decompressor routines will write the data /* Compressed; the decompressor routines will write the data
in interlaced or planar format immediately. in planar format immediately.
*/ */
if (pdev->decompressor) if (pdev->decompressor)
pdev->decompressor->decompress( pdev->decompressor->decompress(
&pdev->image, &pdev->view, &pdev->offset, &pdev->image, &pdev->view, &pdev->offset,
yuv, image, yuv, image,
pdev->vpalette == VIDEO_PALETTE_YUV420P ? 1 : 0, 1,
pdev->decompress_data, pdev->vbandlength); pdev->decompress_data, pdev->vbandlength);
else else
return -ENXIO; /* No such device or address: missing decompressor */ return -ENXIO; /* No such device or address: missing decompressor */
......
/* (C) 1999-2001 Nemosoft Unv. (webcam@smcc.demon.nl) /* (C) 1999-2002 Nemosoft Unv. (webcam@smcc.demon.nl)
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
......
...@@ -60,8 +60,8 @@ ...@@ -60,8 +60,8 @@
/* Version block */ /* Version block */
#define PWC_MAJOR 8 #define PWC_MAJOR 8
#define PWC_MINOR 7 #define PWC_MINOR 9
#define PWC_VERSION "8.7" #define PWC_VERSION "8.9"
#define PWC_NAME "pwc" #define PWC_NAME "pwc"
/* Turn certain features on/off */ /* Turn certain features on/off */
...@@ -130,7 +130,7 @@ struct pwc_device ...@@ -130,7 +130,7 @@ struct pwc_device
int vcinterface; /* video control interface */ int vcinterface; /* video control interface */
int valternate; /* alternate interface needed */ int valternate; /* alternate interface needed */
int vframes, vsize; /* frames-per-second & size (see PSZ_*) */ int vframes, vsize; /* frames-per-second & size (see PSZ_*) */
int vpalette; /* YUV, RGB24, RGB32, etc */ int vpalette; /* YUV */
int vframe_count; /* received frames */ int vframe_count; /* received frames */
int vframes_dumped; /* counter for dumped frames */ int vframes_dumped; /* counter for dumped frames */
int vframes_error; /* frames received in error */ int vframes_error; /* frames received in error */
...@@ -140,7 +140,8 @@ struct pwc_device ...@@ -140,7 +140,8 @@ struct pwc_device
int vbandlength; /* compressed band length; 0 is uncompressed */ int vbandlength; /* compressed band length; 0 is uncompressed */
char vsnapshot; /* snapshot mode */ char vsnapshot; /* snapshot mode */
char vsync; /* used by isoc handler */ char vsync; /* used by isoc handler */
char vmirror; /* for ToUCaM series */
/* The image acquisition requires 3 to 4 steps: /* The image acquisition requires 3 to 4 steps:
1. data is gathered in short packets from the USB controller 1. data is gathered in short packets from the USB controller
2. data is synchronized and packed into a frame buffer 2. data is synchronized and packed into a frame buffer
......
/* /*
* USB ViCam WebCam driver * USB ViCam WebCam driver
* Copyright (c) 2002 Joe Burks (jburks@wavicle.org), * Copyright (c) 2002 Joe Burks (jburks@wavicle.org),
* John Tyner (fill in email address) * John Tyner (jtyner@cs.ucr.edu)
* *
* Supports 3COM HomeConnect PC Digital WebCam * Supports 3COM HomeConnect PC Digital WebCam
* *
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
* Andy Armstrong who reverse engineered the color encoding and * Andy Armstrong who reverse engineered the color encoding and
* Pavel Machek and Chris Cheney who worked on reverse engineering the * Pavel Machek and Chris Cheney who worked on reverse engineering the
* camera controls and wrote the first generation driver. * camera controls and wrote the first generation driver.
* */ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/wrapper.h> #include <linux/wrapper.h>
...@@ -51,19 +51,25 @@ ...@@ -51,19 +51,25 @@
#define DBG(fmn,args...) do {} while(0) #define DBG(fmn,args...) do {} while(0)
#endif #endif
/* Version Information */ #define DRIVER_AUTHOR "Joe Burks, jburks@wavicle.org"
#define DRIVER_VERSION "v1.0" #define DRIVER_DESC "ViCam WebCam Driver"
#define DRIVER_AUTHOR "Joe Burks, jburks@wavicle.org"
#define DRIVER_DESC "ViCam WebCam Driver"
/* Define these values to match your device */ /* Define these values to match your device */
#define USB_VICAM_VENDOR_ID 0x04c1 #define USB_VICAM_VENDOR_ID 0x04c1
#define USB_VICAM_PRODUCT_ID 0x009d #define USB_VICAM_PRODUCT_ID 0x009d
#define VICAM_BYTES_PER_PIXEL 3 #define VICAM_BYTES_PER_PIXEL 3
#define VICAM_MAX_READ_SIZE (512*242+128) #define VICAM_MAX_READ_SIZE (512*242+128)
#define VICAM_MAX_FRAME_SIZE (VICAM_BYTES_PER_PIXEL*320*240) #define VICAM_MAX_FRAME_SIZE (VICAM_BYTES_PER_PIXEL*320*240)
#define VICAM_FRAMES 2 #define VICAM_FRAMES 2
#define VICAM_HEADER_SIZE 64
#define clamp( x, l, h ) max_t( __typeof__( x ), \
( l ), \
min_t( __typeof__( x ), \
( h ), \
( x ) ) )
/* Not sure what all the bytes in these char /* Not sure what all the bytes in these char
* arrays do, but they're necessary to make * arrays do, but they're necessary to make
...@@ -408,7 +414,8 @@ struct vicam_camera { ...@@ -408,7 +414,8 @@ struct vicam_camera {
struct video_device vdev; // v4l video device struct video_device vdev; // v4l video device
struct usb_device *udev; // usb device struct usb_device *udev; // usb device
struct semaphore busy_lock; // guard against SMP multithreading /* guard against simultaneous accesses to the camera */
struct semaphore cam_lock;
int is_initialized; int is_initialized;
u8 open_count; u8 open_count;
...@@ -424,17 +431,21 @@ struct vicam_camera { ...@@ -424,17 +431,21 @@ struct vicam_camera {
static int vicam_probe( struct usb_interface *intf, const struct usb_device_id *id); static int vicam_probe( struct usb_interface *intf, const struct usb_device_id *id);
static void vicam_disconnect(struct usb_interface *intf); static void vicam_disconnect(struct usb_interface *intf);
static void read_frame(struct vicam_camera *cam, int framenum); static void read_frame(struct vicam_camera *cam, int framenum);
static void vicam_decode_color(const u8 *, u8 *);
static int
send_control_msg(struct usb_device *udev, u8 request, u16 value, u16 index, static int __send_control_msg(struct vicam_camera *cam,
unsigned char *cp, u16 size) u8 request,
u16 value,
u16 index,
unsigned char *cp,
u16 size)
{ {
int status; int status;
/* cp must be memory that has been allocated by kmalloc */ /* cp must be memory that has been allocated by kmalloc */
status = usb_control_msg(udev, status = usb_control_msg(cam->udev,
usb_sndctrlpipe(udev, 0), usb_sndctrlpipe(cam->udev, 0),
request, request,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, value, index, USB_RECIP_DEVICE, value, index,
...@@ -450,6 +461,22 @@ send_control_msg(struct usb_device *udev, u8 request, u16 value, u16 index, ...@@ -450,6 +461,22 @@ send_control_msg(struct usb_device *udev, u8 request, u16 value, u16 index,
return status; return status;
} }
static int send_control_msg(struct vicam_camera *cam,
u8 request,
u16 value,
u16 index,
unsigned char *cp,
u16 size)
{
int status = -ENODEV;
down(&cam->cam_lock);
if (cam->udev) {
status = __send_control_msg(cam, request, value,
index, cp, size);
}
up(&cam->cam_lock);
return status;
}
static int static int
initialize_camera(struct vicam_camera *cam) initialize_camera(struct vicam_camera *cam)
{ {
...@@ -465,14 +492,13 @@ initialize_camera(struct vicam_camera *cam) ...@@ -465,14 +492,13 @@ initialize_camera(struct vicam_camera *cam)
{ .data = setup3, .size = sizeof(setup3) }, { .data = setup3, .size = sizeof(setup3) },
{ .data = NULL, .size = 0 } { .data = NULL, .size = 0 }
}; };
struct usb_device *udev = cam->udev;
int err, i; int err, i;
for (i = 0, err = 0; firmware[i].data && !err; i++) { for (i = 0, err = 0; firmware[i].data && !err; i++) {
memcpy(cam->cntrlbuf, firmware[i].data, firmware[i].size); memcpy(cam->cntrlbuf, firmware[i].data, firmware[i].size);
err = send_control_msg(udev, 0xff, 0, 0, err = send_control_msg(cam, 0xff, 0, 0,
cam->cntrlbuf, firmware[i].size); cam->cntrlbuf, firmware[i].size);
} }
...@@ -484,11 +510,11 @@ set_camera_power(struct vicam_camera *cam, int state) ...@@ -484,11 +510,11 @@ set_camera_power(struct vicam_camera *cam, int state)
{ {
int status; int status;
if ((status = send_control_msg(cam->udev, 0x50, state, 0, NULL, 0)) < 0) if ((status = send_control_msg(cam, 0x50, state, 0, NULL, 0)) < 0)
return status; return status;
if (state) { if (state) {
send_control_msg(cam->udev, 0x55, 1, 0, NULL, 0); send_control_msg(cam, 0x55, 1, 0, NULL, 0);
} }
return 0; return 0;
...@@ -504,10 +530,6 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign ...@@ -504,10 +530,6 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign
if (!cam) if (!cam)
return -ENODEV; return -ENODEV;
/* make this _really_ smp-safe */
if (down_interruptible(&cam->busy_lock))
return -EINTR;
switch (ioctlnr) { switch (ioctlnr) {
/* query capabilites */ /* query capabilites */
case VIDIOCGCAP: case VIDIOCGCAP:
...@@ -694,6 +716,9 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign ...@@ -694,6 +716,9 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign
DBG("VIDIOCSYNC: %d\n", frame); DBG("VIDIOCSYNC: %d\n", frame);
read_frame(cam, frame); read_frame(cam, frame);
vicam_decode_color(cam->raw_image,
cam->framebuf +
frame * VICAM_MAX_FRAME_SIZE );
break; break;
} }
...@@ -724,7 +749,6 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign ...@@ -724,7 +749,6 @@ vicam_ioctl(struct inode *inode, struct file *file, unsigned int ioctlnr, unsign
break; break;
} }
up(&cam->busy_lock);
return retval; return retval;
} }
...@@ -741,26 +765,25 @@ vicam_open(struct inode *inode, struct file *file) ...@@ -741,26 +765,25 @@ vicam_open(struct inode *inode, struct file *file)
"vicam video_device improperly initialized"); "vicam video_device improperly initialized");
} }
if ( down_interruptible(&cam->busy_lock) ) /* the videodev_lock held above us protects us from
return -EINTR; * simultaneous opens...for now. we probably shouldn't
* rely on this fact forever.
*/
if (cam->open_count > 0) { if (cam->open_count > 0) {
printk(KERN_INFO printk(KERN_INFO
"vicam_open called on already opened camera"); "vicam_open called on already opened camera");
up(&cam->busy_lock);
return -EBUSY; return -EBUSY;
} }
cam->raw_image = kmalloc(VICAM_MAX_READ_SIZE, GFP_KERNEL); cam->raw_image = kmalloc(VICAM_MAX_READ_SIZE, GFP_KERNEL);
if (!cam->raw_image) { if (!cam->raw_image) {
up(&cam->busy_lock);
return -ENOMEM; return -ENOMEM;
} }
cam->framebuf = rvmalloc(VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); cam->framebuf = rvmalloc(VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
if (!cam->framebuf) { if (!cam->framebuf) {
kfree(cam->raw_image); kfree(cam->raw_image);
up(&cam->busy_lock);
return -ENOMEM; return -ENOMEM;
} }
...@@ -768,7 +791,6 @@ vicam_open(struct inode *inode, struct file *file) ...@@ -768,7 +791,6 @@ vicam_open(struct inode *inode, struct file *file)
if (!cam->cntrlbuf) { if (!cam->cntrlbuf) {
kfree(cam->raw_image); kfree(cam->raw_image);
rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
up(&cam->busy_lock);
return -ENOMEM; return -ENOMEM;
} }
...@@ -785,8 +807,6 @@ vicam_open(struct inode *inode, struct file *file) ...@@ -785,8 +807,6 @@ vicam_open(struct inode *inode, struct file *file)
cam->needsDummyRead = 1; cam->needsDummyRead = 1;
cam->open_count++; cam->open_count++;
up(&cam->busy_lock);
file->private_data = cam; file->private_data = cam;
return 0; return 0;
...@@ -796,118 +816,105 @@ static int ...@@ -796,118 +816,105 @@ static int
vicam_close(struct inode *inode, struct file *file) vicam_close(struct inode *inode, struct file *file)
{ {
struct vicam_camera *cam = file->private_data; struct vicam_camera *cam = file->private_data;
int open_count;
struct usb_device *udev;
DBG("close\n"); DBG("close\n");
/* it's not the end of the world if
* we fail to turn the camera off.
*/
set_camera_power(cam, 0); set_camera_power(cam, 0);
kfree(cam->raw_image); kfree(cam->raw_image);
rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES); rvfree(cam->framebuf, VICAM_MAX_FRAME_SIZE * VICAM_FRAMES);
kfree(cam->cntrlbuf); kfree(cam->cntrlbuf);
down(&cam->cam_lock);
cam->open_count--; cam->open_count--;
open_count = cam->open_count;
udev = cam->udev;
up(&cam->cam_lock);
if (!open_count && !udev) {
kfree(cam);
}
return 0; return 0;
} }
inline int pin(int x) static void vicam_decode_color(const u8 *data, u8 *rgb)
{ {
return((x > 255) ? 255 : ((x < 0) ? 0 : x)); /* vicam_decode_color - Convert from Vicam Y-Cr-Cb to RGB
} * Copyright (C) 2002 Monroe Williams (monroe@pobox.com)
*/
inline void writepixel(char *rgb, int Y, int Cr, int Cb) int i, prevY, nextY;
{
Y = 1160 * (Y - 16);
rgb[2] = pin( ( ( Y + ( 1594 * Cr ) ) + 500 ) / 1300 );
rgb[1] = pin( ( ( Y - ( 392 * Cb ) - ( 813 * Cr ) ) + 500 ) / 1000 );
rgb[0] = pin( ( ( Y + ( 2017 * Cb ) ) + 500 ) / 900 );
}
#define DATA_HEADER_SIZE 64 prevY = 512;
nextY = 512;
// -------------------------------------------------------------------------------- data += VICAM_HEADER_SIZE;
// vicam_decode_color - Convert from Vicam Y-Cr-Cb to RGB
//
// Copyright (C) 2002 Monroe Williams (monroe@pobox.com)
// --------------------------------------------------------------------------------
static void vicam_decode_color( char *data, char *rgb) for( i = 0; i < 240; i++, data += 512 ) {
{ const int y = ( i * 242 ) / 240;
int x,y;
int Cr, Cb;
int sign;
int prevX, nextX, prevY, nextY;
int skip;
unsigned char *src;
unsigned char *dst;
prevY = 512; int j, prevX, nextX;
nextY = 512; int Y, Cr, Cb;
src = data + DATA_HEADER_SIZE; if ( y == 242 - 1 ) {
dst = rgb; nextY = -512;
}
for(y = 1; y < 241; y += 2)
{
// even line
sign = 1;
prevX = 1; prevX = 1;
nextX = 1; nextX = 1;
skip = 0; for ( j = 0; j < 320; j++, rgb += 3 ) {
const int x = ( j * 512 ) / 320;
const u8 * const src = &data[x];
dst = rgb + (y-1)*320*3; if ( x == 512 - 1 ) {
for(x = 0; x < 512; x++)
{
if(x == 512-1)
nextX = -1; nextX = -1;
}
Cr = sign * ((src[prevX] - src[0]) + (src[nextX] - src[0])) >> 1; Cr = ( src[prevX] - src[0] ) +
Cb = sign * ((src[prevY] - src[prevX + prevY]) + (src[prevY] - src[nextX + prevY]) + (src[nextY] - src[prevX + nextY]) + (src[nextY] - src[nextX + nextY])) >> 2; ( src[nextX] - src[0] );
Cr /= 2;
writepixel(
dst + ((x*5)>>3)*3,
src[0] + (sign * (Cr >> 1)),
Cr,
Cb);
src++;
sign *= -1;
prevX = -1;
}
prevY = -512;
if(y == (242 - 2)) Cb = ( src[prevY] - src[prevX + prevY] ) +
nextY = -512; ( src[prevY] - src[nextX + prevY] ) +
( src[nextY] - src[prevX + nextY] ) +
( src[nextY] - src[nextX + nextY] );
Cb /= 4;
// odd line Y = 1160 * ( src[0] + ( Cr / 2 ) - 16 );
sign = 1;
prevX = 1;
nextX = 1;
skip = 0; if ( i & 1 ) {
int Ct = Cr;
Cr = Cb;
Cb = Ct;
}
dst = rgb + (y)*320*3; if ( ( x ^ i ) & 1 ) {
Cr = -Cr;
for(x = 0; x < 512; x++) Cb = -Cb;
{ }
if(x == 512-1)
nextX = -1;
Cr = sign * ((src[prevX + prevY] - src[prevY]) + (src[nextX + prevY] - src[prevY]) + (src[prevX + nextY] - src[nextY]) + (src[nextX + nextY] - src[nextY])) >> 2;
Cb = sign * ((src[0] - src[prevX]) + (src[0] - src[nextX])) >> 1;
writepixel( rgb[0] = clamp( ( ( Y + ( 2017 * Cb ) ) +
dst + ((x * 5)>>3)*3, 500 ) / 900, 0, 255 );
src[0] - (sign * (Cb >> 1)), rgb[1] = clamp( ( ( Y - ( 392 * Cb ) -
Cr, ( 813 * Cr ) ) +
Cb); 500 ) / 1000, 0, 255 );
rgb[2] = clamp( ( ( Y + ( 1594 * Cr ) ) +
500 ) / 1300, 0, 255 );
src++;
sign *= -1;
prevX = -1; prevX = -1;
} }
prevY = -512;
} }
} }
...@@ -953,12 +960,18 @@ read_frame(struct vicam_camera *cam, int framenum) ...@@ -953,12 +960,18 @@ read_frame(struct vicam_camera *cam, int framenum)
request[8] = 0; request[8] = 0;
// bytes 9-15 do not seem to affect exposure or image quality // bytes 9-15 do not seem to affect exposure or image quality
n = send_control_msg(cam->udev, 0x51, 0x80, 0, request, 16); down(&cam->cam_lock);
if (!cam->udev) {
goto done;
}
n = __send_control_msg(cam, 0x51, 0x80, 0, request, 16);
if (n < 0) { if (n < 0) {
printk(KERN_ERR printk(KERN_ERR
" Problem sending frame capture control message"); " Problem sending frame capture control message");
return; goto done;
} }
n = usb_bulk_msg(cam->udev, n = usb_bulk_msg(cam->udev,
...@@ -971,9 +984,8 @@ read_frame(struct vicam_camera *cam, int framenum) ...@@ -971,9 +984,8 @@ read_frame(struct vicam_camera *cam, int framenum)
n); n);
} }
vicam_decode_color(cam->raw_image, done:
cam->framebuf + up(&cam->cam_lock);
framenum * VICAM_MAX_FRAME_SIZE );
} }
static int static int
...@@ -983,17 +995,16 @@ vicam_read( struct file *file, char *buf, size_t count, loff_t *ppos ) ...@@ -983,17 +995,16 @@ vicam_read( struct file *file, char *buf, size_t count, loff_t *ppos )
DBG("read %d bytes.\n", (int) count); DBG("read %d bytes.\n", (int) count);
if ( down_interruptible(&cam->busy_lock) )
return -EINTR;
if (*ppos >= VICAM_MAX_FRAME_SIZE) { if (*ppos >= VICAM_MAX_FRAME_SIZE) {
*ppos = 0; *ppos = 0;
up(&cam->busy_lock);
return 0; return 0;
} }
if (*ppos == 0) { if (*ppos == 0) {
read_frame(cam, 0); read_frame(cam, 0);
vicam_decode_color(cam->raw_image,
cam->framebuf +
0 * VICAM_MAX_FRAME_SIZE);
} }
count = min_t(size_t, count, VICAM_MAX_FRAME_SIZE - *ppos); count = min_t(size_t, count, VICAM_MAX_FRAME_SIZE - *ppos);
...@@ -1008,8 +1019,6 @@ vicam_read( struct file *file, char *buf, size_t count, loff_t *ppos ) ...@@ -1008,8 +1019,6 @@ vicam_read( struct file *file, char *buf, size_t count, loff_t *ppos )
*ppos = 0; *ppos = 0;
} }
up(&cam->busy_lock);
return count; return count;
} }
...@@ -1034,10 +1043,6 @@ vicam_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -1034,10 +1043,6 @@ vicam_mmap(struct file *file, struct vm_area_struct *vma)
return -EINVAL; return -EINVAL;
*/ */
/* make this _really_ smp-safe */
if (down_interruptible(&cam->busy_lock))
return -EINTR;
pos = (unsigned long)cam->framebuf; pos = (unsigned long)cam->framebuf;
while (size > 0) { while (size > 0) {
page = kvirt_to_pa(pos); page = kvirt_to_pa(pos);
...@@ -1052,8 +1057,6 @@ vicam_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -1052,8 +1057,6 @@ vicam_mmap(struct file *file, struct vm_area_struct *vma)
size = 0; size = 0;
} }
up(&cam->busy_lock);
return 0; return 0;
} }
...@@ -1285,7 +1288,7 @@ vicam_probe( struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1285,7 +1288,7 @@ vicam_probe( struct usb_interface *intf, const struct usb_device_id *id)
cam->shutter_speed = 15; cam->shutter_speed = 15;
init_MUTEX(&cam->busy_lock); init_MUTEX(&cam->cam_lock);
memcpy(&cam->vdev, &vicam_template, memcpy(&cam->vdev, &vicam_template,
sizeof (vicam_template)); sizeof (vicam_template));
...@@ -1312,17 +1315,43 @@ vicam_probe( struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1312,17 +1315,43 @@ vicam_probe( struct usb_interface *intf, const struct usb_device_id *id)
static void static void
vicam_disconnect(struct usb_interface *intf) vicam_disconnect(struct usb_interface *intf)
{ {
int open_count;
struct vicam_camera *cam = dev_get_drvdata(&intf->dev); struct vicam_camera *cam = dev_get_drvdata(&intf->dev);
dev_set_drvdata ( &intf->dev, NULL ); dev_set_drvdata ( &intf->dev, NULL );
cam->udev = NULL; /* we must unregister the device before taking its
* cam_lock. This is because the video open call
* holds the same lock as video unregister. if we
* unregister inside of the cam_lock and open also
* uses the cam_lock, we get deadlock.
*/
video_unregister_device(&cam->vdev); video_unregister_device(&cam->vdev);
/* stop the camera from being used */
down(&cam->cam_lock);
/* mark the camera as gone */
cam->udev = NULL;
vicam_destroy_proc_entry(cam); vicam_destroy_proc_entry(cam);
kfree(cam); /* the only thing left to do is synchronize with
* our close/release function on who should release
* the camera memory. if there are any users using the
* camera, it's their job. if there are no users,
* it's ours.
*/
open_count = cam->open_count;
up(&cam->cam_lock);
if (!open_count) {
kfree(cam);
}
printk(KERN_DEBUG "ViCam-based WebCam disconnected\n"); printk(KERN_DEBUG "ViCam-based WebCam disconnected\n");
} }
......
...@@ -185,7 +185,7 @@ tiglusb_read (struct file *filp, char *buf, size_t count, loff_t * f_pos) ...@@ -185,7 +185,7 @@ tiglusb_read (struct file *filp, char *buf, size_t count, loff_t * f_pos)
pipe = usb_rcvbulkpipe (s->dev, 1); pipe = usb_rcvbulkpipe (s->dev, 1);
result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_read, result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_read,
&bytes_read, HZ / (timeout / 10)); &bytes_read, HZ * 10 / timeout);
if (result == -ETIMEDOUT) { /* NAK */ if (result == -ETIMEDOUT) { /* NAK */
ret = result; ret = result;
if (!bytes_read) { if (!bytes_read) {
...@@ -242,7 +242,7 @@ tiglusb_write (struct file *filp, const char *buf, size_t count, loff_t * f_pos) ...@@ -242,7 +242,7 @@ tiglusb_write (struct file *filp, const char *buf, size_t count, loff_t * f_pos)
pipe = usb_sndbulkpipe (s->dev, 2); pipe = usb_sndbulkpipe (s->dev, 2);
result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_write, result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_write,
&bytes_written, HZ / (timeout / 10)); &bytes_written, HZ * 10 / timeout);
if (result == -ETIMEDOUT) { /* NAK */ if (result == -ETIMEDOUT) { /* NAK */
warn ("tiglusb_write, NAK received."); warn ("tiglusb_write, NAK received.");
...@@ -453,6 +453,8 @@ tiglusb_setup (char *str) ...@@ -453,6 +453,8 @@ tiglusb_setup (char *str)
if (ints[0] > 0) { if (ints[0] > 0) {
timeout = ints[1]; timeout = ints[1];
} }
if (!timeout)
timeout = TIMAXTIME;
return 1; return 1;
} }
...@@ -494,6 +496,9 @@ tiglusb_init (void) ...@@ -494,6 +496,9 @@ tiglusb_init (void)
info (DRIVER_DESC ", " DRIVER_VERSION); info (DRIVER_DESC ", " DRIVER_VERSION);
if (!timeout)
timeout = TIMAXTIME;
return 0; return 0;
} }
...@@ -516,6 +521,6 @@ MODULE_DESCRIPTION (DRIVER_DESC); ...@@ -516,6 +521,6 @@ MODULE_DESCRIPTION (DRIVER_DESC);
MODULE_LICENSE (DRIVER_LICENSE); MODULE_LICENSE (DRIVER_LICENSE);
MODULE_PARM (timeout, "i"); MODULE_PARM (timeout, "i");
MODULE_PARM_DESC (timeout, "Timeout (default=1.5 seconds)"); MODULE_PARM_DESC (timeout, "Timeout in tenths of seconds (default=1.5 seconds)");
/* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */
...@@ -400,5 +400,10 @@ config USB_SERIAL_OMNINET ...@@ -400,5 +400,10 @@ config USB_SERIAL_OMNINET
The module will be called omninet.o. If you want to compile it as a The module will be called omninet.o. If you want to compile it as a
module, say M here and read <file:Documentation/modules.txt>. module, say M here and read <file:Documentation/modules.txt>.
config USB_EZUSB
bool
depends on USB_SERIAL_KEYSPAN_PDA || USB_SERIAL_XIRCOM || USB_SERIAL_KEYSPAN || USB_SERIAL_WHITEHEAT
default y
endmenu endmenu
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
obj-$(CONFIG_USB_SERIAL) += usbserial.o obj-$(CONFIG_USB_SERIAL) += usbserial.o
usbserial-obj-$(CONFIG_USB_SERIAL_CONSOLE) += console.o usbserial-obj-$(CONFIG_USB_SERIAL_CONSOLE) += console.o
usbserial-obj-$(CONFIG_USB_EZUSB) += ezusb.o
obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o
obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o
...@@ -29,9 +30,9 @@ obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o ...@@ -29,9 +30,9 @@ obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o
obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o
# Objects that export symbols. # Objects that export symbols.
export-objs := usb-serial.o export-objs := usb-serial.o ezusb.o
usbserial-objs := usb-serial.o $(usbserial-obj-y) usbserial-objs := usb-serial.o generic.o $(usbserial-obj-y)
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
/*
* EZ-USB specific functions used by some of the USB to Serial drivers.
*
* Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/module.h>
#include <linux/usb.h>
#ifdef CONFIG_USB_SERIAL_DEBUG
static int debug = 1;
#else
static int debug;
#endif
#include "usb-serial.h"
/* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */
#define CPUCS_REG 0x7F92
int ezusb_writememory (struct usb_serial *serial, int address, unsigned char *data, int length, __u8 bRequest)
{
int result;
unsigned char *transfer_buffer;
/* dbg("ezusb_writememory %x, %d", address, length); */
if (!serial->dev) {
dbg("%s - no physical device present, failing.", __FUNCTION__);
return -ENODEV;
}
transfer_buffer = kmalloc (length, GFP_KERNEL);
if (!transfer_buffer) {
err("%s - kmalloc(%d) failed.", __FUNCTION__, length);
return -ENOMEM;
}
memcpy (transfer_buffer, data, length);
result = usb_control_msg (serial->dev, usb_sndctrlpipe(serial->dev, 0), bRequest, 0x40, address, 0, transfer_buffer, length, 3*HZ);
kfree (transfer_buffer);
return result;
}
int ezusb_set_reset (struct usb_serial *serial, unsigned char reset_bit)
{
int response;
dbg("%s - %d", __FUNCTION__, reset_bit);
response = ezusb_writememory (serial, CPUCS_REG, &reset_bit, 1, 0xa0);
if (response < 0) {
err("%s- %d failed", __FUNCTION__, reset_bit);
}
return response;
}
EXPORT_SYMBOL(ezusb_writememory);
EXPORT_SYMBOL(ezusb_set_reset);
/*
* USB Serial Converter Generic functions
*
* Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
#ifdef CONFIG_USB_SERIAL_DEBUG
static int debug = 1;
#else
static int debug;
#endif
#include "usb-serial.h"
#ifdef CONFIG_USB_SERIAL_GENERIC
static __u16 vendor = 0x05f9;
static __u16 product = 0xffff;
MODULE_PARM(vendor, "h");
MODULE_PARM_DESC(vendor, "User specified USB idVendor");
MODULE_PARM(product, "h");
MODULE_PARM_DESC(product, "User specified USB idProduct");
static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */
/* All of the device info needed for the Generic Serial Converter */
struct usb_serial_device_type usb_serial_generic_device = {
.owner = THIS_MODULE,
.name = "Generic",
.id_table = generic_device_ids,
.num_interrupt_in = NUM_DONT_CARE,
.num_bulk_in = NUM_DONT_CARE,
.num_bulk_out = NUM_DONT_CARE,
.num_ports = 1,
.shutdown = usb_serial_generic_shutdown,
};
#endif
int usb_serial_generic_register (int _debug)
{
int retval = 0;
debug = _debug;
#ifdef CONFIG_USB_SERIAL_GENERIC
generic_device_ids[0].idVendor = vendor;
generic_device_ids[0].idProduct = product;
generic_device_ids[0].match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;
/* register our generic driver with ourselves */
retval = usb_serial_register (&usb_serial_generic_device);
#endif
return retval;
}
void usb_serial_generic_deregister (void)
{
#ifdef CONFIG_USB_SERIAL_GENERIC
/* remove our generic driver */
usb_serial_deregister (&usb_serial_generic_device);
#endif
}
int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp)
{
struct usb_serial *serial = port->serial;
int result = 0;
if (port_paranoia_check (port, __FUNCTION__))
return -ENODEV;
dbg("%s - port %d", __FUNCTION__, port->number);
/* force low_latency on so that our tty_push actually forces the data through,
otherwise it is scheduled, and with high data rates (like with OHCI) data
can get lost. */
if (port->tty)
port->tty->low_latency = 1;
/* if we have a bulk interrupt, start reading from it */
if (serial->num_bulk_in) {
/* Start reading from the device */
usb_fill_bulk_urb (port->read_urb, serial->dev,
usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress),
port->read_urb->transfer_buffer,
port->read_urb->transfer_buffer_length,
((serial->type->read_bulk_callback) ?
serial->type->read_bulk_callback :
usb_serial_generic_read_bulk_callback),
port);
result = usb_submit_urb(port->read_urb, GFP_KERNEL);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
}
return result;
}
static void generic_cleanup (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->dev) {
/* shutdown any bulk reads that might be going on */
if (serial->num_bulk_out)
usb_unlink_urb (port->write_urb);
if (serial->num_bulk_in)
usb_unlink_urb (port->read_urb);
}
}
void usb_serial_generic_close (struct usb_serial_port *port, struct file * filp)
{
dbg("%s - port %d", __FUNCTION__, port->number);
generic_cleanup (port);
}
int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count)
{
struct usb_serial *serial = port->serial;
int result;
dbg("%s - port %d", __FUNCTION__, port->number);
if (count == 0) {
dbg("%s - write request of 0 bytes", __FUNCTION__);
return (0);
}
/* only do something if we have a bulk out endpoint */
if (serial->num_bulk_out) {
if (port->write_urb->status == -EINPROGRESS) {
dbg("%s - already writing", __FUNCTION__);
return (0);
}
count = (count > port->bulk_out_size) ? port->bulk_out_size : count;
if (from_user) {
if (copy_from_user(port->write_urb->transfer_buffer, buf, count))
return -EFAULT;
}
else {
memcpy (port->write_urb->transfer_buffer, buf, count);
}
usb_serial_debug_data (__FILE__, __FUNCTION__, count, port->write_urb->transfer_buffer);
/* set up our urb */
usb_fill_bulk_urb (port->write_urb, serial->dev,
usb_sndbulkpipe (serial->dev,
port->bulk_out_endpointAddress),
port->write_urb->transfer_buffer, count,
((serial->type->write_bulk_callback) ?
serial->type->write_bulk_callback :
usb_serial_generic_write_bulk_callback), port);
/* send the data out the bulk port */
result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
if (result)
err("%s - failed submitting write urb, error %d", __FUNCTION__, result);
else
result = count;
return result;
}
/* no bulk out, so return 0 bytes written */
return (0);
}
int usb_serial_generic_write_room (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
int room = 0;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->num_bulk_out) {
if (port->write_urb->status != -EINPROGRESS)
room = port->bulk_out_size;
}
dbg("%s - returns %d", __FUNCTION__, room);
return (room);
}
int usb_serial_generic_chars_in_buffer (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
int chars = 0;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->num_bulk_out) {
if (port->write_urb->status == -EINPROGRESS)
chars = port->write_urb->transfer_buffer_length;
}
dbg("%s - returns %d", __FUNCTION__, chars);
return (chars);
}
void usb_serial_generic_read_bulk_callback (struct urb *urb)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
struct tty_struct *tty;
unsigned char *data = urb->transfer_buffer;
int i;
int result;
dbg("%s - port %d", __FUNCTION__, port->number);
if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__);
return;
}
if (urb->status) {
dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status);
return;
}
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data);
tty = port->tty;
if (tty && urb->actual_length) {
for (i = 0; i < urb->actual_length ; ++i) {
/* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */
if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
tty_flip_buffer_push(tty);
}
/* this doesn't actually push the data through unless tty->low_latency is set */
tty_insert_flip_char(tty, data[i], 0);
}
tty_flip_buffer_push(tty);
}
/* Continue trying to always read */
usb_fill_bulk_urb (port->read_urb, serial->dev,
usb_rcvbulkpipe (serial->dev,
port->bulk_in_endpointAddress),
port->read_urb->transfer_buffer,
port->read_urb->transfer_buffer_length,
((serial->type->read_bulk_callback) ?
serial->type->read_bulk_callback :
usb_serial_generic_read_bulk_callback), port);
result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
}
void usb_serial_generic_write_bulk_callback (struct urb *urb)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
dbg("%s - port %d", __FUNCTION__, port->number);
if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__);
return;
}
if (urb->status) {
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
return;
}
usb_serial_port_softint((void *)port);
schedule_work(&port->work);
}
void usb_serial_generic_shutdown (struct usb_serial *serial)
{
int i;
dbg("%s", __FUNCTION__);
/* stop reads and writes on all ports */
for (i=0; i < serial->num_ports; ++i) {
generic_cleanup (&serial->port[i]);
}
}
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* (26/11/2002) ganesh
* Added insmod options to specify product and vendor id.
* Use modprobe ipaq vendor=0xfoo product=0xbar
*
* (26/7/2002) ganesh * (26/7/2002) ganesh
* Fixed up broken error handling in ipaq_open. Retry the "kickstart" * Fixed up broken error handling in ipaq_open. Retry the "kickstart"
* packet much harder - this drastically reduces connection failures. * packet much harder - this drastically reduces connection failures.
...@@ -63,10 +67,13 @@ ...@@ -63,10 +67,13 @@
/* /*
* Version Information * Version Information
*/ */
#define DRIVER_VERSION "v0.2"
#define DRIVER_VERSION "v0.4"
#define DRIVER_AUTHOR "Ganesh Varadarajan <ganesh@veritas.com>" #define DRIVER_AUTHOR "Ganesh Varadarajan <ganesh@veritas.com>"
#define DRIVER_DESC "USB Compaq iPAQ, HP Jornada, Casio EM500 driver" #define DRIVER_DESC "USB Compaq iPAQ, HP Jornada, Casio EM500 driver"
static int product, vendor;
/* Function prototypes for an ipaq */ /* Function prototypes for an ipaq */
static int ipaq_open (struct usb_serial_port *port, struct file *filp); static int ipaq_open (struct usb_serial_port *port, struct file *filp);
static void ipaq_close (struct usb_serial_port *port, struct file *filp); static void ipaq_close (struct usb_serial_port *port, struct file *filp);
...@@ -85,6 +92,8 @@ static void ipaq_destroy_lists(struct usb_serial_port *port); ...@@ -85,6 +92,8 @@ static void ipaq_destroy_lists(struct usb_serial_port *port);
static struct usb_device_id ipaq_id_table [] = { static struct usb_device_id ipaq_id_table [] = {
/* The first entry is a placeholder for the insmod-specified device */
{ USB_DEVICE(COMPAQ_VENDOR_ID, COMPAQ_IPAQ_ID) },
{ USB_DEVICE(COMPAQ_VENDOR_ID, COMPAQ_IPAQ_ID) }, { USB_DEVICE(COMPAQ_VENDOR_ID, COMPAQ_IPAQ_ID) },
{ USB_DEVICE(HP_VENDOR_ID, HP_JORNADA_548_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_JORNADA_548_ID) },
{ USB_DEVICE(HP_VENDOR_ID, HP_JORNADA_568_ID) }, { USB_DEVICE(HP_VENDOR_ID, HP_JORNADA_568_ID) },
...@@ -521,9 +530,14 @@ static void ipaq_shutdown(struct usb_serial *serial) ...@@ -521,9 +530,14 @@ static void ipaq_shutdown(struct usb_serial *serial)
static int __init ipaq_init(void) static int __init ipaq_init(void)
{ {
spin_lock_init(&write_list_lock);
usb_serial_register(&ipaq_device); usb_serial_register(&ipaq_device);
usb_register(&ipaq_driver);
info(DRIVER_DESC " " DRIVER_VERSION); info(DRIVER_DESC " " DRIVER_VERSION);
if (vendor) {
ipaq_id_table[0].idVendor = vendor;
ipaq_id_table[0].idProduct = product;
}
usb_register(&ipaq_driver);
return 0; return 0;
} }
...@@ -546,3 +560,8 @@ MODULE_LICENSE("GPL"); ...@@ -546,3 +560,8 @@ MODULE_LICENSE("GPL");
MODULE_PARM(debug, "i"); MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not"); MODULE_PARM_DESC(debug, "Debug enabled or not");
MODULE_PARM(vendor, "h");
MODULE_PARM_DESC(vendor, "User specified USB idVendor");
MODULE_PARM(product, "h");
MODULE_PARM_DESC(product, "User specified USB idProduct");
...@@ -346,7 +346,7 @@ static int keyspan_write(struct usb_serial_port *port, int from_user, ...@@ -346,7 +346,7 @@ static int keyspan_write(struct usb_serial_port *port, int from_user,
if (this_urb->status == -EINPROGRESS) { if (this_urb->status == -EINPROGRESS) {
if (this_urb->transfer_flags & URB_ASYNC_UNLINK) if (this_urb->transfer_flags & URB_ASYNC_UNLINK)
break; break;
if (jiffies - p_priv->tx_start_time[flip] < 10 * HZ) if (time_before(jiffies, p_priv->tx_start_time[flip] + 10 * HZ))
break; break;
this_urb->transfer_flags |= URB_ASYNC_UNLINK; this_urb->transfer_flags |= URB_ASYNC_UNLINK;
usb_unlink_urb(this_urb); usb_unlink_urb(this_urb);
......
...@@ -345,41 +345,12 @@ ...@@ -345,41 +345,12 @@
/* /*
* Version Information * Version Information
*/ */
#define DRIVER_VERSION "v1.7" #define DRIVER_VERSION "v1.8"
#define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com, http://www.kroah.com/linux/" #define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com, http://www.kroah.com/linux/"
#define DRIVER_DESC "USB Serial Driver core" #define DRIVER_DESC "USB Serial Driver core"
/* function prototypes for a "generic" type serial converter (no flow control, not all endpoints needed) */
/* need to always compile these in, as some of the other devices use these functions as their own. */
/* if a driver does not provide a function pointer, the generic function will be called. */
int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp);
int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count);
static void generic_close (struct usb_serial_port *port, struct file *filp);
static int generic_write_room (struct usb_serial_port *port);
static int generic_chars_in_buffer (struct usb_serial_port *port);
static void generic_read_bulk_callback (struct urb *urb);
static void generic_write_bulk_callback (struct urb *urb);
static void generic_shutdown (struct usb_serial *serial);
#ifdef CONFIG_USB_SERIAL_GENERIC #ifdef CONFIG_USB_SERIAL_GENERIC
static __u16 vendor = 0x05f9;
static __u16 product = 0xffff;
static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */
/* All of the device info needed for the Generic Serial Converter */
static struct usb_serial_device_type generic_device = {
.owner = THIS_MODULE,
.name = "Generic",
.id_table = generic_device_ids,
.num_interrupt_in = NUM_DONT_CARE,
.num_bulk_in = NUM_DONT_CARE,
.num_bulk_out = NUM_DONT_CARE,
.num_ports = 1,
.shutdown = generic_shutdown,
};
/* we want to look at all devices, as the vendor/product id can change /* we want to look at all devices, as the vendor/product id can change
* depending on the command line argument */ * depending on the command line argument */
static struct usb_device_id generic_serial_ids[] = { static struct usb_device_id generic_serial_ids[] = {
...@@ -387,27 +358,6 @@ static struct usb_device_id generic_serial_ids[] = { ...@@ -387,27 +358,6 @@ static struct usb_device_id generic_serial_ids[] = {
{} {}
}; };
static int generic_register (void)
{
generic_device_ids[0].idVendor = vendor;
generic_device_ids[0].idProduct = product;
generic_device_ids[0].match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;
/* register our generic driver with ourselves */
return usb_serial_register (&generic_device);
}
static void generic_deregister (void)
{
/* remove our generic driver */
usb_serial_deregister (&generic_device);
}
#else
static inline int generic_register (void) { return 0; }
static inline void generic_deregister (void) { }
#endif /* CONFIG_USB_SERIAL_GENERIC */ #endif /* CONFIG_USB_SERIAL_GENERIC */
/* Driver structure we register with the USB core */ /* Driver structure we register with the USB core */
...@@ -488,45 +438,6 @@ static void return_serial (struct usb_serial *serial) ...@@ -488,45 +438,6 @@ static void return_serial (struct usb_serial *serial)
return; return;
} }
#ifdef USES_EZUSB_FUNCTIONS
/* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */
#define CPUCS_REG 0x7F92
int ezusb_writememory (struct usb_serial *serial, int address, unsigned char *data, int length, __u8 bRequest)
{
int result;
unsigned char *transfer_buffer;
/* dbg("ezusb_writememory %x, %d", address, length); */
if (!serial->dev) {
dbg("%s - no physical device present, failing.", __FUNCTION__);
return -ENODEV;
}
transfer_buffer = kmalloc (length, GFP_KERNEL);
if (!transfer_buffer) {
err("%s - kmalloc(%d) failed.", __FUNCTION__, length);
return -ENOMEM;
}
memcpy (transfer_buffer, data, length);
result = usb_control_msg (serial->dev, usb_sndctrlpipe(serial->dev, 0), bRequest, 0x40, address, 0, transfer_buffer, length, 3*HZ);
kfree (transfer_buffer);
return result;
}
int ezusb_set_reset (struct usb_serial *serial, unsigned char reset_bit)
{
int response;
dbg("%s - %d", __FUNCTION__, reset_bit);
response = ezusb_writememory (serial, CPUCS_REG, &reset_bit, 1, 0xa0);
if (response < 0) {
err("%s- %d failed", __FUNCTION__, reset_bit);
}
return response;
}
#endif /* USES_EZUSB_FUNCTIONS */
/***************************************************************************** /*****************************************************************************
* Driver tty interface functions * Driver tty interface functions
*****************************************************************************/ *****************************************************************************/
...@@ -593,7 +504,7 @@ static void __serial_close(struct usb_serial_port *port, struct file *filp) ...@@ -593,7 +504,7 @@ static void __serial_close(struct usb_serial_port *port, struct file *filp)
if (port->serial->type->close) if (port->serial->type->close)
port->serial->type->close(port, filp); port->serial->type->close(port, filp);
else else
generic_close(port, filp); usb_serial_generic_close(port, filp);
port->open_count = 0; port->open_count = 0;
} }
...@@ -672,7 +583,7 @@ static int serial_write_room (struct tty_struct *tty) ...@@ -672,7 +583,7 @@ static int serial_write_room (struct tty_struct *tty)
if (serial->type->write_room) if (serial->type->write_room)
retval = serial->type->write_room(port); retval = serial->type->write_room(port);
else else
retval = generic_write_room(port); retval = usb_serial_generic_write_room(port);
exit: exit:
up (&port->sem); up (&port->sem);
...@@ -701,7 +612,7 @@ static int serial_chars_in_buffer (struct tty_struct *tty) ...@@ -701,7 +612,7 @@ static int serial_chars_in_buffer (struct tty_struct *tty)
if (serial->type->chars_in_buffer) if (serial->type->chars_in_buffer)
retval = serial->type->chars_in_buffer(port); retval = serial->type->chars_in_buffer(port);
else else
retval = generic_chars_in_buffer(port); retval = usb_serial_generic_chars_in_buffer(port);
exit: exit:
up (&port->sem); up (&port->sem);
...@@ -844,7 +755,7 @@ static void serial_shutdown (struct usb_serial *serial) ...@@ -844,7 +755,7 @@ static void serial_shutdown (struct usb_serial *serial)
if (serial->type->shutdown) if (serial->type->shutdown)
serial->type->shutdown(serial); serial->type->shutdown(serial);
else else
generic_shutdown(serial); usb_serial_generic_shutdown(serial);
} }
static int serial_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data) static int serial_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data)
...@@ -889,235 +800,6 @@ static int serial_read_proc (char *page, char **start, off_t off, int count, int ...@@ -889,235 +800,6 @@ static int serial_read_proc (char *page, char **start, off_t off, int count, int
return ((count < begin+length-off) ? count : begin+length-off); return ((count < begin+length-off) ? count : begin+length-off);
} }
/*****************************************************************************
* generic devices specific driver functions
*****************************************************************************/
int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp)
{
struct usb_serial *serial = port->serial;
int result = 0;
if (port_paranoia_check (port, __FUNCTION__))
return -ENODEV;
dbg("%s - port %d", __FUNCTION__, port->number);
/* force low_latency on so that our tty_push actually forces the data through,
otherwise it is scheduled, and with high data rates (like with OHCI) data
can get lost. */
if (port->tty)
port->tty->low_latency = 1;
/* if we have a bulk interrupt, start reading from it */
if (serial->num_bulk_in) {
/* Start reading from the device */
usb_fill_bulk_urb (port->read_urb, serial->dev,
usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress),
port->read_urb->transfer_buffer,
port->read_urb->transfer_buffer_length,
((serial->type->read_bulk_callback) ?
serial->type->read_bulk_callback :
generic_read_bulk_callback),
port);
result = usb_submit_urb(port->read_urb, GFP_KERNEL);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
}
return result;
}
static void generic_cleanup (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->dev) {
/* shutdown any bulk reads that might be going on */
if (serial->num_bulk_out)
usb_unlink_urb (port->write_urb);
if (serial->num_bulk_in)
usb_unlink_urb (port->read_urb);
}
}
static void generic_close (struct usb_serial_port *port, struct file * filp)
{
dbg("%s - port %d", __FUNCTION__, port->number);
generic_cleanup (port);
}
int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count)
{
struct usb_serial *serial = port->serial;
int result;
dbg("%s - port %d", __FUNCTION__, port->number);
if (count == 0) {
dbg("%s - write request of 0 bytes", __FUNCTION__);
return (0);
}
/* only do something if we have a bulk out endpoint */
if (serial->num_bulk_out) {
if (port->write_urb->status == -EINPROGRESS) {
dbg("%s - already writing", __FUNCTION__);
return (0);
}
count = (count > port->bulk_out_size) ? port->bulk_out_size : count;
if (from_user) {
if (copy_from_user(port->write_urb->transfer_buffer, buf, count))
return -EFAULT;
}
else {
memcpy (port->write_urb->transfer_buffer, buf, count);
}
usb_serial_debug_data (__FILE__, __FUNCTION__, count, port->write_urb->transfer_buffer);
/* set up our urb */
usb_fill_bulk_urb (port->write_urb, serial->dev,
usb_sndbulkpipe (serial->dev,
port->bulk_out_endpointAddress),
port->write_urb->transfer_buffer, count,
((serial->type->write_bulk_callback) ?
serial->type->write_bulk_callback :
generic_write_bulk_callback), port);
/* send the data out the bulk port */
result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
if (result)
err("%s - failed submitting write urb, error %d", __FUNCTION__, result);
else
result = count;
return result;
}
/* no bulk out, so return 0 bytes written */
return (0);
}
static int generic_write_room (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
int room = 0;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->num_bulk_out) {
if (port->write_urb->status != -EINPROGRESS)
room = port->bulk_out_size;
}
dbg("%s - returns %d", __FUNCTION__, room);
return (room);
}
static int generic_chars_in_buffer (struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
int chars = 0;
dbg("%s - port %d", __FUNCTION__, port->number);
if (serial->num_bulk_out) {
if (port->write_urb->status == -EINPROGRESS)
chars = port->write_urb->transfer_buffer_length;
}
dbg("%s - returns %d", __FUNCTION__, chars);
return (chars);
}
static void generic_read_bulk_callback (struct urb *urb)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
struct tty_struct *tty;
unsigned char *data = urb->transfer_buffer;
int i;
int result;
dbg("%s - port %d", __FUNCTION__, port->number);
if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__);
return;
}
if (urb->status) {
dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status);
return;
}
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data);
tty = port->tty;
if (tty && urb->actual_length) {
for (i = 0; i < urb->actual_length ; ++i) {
/* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */
if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
tty_flip_buffer_push(tty);
}
/* this doesn't actually push the data through unless tty->low_latency is set */
tty_insert_flip_char(tty, data[i], 0);
}
tty_flip_buffer_push(tty);
}
/* Continue trying to always read */
usb_fill_bulk_urb (port->read_urb, serial->dev,
usb_rcvbulkpipe (serial->dev,
port->bulk_in_endpointAddress),
port->read_urb->transfer_buffer,
port->read_urb->transfer_buffer_length,
((serial->type->read_bulk_callback) ?
serial->type->read_bulk_callback :
generic_read_bulk_callback), port);
result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
}
static void generic_write_bulk_callback (struct urb *urb)
{
struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
dbg("%s - port %d", __FUNCTION__, port->number);
if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__);
return;
}
if (urb->status) {
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
return;
}
usb_serial_port_softint((void *)port);
schedule_work(&port->work);
}
static void generic_shutdown (struct usb_serial *serial)
{
int i;
dbg("%s", __FUNCTION__);
/* stop reads and writes on all ports */
for (i=0; i < serial->num_ports; ++i) {
generic_cleanup (&serial->port[i]);
}
}
void usb_serial_port_softint(void *private) void usb_serial_port_softint(void *private)
{ {
struct usb_serial_port *port = (struct usb_serial_port *)private; struct usb_serial_port *port = (struct usb_serial_port *)private;
...@@ -1268,7 +950,6 @@ int usb_serial_probe(struct usb_interface *interface, ...@@ -1268,7 +950,6 @@ int usb_serial_probe(struct usb_interface *interface,
(dev->descriptor.idProduct == PL2303_PRODUCT_ID)) || (dev->descriptor.idProduct == PL2303_PRODUCT_ID)) ||
((dev->descriptor.idVendor == ATEN_VENDOR_ID) && ((dev->descriptor.idVendor == ATEN_VENDOR_ID) &&
(dev->descriptor.idProduct == ATEN_PRODUCT_ID))) { (dev->descriptor.idProduct == ATEN_PRODUCT_ID))) {
//if (ifnum == 1) {
if (interface != &dev->actconfig->interface[0]) { if (interface != &dev->actconfig->interface[0]) {
/* check out the endpoints of the other interface*/ /* check out the endpoints of the other interface*/
//interface = &dev->actconfig->interface[ifnum ^ 1]; //interface = &dev->actconfig->interface[ifnum ^ 1];
...@@ -1303,7 +984,7 @@ int usb_serial_probe(struct usb_interface *interface, ...@@ -1303,7 +984,7 @@ int usb_serial_probe(struct usb_interface *interface,
info("%s converter detected", type->name); info("%s converter detected", type->name);
#ifdef CONFIG_USB_SERIAL_GENERIC #ifdef CONFIG_USB_SERIAL_GENERIC
if (type == &generic_device) { if (type == &usb_serial_generic_device) {
num_ports = num_bulk_out; num_ports = num_bulk_out;
if (num_ports == 0) { if (num_ports == 0) {
err("Generic device with no bulk out, not allowed."); err("Generic device with no bulk out, not allowed.");
...@@ -1359,7 +1040,7 @@ int usb_serial_probe(struct usb_interface *interface, ...@@ -1359,7 +1040,7 @@ int usb_serial_probe(struct usb_interface *interface,
port->bulk_in_buffer, buffer_size, port->bulk_in_buffer, buffer_size,
((serial->type->read_bulk_callback) ? ((serial->type->read_bulk_callback) ?
serial->type->read_bulk_callback : serial->type->read_bulk_callback :
generic_read_bulk_callback), usb_serial_generic_read_bulk_callback),
port); port);
} }
...@@ -1385,7 +1066,7 @@ int usb_serial_probe(struct usb_interface *interface, ...@@ -1385,7 +1066,7 @@ int usb_serial_probe(struct usb_interface *interface,
port->bulk_out_buffer, buffer_size, port->bulk_out_buffer, buffer_size,
((serial->type->write_bulk_callback) ? ((serial->type->write_bulk_callback) ?
serial->type->write_bulk_callback : serial->type->write_bulk_callback :
generic_write_bulk_callback), usb_serial_generic_write_bulk_callback),
port); port);
} }
...@@ -1504,10 +1185,10 @@ void usb_serial_disconnect(struct usb_interface *interface) ...@@ -1504,10 +1185,10 @@ void usb_serial_disconnect(struct usb_interface *interface)
port = &serial->port[i]; port = &serial->port[i];
down (&port->sem); down (&port->sem);
if (port->tty != NULL) { if (port->tty != NULL) {
port->tty->driver_data = NULL;
while (port->open_count > 0) { while (port->open_count > 0) {
__serial_close(port, NULL); __serial_close(port, NULL);
} }
port->tty->driver_data = NULL;
} }
up (&port->sem); up (&port->sem);
} }
...@@ -1607,7 +1288,7 @@ static int __init usb_serial_init(void) ...@@ -1607,7 +1288,7 @@ static int __init usb_serial_init(void)
} }
/* register the generic driver, if we should */ /* register the generic driver, if we should */
result = generic_register(); result = usb_serial_generic_register(debug);
if (result < 0) { if (result < 0) {
err("%s - registering generic driver failed", __FUNCTION__); err("%s - registering generic driver failed", __FUNCTION__);
goto exit; goto exit;
...@@ -1637,7 +1318,7 @@ static int __init usb_serial_init(void) ...@@ -1637,7 +1318,7 @@ static int __init usb_serial_init(void)
tty_unregister_driver(&serial_tty_driver); tty_unregister_driver(&serial_tty_driver);
exit_generic: exit_generic:
generic_deregister(); usb_serial_generic_deregister();
exit: exit:
err ("%s - returning with error %d", __FUNCTION__, result); err ("%s - returning with error %d", __FUNCTION__, result);
...@@ -1649,7 +1330,7 @@ static void __exit usb_serial_exit(void) ...@@ -1649,7 +1330,7 @@ static void __exit usb_serial_exit(void)
{ {
usb_serial_console_exit(); usb_serial_console_exit();
generic_deregister(); usb_serial_generic_deregister();
usb_deregister(&usb_serial_driver); usb_deregister(&usb_serial_driver);
tty_unregister_driver(&serial_tty_driver); tty_unregister_driver(&serial_tty_driver);
...@@ -1699,12 +1380,6 @@ EXPORT_SYMBOL(usb_serial_deregister); ...@@ -1699,12 +1380,6 @@ EXPORT_SYMBOL(usb_serial_deregister);
EXPORT_SYMBOL(usb_serial_probe); EXPORT_SYMBOL(usb_serial_probe);
EXPORT_SYMBOL(usb_serial_disconnect); EXPORT_SYMBOL(usb_serial_disconnect);
EXPORT_SYMBOL(usb_serial_port_softint); EXPORT_SYMBOL(usb_serial_port_softint);
#ifdef USES_EZUSB_FUNCTIONS
EXPORT_SYMBOL(ezusb_writememory);
EXPORT_SYMBOL(ezusb_set_reset);
#endif
/* Module information */ /* Module information */
...@@ -1714,11 +1389,3 @@ MODULE_LICENSE("GPL"); ...@@ -1714,11 +1389,3 @@ MODULE_LICENSE("GPL");
MODULE_PARM(debug, "i"); MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not"); MODULE_PARM_DESC(debug, "Debug enabled or not");
#ifdef CONFIG_USB_SERIAL_GENERIC
MODULE_PARM(vendor, "h");
MODULE_PARM_DESC(vendor, "User specified USB idVendor");
MODULE_PARM(product, "h");
MODULE_PARM_DESC(product, "User specified USB idProduct");
#endif
...@@ -237,24 +237,8 @@ extern void usb_serial_port_softint(void *private); ...@@ -237,24 +237,8 @@ extern void usb_serial_port_softint(void *private);
extern int usb_serial_probe(struct usb_interface *iface, const struct usb_device_id *id); extern int usb_serial_probe(struct usb_interface *iface, const struct usb_device_id *id);
extern void usb_serial_disconnect(struct usb_interface *iface); extern void usb_serial_disconnect(struct usb_interface *iface);
/* determine if we should include the EzUSB loader functions */
#undef USES_EZUSB_FUNCTIONS
#if defined(CONFIG_USB_SERIAL_KEYSPAN_PDA) || defined(CONFIG_USB_SERIAL_KEYSPAN_PDA_MODULE)
#define USES_EZUSB_FUNCTIONS
#endif
#if defined(CONFIG_USB_SERIAL_XIRCOM) || defined(CONFIG_USB_SERIAL_XIRCOM_MODULE)
#define USES_EZUSB_FUNCTIONS
#endif
#if defined(CONFIG_USB_SERIAL_KEYSPAN) || defined(CONFIG_USB_SERIAL_KEYSPAN_MODULE)
#define USES_EZUSB_FUNCTIONS
#endif
#if defined(CONFIG_USB_SERIAL_WHITEHEAT) || defined(CONFIG_USB_SERIAL_WHITEHEAT_MODULE)
#define USES_EZUSB_FUNCTIONS
#endif
#ifdef USES_EZUSB_FUNCTIONS
extern int ezusb_writememory (struct usb_serial *serial, int address, unsigned char *data, int length, __u8 bRequest); extern int ezusb_writememory (struct usb_serial *serial, int address, unsigned char *data, int length, __u8 bRequest);
extern int ezusb_set_reset (struct usb_serial *serial, unsigned char reset_bit); extern int ezusb_set_reset (struct usb_serial *serial, unsigned char reset_bit);
#endif
/* USB Serial console functions */ /* USB Serial console functions */
#ifdef CONFIG_USB_SERIAL_CONSOLE #ifdef CONFIG_USB_SERIAL_CONSOLE
...@@ -265,10 +249,20 @@ static inline void usb_serial_console_init (int debug, int minor) { } ...@@ -265,10 +249,20 @@ static inline void usb_serial_console_init (int debug, int minor) { }
static inline void usb_serial_console_exit (void) { } static inline void usb_serial_console_exit (void) { }
#endif #endif
/* Functions needed by the usb serial console code */ /* Functions needed by other parts of the usbserial core */
extern struct usb_serial *usb_serial_get_by_minor (unsigned int minor); extern struct usb_serial *usb_serial_get_by_minor (unsigned int minor);
extern int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp); extern int usb_serial_generic_open (struct usb_serial_port *port, struct file *filp);
extern int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); extern int usb_serial_generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count);
extern void usb_serial_generic_close (struct usb_serial_port *port, struct file *filp);
extern int usb_serial_generic_write_room (struct usb_serial_port *port);
extern int usb_serial_generic_chars_in_buffer (struct usb_serial_port *port);
extern void usb_serial_generic_read_bulk_callback (struct urb *urb);
extern void usb_serial_generic_write_bulk_callback (struct urb *urb);
extern void usb_serial_generic_shutdown (struct usb_serial *serial);
extern int usb_serial_generic_register (int debug);
extern void usb_serial_generic_deregister (void);
extern struct usb_serial_device_type usb_serial_generic_device;
/* Inline functions to check the sanity of a pointer that is passed to us */ /* Inline functions to check the sanity of a pointer that is passed to us */
static inline int serial_paranoia_check (struct usb_serial *serial, const char *function) static inline int serial_paranoia_check (struct usb_serial *serial, const char *function)
......
...@@ -186,6 +186,7 @@ static struct usb_device_id id_table [] = { ...@@ -186,6 +186,7 @@ static struct usb_device_id id_table [] = {
{ USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) },
{ USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) }, { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) },
{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) },
...@@ -209,6 +210,7 @@ static struct usb_device_id id_table_combined [] = { ...@@ -209,6 +210,7 @@ static struct usb_device_id id_table_combined [] = {
{ USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID) },
{ USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) },
{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) },
{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) },
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#define PALM_M125_ID 0x0040 #define PALM_M125_ID 0x0040
#define PALM_M130_ID 0x0050 #define PALM_M130_ID 0x0050
#define PALM_TUNGSTEN_T_ID 0x0060 #define PALM_TUNGSTEN_T_ID 0x0060
#define PALM_TUNGSTEN_Z_ID 0x0031
#define PALM_ZIRE_ID 0x0070 #define PALM_ZIRE_ID 0x0070
#define SONY_VENDOR_ID 0x054C #define SONY_VENDOR_ID 0x054C
......
...@@ -202,10 +202,23 @@ struct whiteheat_command_private { ...@@ -202,10 +202,23 @@ struct whiteheat_command_private {
#define THROTTLED 0x01 #define THROTTLED 0x01
#define ACTUALLY_THROTTLED 0x02 #define ACTUALLY_THROTTLED 0x02
static int urb_pool_size = 8;
struct whiteheat_urb_wrap {
struct list_head list;
struct urb *urb;
};
struct whiteheat_private { struct whiteheat_private {
spinlock_t lock; spinlock_t lock;
__u8 flags; __u8 flags;
__u8 mcr; __u8 mcr;
struct list_head rx_urbs_free;
struct list_head rx_urbs_submitted;
struct list_head rx_urb_q;
struct work_struct rx_work;
struct list_head tx_urbs_free;
struct list_head tx_urbs_submitted;
}; };
...@@ -215,6 +228,11 @@ static void stop_command_port(struct usb_serial *serial); ...@@ -215,6 +228,11 @@ static void stop_command_port(struct usb_serial *serial);
static void command_port_write_callback(struct urb *urb); static void command_port_write_callback(struct urb *urb);
static void command_port_read_callback(struct urb *urb); static void command_port_read_callback(struct urb *urb);
static int start_port_read(struct usb_serial_port *port);
static struct whiteheat_urb_wrap *urb_to_wrap(struct urb *urb, struct list_head *head);
static struct list_head *list_first(struct list_head *head);
static void rx_data_softint(void *private);
static int firm_send_command(struct usb_serial_port *port, __u8 command, __u8 *data, __u8 datasize); static int firm_send_command(struct usb_serial_port *port, __u8 command, __u8 *data, __u8 datasize);
static int firm_open(struct usb_serial_port *port); static int firm_open(struct usb_serial_port *port);
static int firm_close(struct usb_serial_port *port); static int firm_close(struct usb_serial_port *port);
...@@ -330,6 +348,11 @@ static int whiteheat_attach (struct usb_serial *serial) ...@@ -330,6 +348,11 @@ static int whiteheat_attach (struct usb_serial *serial)
__u8 command[2] = { WHITEHEAT_GET_HW_INFO, 0 }; __u8 command[2] = { WHITEHEAT_GET_HW_INFO, 0 };
__u8 result[sizeof(*hw_info) + 1]; __u8 result[sizeof(*hw_info) + 1];
int i; int i;
int j;
struct urb *urb;
int buf_size;
struct whiteheat_urb_wrap *wrap;
struct list_head *tmp;
command_port = &serial->port[COMMAND_PORT]; command_port = &serial->port[COMMAND_PORT];
...@@ -373,18 +396,80 @@ static int whiteheat_attach (struct usb_serial *serial) ...@@ -373,18 +396,80 @@ static int whiteheat_attach (struct usb_serial *serial)
port = &serial->port[i]; port = &serial->port[i];
info = (struct whiteheat_private *)kmalloc(sizeof(struct whiteheat_private), GFP_KERNEL); info = (struct whiteheat_private *)kmalloc(sizeof(struct whiteheat_private), GFP_KERNEL);
if (info == NULL) if (info == NULL) {
goto no_memory; err("%s: Out of memory for port structures\n", serial->type->name);
goto no_private;
}
spin_lock_init(&info->lock); spin_lock_init(&info->lock);
info->flags = 0; info->flags = 0;
info->mcr = 0; info->mcr = 0;
INIT_WORK(&info->rx_work, rx_data_softint, port);
INIT_LIST_HEAD(&info->rx_urbs_free);
INIT_LIST_HEAD(&info->rx_urbs_submitted);
INIT_LIST_HEAD(&info->rx_urb_q);
INIT_LIST_HEAD(&info->tx_urbs_free);
INIT_LIST_HEAD(&info->tx_urbs_submitted);
for (j = 0; j < urb_pool_size; j++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
err("No free urbs available");
goto no_rx_urb;
}
buf_size = port->read_urb->transfer_buffer_length;
urb->transfer_buffer = kmalloc(buf_size, GFP_KERNEL);
if (!urb->transfer_buffer) {
err("Couldn't allocate urb buffer");
goto no_rx_buf;
}
wrap = kmalloc(sizeof(*wrap), GFP_KERNEL);
if (!wrap) {
err("Couldn't allocate urb wrapper");
goto no_rx_wrap;
}
usb_fill_bulk_urb(urb, serial->dev,
usb_rcvbulkpipe(serial->dev,
port->bulk_in_endpointAddress),
urb->transfer_buffer, buf_size,
whiteheat_read_callback, port);
wrap->urb = urb;
list_add(&wrap->list, &info->rx_urbs_free);
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
err("No free urbs available");
goto no_tx_urb;
}
buf_size = port->write_urb->transfer_buffer_length;
urb->transfer_buffer = kmalloc(buf_size, GFP_KERNEL);
if (!urb->transfer_buffer) {
err("Couldn't allocate urb buffer");
goto no_tx_buf;
}
wrap = kmalloc(sizeof(*wrap), GFP_KERNEL);
if (!wrap) {
err("Couldn't allocate urb wrapper");
goto no_tx_wrap;
}
usb_fill_bulk_urb(urb, serial->dev,
usb_sndbulkpipe(serial->dev,
port->bulk_out_endpointAddress),
urb->transfer_buffer, buf_size,
whiteheat_write_callback, port);
wrap->urb = urb;
list_add(&wrap->list, &info->tx_urbs_free);
}
port->private = info; port->private = info;
} }
command_info = (struct whiteheat_command_private *)kmalloc(sizeof(struct whiteheat_command_private), GFP_KERNEL); command_info = (struct whiteheat_command_private *)kmalloc(sizeof(struct whiteheat_command_private), GFP_KERNEL);
if (command_info == NULL) if (command_info == NULL) {
goto no_memory; err("%s: Out of memory for port structures\n", serial->type->name);
goto no_command_private;
}
spin_lock_init(&command_info->lock); spin_lock_init(&command_info->lock);
command_info->port_running = 0; command_info->port_running = 0;
...@@ -402,12 +487,35 @@ static int whiteheat_attach (struct usb_serial *serial) ...@@ -402,12 +487,35 @@ static int whiteheat_attach (struct usb_serial *serial)
err("%s: please contact support@connecttech.com\n", serial->type->name); err("%s: please contact support@connecttech.com\n", serial->type->name);
return -ENODEV; return -ENODEV;
no_memory: no_command_private:
for (i--; i >= 0; i--) { for (i = serial->num_ports - 1; i >= 0; i--) {
port = &serial->port[i]; port = &serial->port[i];
kfree(port->private); info = port->private;
for (j = urb_pool_size - 1; j >= 0; j--) {
tmp = list_first(&info->tx_urbs_free);
list_del(tmp);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
kfree(wrap);
no_tx_wrap:
kfree(urb->transfer_buffer);
no_tx_buf:
usb_free_urb(urb);
no_tx_urb:
tmp = list_first(&info->rx_urbs_free);
list_del(tmp);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
kfree(wrap);
no_rx_wrap:
kfree(urb->transfer_buffer);
no_rx_buf:
usb_free_urb(urb);
no_rx_urb:
}
kfree(info);
no_private:
} }
err("%s: Out of memory for port structures\n", serial->type->name);
return -ENOMEM; return -ENOMEM;
} }
...@@ -416,6 +524,11 @@ static void whiteheat_shutdown (struct usb_serial *serial) ...@@ -416,6 +524,11 @@ static void whiteheat_shutdown (struct usb_serial *serial)
{ {
struct usb_serial_port *command_port; struct usb_serial_port *command_port;
struct usb_serial_port *port; struct usb_serial_port *port;
struct whiteheat_private *info;
struct whiteheat_urb_wrap *wrap;
struct urb *urb;
struct list_head *tmp;
struct list_head *tmp2;
int i; int i;
dbg("%s", __FUNCTION__); dbg("%s", __FUNCTION__);
...@@ -423,12 +536,27 @@ static void whiteheat_shutdown (struct usb_serial *serial) ...@@ -423,12 +536,27 @@ static void whiteheat_shutdown (struct usb_serial *serial)
/* free up our private data for our command port */ /* free up our private data for our command port */
command_port = &serial->port[COMMAND_PORT]; command_port = &serial->port[COMMAND_PORT];
kfree (command_port->private); kfree (command_port->private);
command_port->private = NULL;
for (i = 0; i < serial->num_ports; i++) { for (i = 0; i < serial->num_ports; i++) {
port = &serial->port[i]; port = &serial->port[i];
kfree(port->private); info = port->private;
port->private = NULL; list_for_each_safe(tmp, tmp2, &info->rx_urbs_free) {
list_del(tmp);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
kfree(wrap);
kfree(urb->transfer_buffer);
usb_free_urb(urb);
}
list_for_each_safe(tmp, tmp2, &info->tx_urbs_free) {
list_del(tmp);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
kfree(wrap);
kfree(urb->transfer_buffer);
usb_free_urb(urb);
}
kfree(info);
} }
return; return;
...@@ -446,6 +574,9 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp) ...@@ -446,6 +574,9 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp)
if (retval) if (retval)
goto exit; goto exit;
if (port->tty)
port->tty->low_latency = 1;
/* send an open port command */ /* send an open port command */
retval = firm_open(port); retval = firm_open(port);
if (retval) { if (retval) {
...@@ -469,8 +600,7 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp) ...@@ -469,8 +600,7 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp)
usb_clear_halt(port->serial->dev, port->write_urb->pipe); usb_clear_halt(port->serial->dev, port->write_urb->pipe);
/* Start reading from the device */ /* Start reading from the device */
port->read_urb->dev = port->serial->dev; retval = start_port_read(port);
retval = usb_submit_urb(port->read_urb, GFP_KERNEL);
if (retval) { if (retval) {
err("%s - failed submitting read urb, error %d", __FUNCTION__, retval); err("%s - failed submitting read urb, error %d", __FUNCTION__, retval);
firm_close(port); firm_close(port);
...@@ -486,6 +616,13 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp) ...@@ -486,6 +616,13 @@ static int whiteheat_open (struct usb_serial_port *port, struct file *filp)
static void whiteheat_close(struct usb_serial_port *port, struct file * filp) static void whiteheat_close(struct usb_serial_port *port, struct file * filp)
{ {
struct whiteheat_private *info = port->private;
struct whiteheat_urb_wrap *wrap;
struct urb *urb;
struct list_head *tmp;
struct list_head *tmp2;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
/* filp is NULL when called from usb_serial_disconnect */ /* filp is NULL when called from usb_serial_disconnect */
...@@ -515,8 +652,26 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp) ...@@ -515,8 +652,26 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp)
firm_close(port); firm_close(port);
/* shutdown our bulk reads and writes */ /* shutdown our bulk reads and writes */
usb_unlink_urb (port->write_urb); spin_lock_irqsave(&info->lock, flags);
usb_unlink_urb (port->read_urb); list_for_each_safe(tmp, tmp2, &info->rx_urbs_submitted) {
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
usb_unlink_urb(urb);
list_del(tmp);
list_add(tmp, &info->rx_urbs_free);
}
list_for_each_safe(tmp, tmp2, &info->rx_urb_q) {
list_del(tmp);
list_add(tmp, &info->rx_urbs_free);
}
list_for_each_safe(tmp, tmp2, &info->tx_urbs_submitted) {
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
usb_unlink_urb(urb);
list_del(tmp);
list_add(tmp, &info->tx_urbs_free);
}
spin_unlock_irqrestore(&info->lock, flags);
stop_command_port(port->serial); stop_command_port(port->serial);
...@@ -527,7 +682,14 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp) ...@@ -527,7 +682,14 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp)
static int whiteheat_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) static int whiteheat_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count)
{ {
struct usb_serial *serial = port->serial; struct usb_serial *serial = port->serial;
struct whiteheat_private *info = port->private;
struct whiteheat_urb_wrap *wrap;
struct urb *urb;
int result; int result;
int bytes;
int sent = 0;
unsigned long flags;
struct list_head *tmp;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
...@@ -536,43 +698,65 @@ static int whiteheat_write(struct usb_serial_port *port, int from_user, const un ...@@ -536,43 +698,65 @@ static int whiteheat_write(struct usb_serial_port *port, int from_user, const un
return (0); return (0);
} }
if (port->write_urb->status == -EINPROGRESS) { while (count) {
dbg ("%s - already writing", __FUNCTION__); spin_lock_irqsave(&info->lock, flags);
return (0); if (list_empty(&info->tx_urbs_free)) {
} spin_unlock_irqrestore(&info->lock, flags);
break;
}
tmp = list_first(&info->tx_urbs_free);
list_del(tmp);
spin_unlock_irqrestore(&info->lock, flags);
count = (count > port->bulk_out_size) ? port->bulk_out_size : count; wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
bytes = (count > port->bulk_out_size) ? port->bulk_out_size : count;
if (from_user) {
if (copy_from_user(urb->transfer_buffer, buf + sent, bytes))
return -EFAULT;
} else {
memcpy (urb->transfer_buffer, buf + sent, bytes);
}
if (from_user) { usb_serial_debug_data (__FILE__, __FUNCTION__, bytes, urb->transfer_buffer);
if (copy_from_user(port->write_urb->transfer_buffer, buf, count))
return -EFAULT; urb->dev = serial->dev;
} urb->transfer_buffer_length = bytes;
else { result = usb_submit_urb(urb, GFP_ATOMIC);
memcpy (port->write_urb->transfer_buffer, buf, count); if (result) {
err("%s - failed submitting write urb, error %d", __FUNCTION__, result);
sent = result;
spin_lock_irqsave(&info->lock, flags);
list_add(tmp, &info->tx_urbs_free);
spin_unlock_irqrestore(&info->lock, flags);
break;
} else {
sent += bytes;
count -= bytes;
spin_lock_irqsave(&info->lock, flags);
list_add(tmp, &info->tx_urbs_submitted);
spin_unlock_irqrestore(&info->lock, flags);
}
} }
usb_serial_debug_data (__FILE__, __FUNCTION__, count, port->write_urb->transfer_buffer); return sent;
port->write_urb->dev = serial->dev;
port->write_urb->transfer_buffer_length = count;
result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
if (result)
err("%s - failed submitting write urb, error %d", __FUNCTION__, result);
else
result = count;
return result;
} }
static int whiteheat_write_room(struct usb_serial_port *port) static int whiteheat_write_room(struct usb_serial_port *port)
{ {
struct whiteheat_private *info = port->private;
struct list_head *tmp;
int room = 0; int room = 0;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
if (port->write_urb->status != -EINPROGRESS) spin_lock_irqsave(&info->lock, flags);
room = port->bulk_out_size; list_for_each(tmp, &info->tx_urbs_free)
room++;
spin_unlock_irqrestore(&info->lock, flags);
room *= port->bulk_out_size;
dbg("%s - returns %d", __FUNCTION__, room); dbg("%s - returns %d", __FUNCTION__, room);
return (room); return (room);
...@@ -715,12 +899,20 @@ static void whiteheat_break_ctl(struct usb_serial_port *port, int break_state) { ...@@ -715,12 +899,20 @@ static void whiteheat_break_ctl(struct usb_serial_port *port, int break_state) {
static int whiteheat_chars_in_buffer(struct usb_serial_port *port) static int whiteheat_chars_in_buffer(struct usb_serial_port *port)
{ {
struct whiteheat_private *info = port->private;
struct list_head *tmp;
struct whiteheat_urb_wrap *wrap;
int chars = 0; int chars = 0;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
if (port->write_urb->status == -EINPROGRESS) spin_lock_irqsave(&info->lock, flags);
chars = port->write_urb->transfer_buffer_length; list_for_each(tmp, &info->tx_urbs_submitted) {
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
chars += wrap->urb->transfer_buffer_length;
}
spin_unlock_irqrestore(&info->lock, flags);
dbg ("%s - returns %d", __FUNCTION__, chars); dbg ("%s - returns %d", __FUNCTION__, chars);
return (chars); return (chars);
...@@ -745,25 +937,19 @@ static void whiteheat_throttle (struct usb_serial_port *port) ...@@ -745,25 +937,19 @@ static void whiteheat_throttle (struct usb_serial_port *port)
static void whiteheat_unthrottle (struct usb_serial_port *port) static void whiteheat_unthrottle (struct usb_serial_port *port)
{ {
struct whiteheat_private *info = (struct whiteheat_private *)port->private; struct whiteheat_private *info = (struct whiteheat_private *)port->private;
int result; int actually_throttled;
unsigned long flags; unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock_irqsave(&info->lock, flags); spin_lock_irqsave(&info->lock, flags);
actually_throttled = info->flags & ACTUALLY_THROTTLED;
if (info->flags & ACTUALLY_THROTTLED) {
/* Continue trying to always read */
port->read_urb->dev = port->serial->dev;
result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
}
info->flags &= ~(THROTTLED | ACTUALLY_THROTTLED); info->flags &= ~(THROTTLED | ACTUALLY_THROTTLED);
spin_unlock_irqrestore(&info->lock, flags); spin_unlock_irqrestore(&info->lock, flags);
if (actually_throttled)
rx_data_softint(port);
return; return;
} }
...@@ -846,53 +1032,50 @@ static void whiteheat_read_callback(struct urb *urb) ...@@ -846,53 +1032,50 @@ static void whiteheat_read_callback(struct urb *urb)
{ {
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
struct tty_struct *tty; struct whiteheat_urb_wrap *wrap;
unsigned char *data = urb->transfer_buffer; unsigned char *data = urb->transfer_buffer;
int i;
int result;
struct whiteheat_private *info = (struct whiteheat_private *)port->private; struct whiteheat_private *info = (struct whiteheat_private *)port->private;
unsigned long flags;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock(&info->lock);
wrap = urb_to_wrap(urb, &info->rx_urbs_submitted);
if (!wrap) {
spin_unlock(&info->lock);
err("%s - Not my urb!", __FUNCTION__);
return;
}
list_del(&wrap->list);
spin_unlock(&info->lock);
if (!serial) { if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__); dbg("%s - bad serial pointer, exiting", __FUNCTION__);
spin_lock(&info->lock);
list_add(&wrap->list, &info->rx_urbs_free);
spin_unlock(&info->lock);
return; return;
} }
if (urb->status) { if (urb->status) {
dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status); dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status);
spin_lock(&info->lock);
list_add(&wrap->list, &info->rx_urbs_free);
spin_unlock(&info->lock);
return; return;
} }
usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data);
tty = port->tty; spin_lock(&info->lock);
if (tty && urb->actual_length) { list_add_tail(&wrap->list, &info->rx_urb_q);
for (i = 0; i < urb->actual_length ; ++i) {
/* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */
if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
tty_flip_buffer_push(tty);
}
/* this doesn't actually push the data through unless tty->low_latency is set */
tty_insert_flip_char(tty, data[i], 0);
}
tty_flip_buffer_push(tty);
}
spin_lock_irqsave(&info->lock, flags);
if (info->flags & THROTTLED) { if (info->flags & THROTTLED) {
info->flags |= ACTUALLY_THROTTLED; info->flags |= ACTUALLY_THROTTLED;
spin_unlock_irqrestore(&info->lock, flags); spin_unlock(&info->lock);
return; return;
} }
spin_unlock_irqrestore(&info->lock, flags); spin_unlock(&info->lock);
/* Continue trying to always read */ schedule_work(&info->rx_work);
port->read_urb->dev = serial->dev;
result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
if (result)
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
} }
...@@ -900,9 +1083,22 @@ static void whiteheat_write_callback(struct urb *urb) ...@@ -900,9 +1083,22 @@ static void whiteheat_write_callback(struct urb *urb)
{ {
struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
struct whiteheat_private *info = port->private;
struct whiteheat_urb_wrap *wrap;
dbg("%s - port %d", __FUNCTION__, port->number); dbg("%s - port %d", __FUNCTION__, port->number);
spin_lock(&info->lock);
wrap = urb_to_wrap(urb, &info->tx_urbs_submitted);
if (!wrap) {
spin_unlock(&info->lock);
err("%s - Not my urb!", __FUNCTION__);
return;
}
list_del(&wrap->list);
list_add(&wrap->list, &info->tx_urbs_free);
spin_unlock(&info->lock);
if (!serial) { if (!serial) {
dbg("%s - bad serial pointer, exiting", __FUNCTION__); dbg("%s - bad serial pointer, exiting", __FUNCTION__);
return; return;
...@@ -1176,6 +1372,122 @@ static void stop_command_port(struct usb_serial *serial) ...@@ -1176,6 +1372,122 @@ static void stop_command_port(struct usb_serial *serial)
} }
static int start_port_read(struct usb_serial_port *port) {
struct whiteheat_private *info = port->private;
struct whiteheat_urb_wrap *wrap;
struct urb *urb;
int retval = 0;
unsigned long flags;
struct list_head *tmp;
struct list_head *tmp2;
spin_lock_irqsave(&info->lock, flags);
list_for_each_safe(tmp, tmp2, &info->rx_urbs_free) {
list_del(tmp);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
urb->dev = port->serial->dev;
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
list_add(tmp, &info->rx_urbs_free);
list_for_each_safe(tmp, tmp2, &info->rx_urbs_submitted) {
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
usb_unlink_urb(urb);
list_del(tmp);
list_add(tmp, &info->rx_urbs_free);
}
break;
}
list_add(tmp, &info->rx_urbs_submitted);
}
spin_unlock_irqrestore(&info->lock, flags);
return retval;
}
static struct whiteheat_urb_wrap *urb_to_wrap(struct urb* urb, struct list_head *head) {
struct whiteheat_urb_wrap *wrap;
struct list_head *tmp;
list_for_each(tmp, head) {
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
if (wrap->urb == urb)
return wrap;
}
return NULL;
}
static struct list_head *list_first(struct list_head *head) {
return head->next;
}
static void rx_data_softint(void *private) {
struct usb_serial_port *port = (struct usb_serial_port *)private;
struct whiteheat_private *info = port->private;
struct tty_struct *tty = port->tty;
struct whiteheat_urb_wrap *wrap;
struct urb *urb;
unsigned long flags;
struct list_head *tmp;
struct list_head *tmp2;
int result;
int sent = 0;
spin_lock_irqsave(&info->lock, flags);
if (info->flags & THROTTLED) {
spin_unlock_irqrestore(&info->lock, flags);
return;
}
list_for_each_safe(tmp, tmp2, &info->rx_urb_q) {
list_del(tmp);
spin_unlock_irqrestore(&info->lock, flags);
wrap = list_entry(tmp, struct whiteheat_urb_wrap, list);
urb = wrap->urb;
if (tty && urb->actual_length) {
if (urb->actual_length > TTY_FLIPBUF_SIZE - tty->flip.count) {
spin_lock_irqsave(&info->lock, flags);
list_add(tmp, &info->rx_urb_q);
spin_unlock_irqrestore(&info->lock, flags);
tty_flip_buffer_push(tty);
schedule_work(&info->rx_work);
return;
}
memcpy(tty->flip.char_buf_ptr, urb->transfer_buffer, urb->actual_length);
tty->flip.char_buf_ptr += urb->actual_length;
tty->flip.count += urb->actual_length;
sent += urb->actual_length;
}
urb->dev = port->serial->dev;
result = usb_submit_urb(urb, GFP_ATOMIC);
if (result) {
err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
spin_lock_irqsave(&info->lock, flags);
list_add(tmp, &info->rx_urbs_free);
continue;
}
spin_lock_irqsave(&info->lock, flags);
list_add(tmp, &info->rx_urbs_submitted);
}
spin_unlock_irqrestore(&info->lock, flags);
if (sent)
tty_flip_buffer_push(tty);
}
/***************************************************************************** /*****************************************************************************
* Connect Tech's White Heat module functions * Connect Tech's White Heat module functions
*****************************************************************************/ *****************************************************************************/
...@@ -1204,5 +1516,8 @@ MODULE_AUTHOR( DRIVER_AUTHOR ); ...@@ -1204,5 +1516,8 @@ MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC ); MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_PARM(urb_pool_size, "i");
MODULE_PARM_DESC(urb_pool_size, "Number of urbs to use for buffering");
MODULE_PARM(debug, "i"); MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not"); MODULE_PARM_DESC(debug, "Debug enabled or not");
...@@ -901,14 +901,11 @@ extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); ...@@ -901,14 +901,11 @@ extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate);
/* /*
* timeouts, in seconds, used for sending/receiving control messages * timeouts, in seconds, used for sending/receiving control messages
* they typically complete within a few frames (msec) after they're issued * they typically complete within a few frames (msec) after they're issued
* USB identifies 5 second timeouts, maybe more in a few cases, and a few
* slow devices (like some MGE Ellipse UPSes) actually push that limit.
*/ */
#ifdef CONFIG_USB_LONG_TIMEOUT #define USB_CTRL_GET_TIMEOUT 5
#define USB_CTRL_GET_TIMEOUT 4 #define USB_CTRL_SET_TIMEOUT 5
#else
#define USB_CTRL_GET_TIMEOUT 3
#endif
#define USB_CTRL_SET_TIMEOUT 3
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment