From 975e76214cd2516eb6cfff4c3eec581872645e88 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Thu, 19 Sep 2013 01:50:59 +0200 Subject: [PATCH 31/53] uvc: add iPassion iP2970 support Signed-off-by: John Crispin --- drivers/media/usb/uvc/uvc_driver.c | 12 +++ drivers/media/usb/uvc/uvc_status.c | 2 + drivers/media/usb/uvc/uvc_video.c | 147 ++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 5 +- 4 files changed, 165 insertions(+), 1 deletion(-) --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -3183,6 +3183,18 @@ static const struct usb_device_id uvc_id .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = UVC_INFO_META(V4L2_META_FMT_D4XX) }, + /* 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, UVC_PC_PROTOCOL_UNDEFINED) }, { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) }, --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -228,6 +228,7 @@ static void uvc_status_complete(struct u if (uvc_event_control(urb, dev->status, len)) /* The URB will be resubmitted in work context. */ return; + dev->motion = 1; break; } @@ -273,6 +274,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 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -19,6 +19,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -1275,9 +1280,149 @@ static void uvc_video_decode_data(struct uvc_urb->async_operations++; } +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_dbg(stream->dev, FRAME, "Frame complete (EOF found)\n"); @@ -1859,6 +2004,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_each_uvc_urb(uvc_urb, stream) { --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -76,6 +76,8 @@ #define UVC_QUIRK_NO_RESET_RESUME 0x00004000 #define UVC_QUIRK_DISABLE_AUTOSUSPEND 0x00008000 #define UVC_QUIRK_INVALID_DEVICE_SOF 0x00010000 +#define UVC_QUIRK_MOTION 0x00020000 +#define UVC_QUIRK_SINGLE_ISO 0x00040000 /* Format flags */ #define UVC_FMT_FLAG_COMPRESSED 0x00000001 @@ -585,6 +587,7 @@ struct uvc_device { struct input_dev *input; char input_phys[64]; + int motion; struct uvc_ctrl_work { struct work_struct work;