From 0d3e92b4d3e2160873b610aabd46bbc4853ff82e Mon Sep 17 00:00:00 2001 From: John Crispin Date: Thu, 19 Sep 2013 01:50:59 +0200 Subject: [PATCH 57/57] uvc: add iPassion iP2970 support Signed-off-by: John Crispin --- drivers/media/usb/uvc/uvc_driver.c | 14 ++++ drivers/media/usb/uvc/uvc_status.c | 2 + drivers/media/usb/uvc/uvc_video.c | 147 ++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 3 + 4 files changed, 166 insertions(+) --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2504,6 +2504,20 @@ static struct usb_device_id uvc_ids[] = .bInterfaceProtocol = 0, .driver_info = UVC_QUIRK_PROBE_MINMAX | UVC_QUIRK_IGNORE_SELECTOR_UNIT }, + +/* iPassion iP2970 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x1B3B, + .idProduct = 0x2970, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_QUIRK_PROBE_MINMAX + | UVC_QUIRK_STREAM_NO_FID + | UVC_QUIRK_MOTION + | UVC_QUIRK_SINGLE_ISO }, + /* Generic USB Video Class */ { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, {} --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -139,6 +139,7 @@ static void uvc_status_complete(struct u switch (dev->status[0] & 0x0f) { case UVC_STATUS_TYPE_CONTROL: uvc_event_control(dev, dev->status, len); + dev->motion = 1; break; case UVC_STATUS_TYPE_STREAMING: @@ -182,6 +183,7 @@ int uvc_status_init(struct uvc_device *d } pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress); + dev->motion = 0; /* For high-speed interrupt endpoints, the bInterval value is used as * an exponent of two. Some developers forgot about it. --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -21,6 +21,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -1080,9 +1085,149 @@ static void uvc_video_decode_data(struct } } +struct bh_priv { + unsigned long seen; +}; + +struct bh_event { + const char *name; + struct sk_buff *skb; + struct work_struct work; +}; + +#define BH_ERR(fmt, args...) printk(KERN_ERR "%s: " fmt, "webcam", ##args ) +#define BH_DBG(fmt, args...) do {} while (0) +#define BH_SKB_SIZE 2048 + +extern u64 uevent_next_seqnum(void); +static int seen = 0; + +static int bh_event_add_var(struct bh_event *event, int argv, + const char *format, ...) +{ + static char buf[128]; + char *s; + va_list args; + int len; + + if (argv) + return 0; + + va_start(args, format); + len = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + if (len >= sizeof(buf)) { + BH_ERR("buffer size too small\n"); + WARN_ON(1); + return -ENOMEM; + } + + s = skb_put(event->skb, len + 1); + strcpy(s, buf); + + BH_DBG("added variable '%s'\n", s); + + return 0; +} + +static int motion_hotplug_fill_event(struct bh_event *event) +{ + int s = jiffies; + int ret; + + if (!seen) + seen = jiffies; + + ret = bh_event_add_var(event, 0, "HOME=%s", "/"); + if (ret) + return ret; + + ret = bh_event_add_var(event, 0, "PATH=%s", + "/sbin:/bin:/usr/sbin:/usr/bin"); + if (ret) + return ret; + + ret = bh_event_add_var(event, 0, "SUBSYSTEM=usb"); + if (ret) + return ret; + + ret = bh_event_add_var(event, 0, "ACTION=motion"); + if (ret) + return ret; + + ret = bh_event_add_var(event, 0, "SEEN=%d", s - seen); + if (ret) + return ret; + seen = s; + + ret = bh_event_add_var(event, 0, "SEQNUM=%llu", uevent_next_seqnum()); + + return ret; +} + +static void motion_hotplug_work(struct work_struct *work) +{ + struct bh_event *event = container_of(work, struct bh_event, work); + int ret = 0; + + event->skb = alloc_skb(BH_SKB_SIZE, GFP_KERNEL); + if (!event->skb) + goto out_free_event; + + ret = bh_event_add_var(event, 0, "%s@", "add"); + if (ret) + goto out_free_skb; + + ret = motion_hotplug_fill_event(event); + if (ret) + goto out_free_skb; + + NETLINK_CB(event->skb).dst_group = 1; + broadcast_uevent(event->skb, 0, 1, GFP_KERNEL); + +out_free_skb: + if (ret) { + BH_ERR("work error %d\n", ret); + kfree_skb(event->skb); + } +out_free_event: + kfree(event); +} + +static int motion_hotplug_create_event(void) +{ + struct bh_event *event; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->name = "motion"; + + INIT_WORK(&event->work, (void *)(void *)motion_hotplug_work); + schedule_work(&event->work); + + return 0; +} + +#define MOTION_FLAG_OFFSET 4 static void uvc_video_decode_end(struct uvc_streaming *stream, struct uvc_buffer *buf, const __u8 *data, int len) { + if ((stream->dev->quirks & UVC_QUIRK_MOTION) && + (data[len - 2] == 0xff) && (data[len - 1] == 0xd9)) { + u8 *mem; + buf->state = UVC_BUF_STATE_READY; + mem = (u8 *) (buf->mem + MOTION_FLAG_OFFSET); + if ( stream->dev->motion ) { + stream->dev->motion = 0; + motion_hotplug_create_event(); + } else { + *mem &= 0x7f; + } + } + /* Mark the buffer as done if the EOF marker is set. */ if (data[1] & UVC_STREAM_EOF && buf->bytesused != 0) { uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n"); @@ -1495,6 +1640,8 @@ static int uvc_init_video_isoc(struct uv if (npackets == 0) return -ENOMEM; + if (stream->dev->quirks & UVC_QUIRK_SINGLE_ISO) + npackets = 1; size = npackets * psize; for (i = 0; i < UVC_URBS; ++i) { --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -148,6 +148,8 @@ #define UVC_QUIRK_PROBE_DEF 0x00000100 #define UVC_QUIRK_RESTRICT_FRAME_RATE 0x00000200 #define UVC_QUIRK_RESTORE_CTRLS_ON_INIT 0x00000400 +#define UVC_QUIRK_MOTION 0x00000800 +#define UVC_QUIRK_SINGLE_ISO 0x00001000 /* Format flags */ #define UVC_FMT_FLAG_COMPRESSED 0x00000001 @@ -551,6 +553,7 @@ struct uvc_device { __u8 *status; struct input_dev *input; char input_phys[64]; + int motion; }; enum uvc_handle_state {