ChangeSet 1.1504.2.48, 2003/12/12 14:13:03-08:00, nemosoft@smcc.demon.nl [PATCH] USB: PWC 8.12 driver update Attached you will find patches that will bring the Philips Webcam driver (PWC) up to version 8.12. The most important new feature is support for the motorized pan & tilt feature of the new Logitech QuickCam Orbit/Sphere, which I don't think is in the stores yet (at least it's not on Logitech's website), but should be there soon. In addition, the documentation in the kernel about the cams is updated. drivers/usb/media/Kconfig | 15 +-- drivers/usb/media/pwc-ctrl.c | 173 +++++++++++++++++++++++++++++++++++++++++- drivers/usb/media/pwc-if.c | 75 ++++++++++++++---- drivers/usb/media/pwc-ioctl.h | 44 ++++++++++ drivers/usb/media/pwc-misc.c | 31 +++++-- drivers/usb/media/pwc.h | 22 ++++- 6 files changed, 324 insertions(+), 36 deletions(-) diff -Nru a/drivers/usb/media/Kconfig b/drivers/usb/media/Kconfig --- a/drivers/usb/media/Kconfig Mon Dec 29 14:21:37 2003 +++ b/drivers/usb/media/Kconfig Mon Dec 29 14:21:37 2003 @@ -113,16 +113,17 @@ webcams: * Philips PCA645, PCA646 * Philips PCVC675, PCVC680, PCVC690 - * Philips PCVC730, PCVC740, PCVC750 + * Philips PCVC720/40, PCVC730, PCVC740, PCVC750 * Askey VC010 - * Logitech QuickCam Pro 3000, 4000, 'Zoom' and 'Notebook' - * Samsung MPC-C10, MPC-C30 - * Creative Webcam 5 - * SOTECT Afina Eye + * Logitech QuickCam Pro 3000, 4000, 'Zoom', 'Notebook Pro' + and 'Orbit'/'Sphere' + * Samsung MPC-C10, MPC-C30 + * Creative Webcam 5, Pro Ex + * SOTEC 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 + The PCA635, PCVC665 and PCVC720/20 are not supported by this driver + and never will be, but the 665 and 720/20 are supported by other drivers. This driver has an optional plugin (called PWCX), which is diff -Nru a/drivers/usb/media/pwc-ctrl.c b/drivers/usb/media/pwc-ctrl.c --- a/drivers/usb/media/pwc-ctrl.c Mon Dec 29 14:21:38 2003 +++ b/drivers/usb/media/pwc-ctrl.c Mon Dec 29 14:21:38 2003 @@ -44,6 +44,8 @@ #define GET_STATUS_CTL 0x06 #define SET_EP_STREAM_CTL 0x07 #define GET_EP_STREAM_CTL 0x08 +#define SET_MPT_CTL 0x0D +#define GET_MPT_CTL 0x0E /* Selectors for the Luminance controls [GS]ET_LUM_CTL */ #define AGC_MODE_FORMATTER 0x2000 @@ -88,6 +90,11 @@ /* Formatters for the Video Endpoint controls [GS]ET_EP_STREAM_CTL */ #define VIDEO_OUTPUT_CONTROL_FORMATTER 0x0100 +/* Formatters for the motorized pan & tilt [GS]ET_MPT_CTL */ +#define PT_RELATIVE_CONTROL_FORMATTER 0x01 +#define PT_RESET_CONTROL_FORMATTER 0x02 +#define PT_STATUS_FORMATTER 0x03 + static char *size2name[PSZ_MAX] = { "subQCIF", @@ -435,6 +442,7 @@ ret = set_video_mode_Timon(pdev, size, frames, compression, snapshot); break; + case 720: case 730: case 740: case 750: @@ -745,6 +753,7 @@ buf[1] = speed >> 8; buf[0] = speed & 0xff; break; + case 720: case 730: case 740: case 750: @@ -1243,6 +1252,46 @@ return buf; } +int pwc_mpt_reset(struct pwc_device *pdev, int flags) +{ + unsigned char buf; + + buf = flags & 0x03; // only lower two bits are currently used + return SendControlMsg(SET_MPT_CTL, PT_RESET_CONTROL_FORMATTER, 1); +} + +static inline int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt) +{ + unsigned char buf[4]; + + /* set new relative angle; angles are expressed in degrees * 100, + but cam as .5 degree resolution, hence devide by 200. Also + the angle must be multiplied by 64 before it's send to + the cam (??) + */ + pan = 64 * pan / 100; + tilt = -64 * tilt / 100; /* positive tilt is down, which is not what the user would expect */ + buf[0] = pan & 0xFF; + buf[1] = (pan >> 8) & 0xFF; + buf[2] = tilt & 0xFF; + buf[3] = (tilt >> 8) & 0xFF; + return SendControlMsg(SET_MPT_CTL, PT_RELATIVE_CONTROL_FORMATTER, 4); +} + +static inline int pwc_mpt_get_status(struct pwc_device *pdev, struct pwc_mpt_status *status) +{ + int ret; + unsigned char buf[5]; + + ret = RecvControlMsg(GET_MPT_CTL, PT_STATUS_FORMATTER, 5); + if (ret < 0) + return ret; + status->status = buf[0] & 0x7; // 3 bits are used for reporting + status->time_pan = (buf[1] << 8) + buf[2]; + status->time_tilt = (buf[3] << 8) + buf[4]; + return 0; +} + int pwc_get_cmos_sensor(struct pwc_device *pdev) { @@ -1512,8 +1561,128 @@ size->width = pdev->image.x; size->height = pdev->image.y; break; - } - + } + + case VIDIOCPWCMPTRESET: + { + int *flags = arg; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + ret = pwc_mpt_reset(pdev, *flags); + if (ret >= 0) + { + pdev->pan_angle = 0; + pdev->tilt_angle = 0; + } + } + else + { + ret = -ENXIO; + } + break; + } + case VIDIOCPWCMPTGRANGE: + { + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + memcpy(arg, &pdev->angle_range, sizeof(struct pwc_mpt_range)); + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSANGLE: + { + struct pwc_mpt_angles *angles = arg; + int new_pan, new_tilt; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + /* The camera can only set relative angles, so + do some calculations when getting an absolute angle . + */ + if (angles->absolute) + { + new_pan = angles->pan; + new_tilt = angles->tilt; + } + else + { + new_pan = pdev->pan_angle + angles->pan; + new_tilt = pdev->tilt_angle + angles->tilt; + } + /* check absolute ranges */ + if (new_pan < pdev->angle_range.pan_min || + new_pan > pdev->angle_range.pan_max || + new_tilt < pdev->angle_range.tilt_min || + new_tilt > pdev->angle_range.tilt_max) + { + ret = -ERANGE; + } + else + { + /* go to relative range, check again */ + new_pan -= pdev->pan_angle; + new_tilt -= pdev->tilt_angle; + /* angles are specified in degrees * 100, thus the limit = 36000 */ + if (new_pan < -36000 || new_pan > 36000 || new_tilt < -36000 || new_tilt > 36000) + ret = -ERANGE; + } + if (ret == 0) /* no errors so far */ + { + ret = pwc_mpt_set_angle(pdev, new_pan, new_tilt); + if (ret >= 0) + { + pdev->pan_angle += new_pan; + pdev->tilt_angle += new_tilt; + } + if (ret == -EPIPE) /* stall -> out of range */ + ret = -ERANGE; + } + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTGANGLE: + { + struct pwc_mpt_angles *angles = arg; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + angles->absolute = 1; + angles->pan = pdev->pan_angle; + angles->tilt = pdev->tilt_angle; + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSTATUS: + { + struct pwc_mpt_status *status = arg; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + ret = pwc_mpt_get_status(pdev, status); + } + else + { + ret = -ENXIO; + } + break; + } + default: ret = -ENOIOCTLCMD; break; diff -Nru a/drivers/usb/media/pwc-if.c b/drivers/usb/media/pwc-if.c --- a/drivers/usb/media/pwc-if.c Mon Dec 29 14:21:37 2003 +++ b/drivers/usb/media/pwc-if.c Mon Dec 29 14:21:37 2003 @@ -79,9 +79,9 @@ { USB_DEVICE(0x046D, 0x08B0) }, /* Logitech QuickCam Pro 3000 */ { USB_DEVICE(0x046D, 0x08B1) }, /* Logitech QuickCam Notebook Pro */ { USB_DEVICE(0x046D, 0x08B2) }, /* Logitech QuickCam Pro 4000 */ - { USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom */ - { USB_DEVICE(0x046D, 0x08B4) }, /* Logitech (reserved) */ - { USB_DEVICE(0x046D, 0x08B5) }, /* Logitech (reserved) */ + { USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom (old model) */ + { USB_DEVICE(0x046D, 0x08B4) }, /* Logitech QuickCam Zoom (new model) */ + { USB_DEVICE(0x046D, 0x08B5) }, /* Logitech QuickCam Orbit/Sphere */ { USB_DEVICE(0x046D, 0x08B6) }, /* Logitech (reserved) */ { USB_DEVICE(0x046D, 0x08B7) }, /* Logitech (reserved) */ { USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */ @@ -129,6 +129,7 @@ static int pwc_video_open(struct inode *inode, struct file *file); static int pwc_video_close(struct inode *inode, struct file *file); +static int pwc_video_release(struct video_device *); static ssize_t pwc_video_read(struct file *file, char *buf, size_t count, loff_t *ppos); static unsigned int pwc_video_poll(struct file *file, poll_table *wait); @@ -918,21 +919,32 @@ int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot) { - int ret; + int ret, start; /* Stop isoc stuff */ pwc_isoc_cleanup(pdev); /* Reset parameters */ pwc_reset_buffers(pdev); /* Try to set video mode... */ - 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) */ - ret = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); - if (!ret) - if (pwc_isoc_init(pdev) < 0) - Info("Failed to restart ISOC transfer in pwc_try_video_mode.\n"); + start = ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot); + if (ret) { + Trace(TRACE_FLOW, "pwc_set_video_mode attempt 1 failed.\n"); + /* That failed... restore old mode (we know that worked) */ + start = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); + if (start) { + Trace(TRACE_FLOW, "pwc_set_video_mode attempt 2 failed.\n"); + } + } + if (start == 0) + { + if (pwc_isoc_init(pdev) < 0) + { + Info("Failed to restart ISOC transfers in pwc_try_video_mode.\n"); + ret = -EAGAIN; /* let's try again, who knows if it works a second time */ + } + } pdev->drop_frames++; /* try to avoid garbage during switch */ - return ret; + return ret; /* Return original error code */ } @@ -997,6 +1009,7 @@ #if PWC_DEBUG Debug("Found decompressor for %d at 0x%p\n", pdev->type, pdev->decompressor); #endif + pwc_construct(pdev); /* set min/max sizes correct */ /* So far, so good. Allocate memory. */ i = pwc_allocate_buffers(pdev); @@ -1018,6 +1031,7 @@ #if PWC_DEBUG pdev->sequence = 0; #endif + pwc_construct(pdev); /* set min/max sizes correct */ /* Set some defaults */ pdev->vsnapshot = 0; @@ -1104,6 +1118,12 @@ return 0; } +static int pwc_video_release(struct video_device *vfd) +{ + Trace(TRACE_OPEN, "pwc_video_release() called. Now what?\n"); +} + + /* * FIXME: what about two parallel reads ???? * ANSWER: Not supported. You can't open the device more than once, @@ -1124,7 +1144,7 @@ int noblock = file->f_flags & O_NONBLOCK; DECLARE_WAITQUEUE(wait, current); - Trace(TRACE_READ, "video_read(0x%p, %p, %Zd) called.\n", vdev, buf, count); + Trace(TRACE_READ, "video_read(0x%p, %p, %d) called.\n", vdev, buf, count); if (vdev == NULL) return -EFAULT; pdev = vdev->priv; @@ -1568,6 +1588,7 @@ struct pwc_device *pdev = NULL; int vendor_id, product_id, type_id; int i, hint; + int features = 0; int video_nr = -1; /* default: use next available device */ char serial_number[30], *name; @@ -1677,8 +1698,17 @@ name = "Logitech QuickCam Zoom"; type_id = 740; /* CCD sensor */ break; - case 0x08b4: + case 0x08B4: + Info("Logitech QuickCam Zoom (new model) USB webcam detected.\n"); + name = "Logitech QuickCam Zoom"; + type_id = 740; /* CCD sensor */ + break; case 0x08b5: + Info("Logitech QuickCam Orbit/Sphere USB webcam detected.\n"); + name = "Logitech QuickCam Orbit"; + type_id = 740; /* CCD sensor */ + features |= FEATURE_MOTOR_PANTILT; + break; case 0x08b6: case 0x08b7: case 0x08b8: @@ -1776,9 +1806,22 @@ } memset(pdev, 0, sizeof(struct pwc_device)); pdev->type = type_id; - pwc_construct(pdev); pdev->vsize = default_size; pdev->vframes = default_fps; + pdev->features = features; + if (vendor_id == 0x046D && product_id == 0x08B5) + { + /* Logitech QuickCam Orbit + The ranges have been determined experimentally; they may differ from cam to cam. + Also, the exact ranges left-right and up-down are different for my cam + */ + pdev->angle_range.pan_min = -7000; + pdev->angle_range.pan_max = 7000; + pdev->angle_range.tilt_min = -3000; + pdev->angle_range.tilt_max = 2500; + pdev->angle_range.zoom_min = -1; + pdev->angle_range.zoom_max = -1; + } init_MUTEX(&pdev->modlock); pdev->ptrlock = SPIN_LOCK_UNLOCKED; @@ -1791,7 +1834,7 @@ strcpy(pdev->vdev.name, name); pdev->vdev.owner = THIS_MODULE; pdev->vdev.priv = pdev; - + pdev->release = udev->descriptor.bcdDevice; Trace(TRACE_PROBE, "Release: %04x\n", pdev->release); @@ -1809,6 +1852,7 @@ } } + pdev->vdev.release = pwc_video_release; i = video_register_device(&pdev->vdev, VFL_TYPE_GRABBER, video_nr); if (i < 0) { Err("Failed to register as video device (%d).\n", i); @@ -1818,6 +1862,7 @@ else { Info("Registered as /dev/video%d.\n", pdev->vdev.minor & 0x3F); } + /* occupy slot */ if (hint < MAX_DEV_HINTS) device_hint[hint].pdev = pdev; diff -Nru a/drivers/usb/media/pwc-ioctl.h b/drivers/usb/media/pwc-ioctl.h --- a/drivers/usb/media/pwc-ioctl.h Mon Dec 29 14:21:37 2003 +++ b/drivers/usb/media/pwc-ioctl.h Mon Dec 29 14:21:37 2003 @@ -112,6 +112,43 @@ int height; }; +/* Defines and structures for Motorized Pan & Tilt */ +#define PWC_MPT_PAN 0x01 +#define PWC_MPT_TILT 0x02 +#define PWC_MPT_TIMEOUT 0x04 /* for status */ + +/* Set angles; when absolute = 1, the angle is absolute and the + driver calculates the relative offset for you. This can only + be used with VIDIOCPWCSANGLE; VIDIOCPWCGANGLE always returns + absolute angles. + */ +struct pwc_mpt_angles +{ + int absolute; /* write-only */ + int pan; /* degrees * 100 */ + int tilt; /* degress * 100 */ + int zoom; /* N/A, set to -1 */ +}; + +/* Range of angles of the camera, both horizontally and vertically. + The zoom is not used, maybe in the future... + + */ +struct pwc_mpt_range +{ + int pan_min, pan_max; /* degrees * 100 */ + int tilt_min, tilt_max; + int zoom_min, zoom_max; /* -1, -1 */ +}; + +struct pwc_mpt_status +{ + int status; + int time_pan; + int time_tilt; +}; + + /* Restore user settings */ #define VIDIOCPWCRUSER _IO('v', 192) /* Save user settings */ @@ -181,5 +218,12 @@ /* Real image size as used by the camera; tells you whether or not there's a gray border around the image */ #define VIDIOCPWCGREALSIZE _IOR('v', 210, struct pwc_imagesize) + + /* Motorized pan & tilt functions */ +#define VIDIOCPWCMPTRESET _IOW('v', 211, int) +#define VIDIOCPWCMPTGRANGE _IOR('v', 211, struct pwc_mpt_range) +#define VIDIOCPWCMPTSANGLE _IOW('v', 212, struct pwc_mpt_angles) +#define VIDIOCPWCMPTGANGLE _IOR('v', 212, struct pwc_mpt_angles) +#define VIDIOCPWCMPTSTATUS _IOR('v', 213, struct pwc_mpt_status) #endif diff -Nru a/drivers/usb/media/pwc-misc.c b/drivers/usb/media/pwc-misc.c --- a/drivers/usb/media/pwc-misc.c Mon Dec 29 14:21:37 2003 +++ b/drivers/usb/media/pwc-misc.c Mon Dec 29 14:21:37 2003 @@ -52,7 +52,7 @@ return find; } -/* initialize variables depending on type */ +/* initialize variables depending on type and decompressor*/ void pwc_construct(struct pwc_device *pdev) { switch(pdev->type) { @@ -73,9 +73,17 @@ case 690: pdev->view_min.x = 128; pdev->view_min.y = 96; - pdev->view_max.x = 640; - pdev->view_max.y = 480; - pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA; + /* Anthill bug #38: PWC always reports max size, even without PWCX */ + if (pdev->decompressor != NULL) { + pdev->view_max.x = 640; + pdev->view_max.y = 480; + pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA; + } + else { + pdev->view_max.x = 352; + pdev->view_max.y = 288; + pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF; + } pdev->vcinterface = 3; pdev->vendpoint = 4; pdev->frame_header_size = 0; @@ -87,9 +95,18 @@ case 750: pdev->view_min.x = 160; pdev->view_min.y = 120; - pdev->view_max.x = 640; - pdev->view_max.y = 480; - pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA; + /* Anthill bug #38: PWC always reports max size, even without PWCX */ + if (pdev->decompressor != NULL) { + pdev->view_max.x = 640; + pdev->view_max.y = 480; + pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA; + } + else { + /* Tell CIF, even though SIF really is the maximum, but some tools really need CIF */ + pdev->view_max.x = 352; + pdev->view_max.y = 288; + pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF; + } pdev->vcinterface = 3; pdev->vendpoint = 5; pdev->frame_header_size = TOUCAM_HEADER_SIZE; diff -Nru a/drivers/usb/media/pwc.h b/drivers/usb/media/pwc.h --- a/drivers/usb/media/pwc.h Mon Dec 29 14:21:37 2003 +++ b/drivers/usb/media/pwc.h Mon Dec 29 14:21:37 2003 @@ -18,17 +18,21 @@ #ifndef PWC_H #define PWC_H +#include + #include #include -#include +#include #include +#include #include #include -#include #include #include +#include "pwc-ioctl.h" + /* Defines and structures for the Philips webcam */ /* Used for checking memory corruption/pointer validation */ #define PWC_MAGIC 0x89DC10ABUL @@ -58,10 +62,12 @@ #define TOUCAM_HEADER_SIZE 8 #define TOUCAM_TRAILER_SIZE 4 +#define FEATURE_MOTOR_PANTILT 0x0001 + /* Version block */ #define PWC_MAJOR 8 -#define PWC_MINOR 11 -#define PWC_VERSION "8.11" +#define PWC_MINOR 12 +#define PWC_VERSION "8.12" #define PWC_NAME "pwc" /* Turn certain features on/off */ @@ -119,8 +125,9 @@ /* Pointer to our usb_device */ struct usb_device *udev; - int type; /* type of cam (645, 646, 675, 680, 690) */ + int type; /* type of cam (645, 646, 675, 680, 690, 720, 730, 740, 750) */ int release; /* release number */ + int features; /* feature bits */ int error_status; /* set when something goes wrong with the cam (unplugged, USB errors) */ int usb_init; /* set when the cam has been initialized over USB */ @@ -193,6 +200,11 @@ struct semaphore modlock; /* to prevent races in video_open(), etc */ spinlock_t ptrlock; /* for manipulating the buffer pointers */ + + /*** motorized pan/tilt feature */ + struct pwc_mpt_range angle_range; + int pan_angle; /* in degrees * 100 */ + int tilt_angle; /* absolute angle; 0,0 is home position */ /*** Misc. data ***/ wait_queue_head_t frameq; /* When waiting for a frame to finish... */