--- linux-2.4.28-pre1/drivers/usb/pwc-ctrl.c Tue Oct 7 13:31:38 2003 +++ pwc-8.12/drivers/usb/pwc-ctrl.c Sat Oct 18 19:10:39 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", @@ -434,7 +441,8 @@ case 690: ret = set_video_mode_Timon(pdev, size, frames, compression, snapshot); break; - + + case 720: case 730: case 740: case 750: @@ -746,6 +754,7 @@ buf[1] = speed >> 8; buf[0] = speed & 0xff; break; + case 720: case 730: case 740: case 750: @@ -1244,6 +1253,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) { @@ -1564,6 +1613,141 @@ size.height = pdev->image.y; if (copy_to_user(arg, &size, sizeof(size))) ret = -EFAULT; + break; + } + + case VIDIOCPWCMPTRESET: + { + int flags; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + if (copy_from_user(&flags, arg, sizeof(flags))) + ret = -EFAULT; + else + 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) + { + if (copy_to_user(arg, &pdev->angle_range, sizeof(struct pwc_mpt_range))) + ret = -EFAULT; + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSANGLE: + { + struct pwc_mpt_angles angles; + int new_pan, new_tilt; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + if (copy_from_user(&angles, arg, sizeof(angles))) + ret = -EFAULT; + else + { + /* 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; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + angles.absolute = 1; + angles.pan = pdev->pan_angle; + angles.tilt = pdev->tilt_angle; + if (copy_to_user(arg, &angles, sizeof(angles))) + ret = -EFAULT; + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSTATUS: + { + struct pwc_mpt_status status; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + ret = pwc_mpt_get_status(pdev, &status); + if (ret < 0) + break; + if (copy_to_user(arg, &status, sizeof(status))) + ret = -EFAULT; + } + else + { + ret = -ENXIO; + } break; } --- linux-2.4.28-pre1/drivers/usb/pwc-if.c Tue Oct 7 13:31:38 2003 +++ pwc-8.12/drivers/usb/pwc-if.c Sat Oct 18 19:10:39 2003 @@ -80,9 +80,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) */ @@ -914,20 +914,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) + 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 */ } @@ -993,6 +1005,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); @@ -1601,6 +1614,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; @@ -1708,8 +1722,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: @@ -1731,12 +1754,12 @@ case 0x9000: Info("Samsung MPC-C10 USB webcam detected.\n"); name = "Samsung MPC-C10"; - type_id = 675; + type_id = 730; break; case 0x9001: Info("Samsung MPC-C30 USB webcam detected.\n"); name = "Samsung MPC-C30"; - type_id = 675; + type_id = 740; break; default: return NULL; @@ -1807,9 +1830,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; --- linux-2.4.28-pre1/drivers/usb/pwc-ioctl.h Tue Oct 7 13:31:38 2003 +++ pwc-8.12/drivers/usb/pwc-ioctl.h Sat Oct 18 19:09:36 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 --- linux-2.4.28-pre1/drivers/usb/pwc-misc.c Tue Oct 7 13:31:38 2003 +++ pwc-8.12/drivers/usb/pwc-misc.c Sat Oct 18 19:10:39 2003 @@ -51,7 +51,8 @@ } return find; } -/* initialize variables depending on type */ + +/* initialize variables depending on type and decompressor*/ void pwc_construct(struct pwc_device *pdev) { switch(pdev->type) { @@ -72,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; @@ -86,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 { + /* We use CIF, not SIF since some tools really need CIF. So we cheat a bit. */ + 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; --- linux-2.4.28-pre1/drivers/usb/pwc.h Thu Jun 24 11:42:30 2004 +++ pwc-8.12/drivers/usb/pwc.h Sun Oct 19 01:51:40 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... */