diff options
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/gspca/sn9c20x.c | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/drivers/media/video/gspca/sn9c20x.c b/drivers/media/video/gspca/sn9c20x.c index 26ee3c92f56d..2a670eadca1c 100644 --- a/drivers/media/video/gspca/sn9c20x.c +++ b/drivers/media/video/gspca/sn9c20x.c @@ -89,6 +89,12 @@ struct sd { struct gspca_ctrl ctrls[NCTRLS]; + struct work_struct work; + struct workqueue_struct *work_thread; + + u32 pktsz; /* (used by pkt_scan) */ + u16 npkt; + s8 nchg; u8 fmt; /* (used for JPEG QTAB update */ #define MIN_AVG_LUM 80 @@ -108,6 +114,8 @@ struct sd { u8 flags; }; +static void qual_upd(struct work_struct *work); + struct i2c_reg_u8 { u8 reg; u8 val; @@ -1842,6 +1850,7 @@ static int sd_config(struct gspca_dev *gspca_dev, gspca_dev->cam.ctrls = sd->ctrls; + INIT_WORK(&sd->work, qual_upd); return 0; } @@ -2101,6 +2110,15 @@ static int sd_start(struct gspca_dev *gspca_dev) reg_r(gspca_dev, 0x1061, 1); reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] | 0x02); + + /* if JPEG, prepare the compression quality update */ + if (mode & MODE_JPEG) { + sd->pktsz = sd->npkt = 0; + sd->nchg = 0; + sd->work_thread = + create_singlethread_workqueue(KBUILD_MODNAME); + } + return gspca_dev->usb_err; } @@ -2112,6 +2130,20 @@ static void sd_stopN(struct gspca_dev *gspca_dev) reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] & ~0x02); } +/* called on streamoff with alt==0 and on disconnect */ +/* the usb_lock is held at entry - restore on exit */ +static void sd_stop0(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->work_thread != NULL) { + mutex_unlock(&gspca_dev->usb_lock); + destroy_workqueue(sd->work_thread); + mutex_lock(&gspca_dev->usb_lock); + sd->work_thread = NULL; + } +} + static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum) { struct sd *sd = (struct sd *) gspca_dev; @@ -2195,6 +2227,19 @@ static void sd_dqcallback(struct gspca_dev *gspca_dev) do_autoexposure(gspca_dev, avg_lum); } +/* JPEG quality update */ +/* This function is executed from a work queue. */ +static void qual_upd(struct work_struct *work) +{ + struct sd *sd = container_of(work, struct sd, work); + struct gspca_dev *gspca_dev = &sd->gspca_dev; + + mutex_lock(&gspca_dev->usb_lock); + PDEBUG(D_STREAM, "qual_upd %d%%", sd->ctrls[QUALITY].val); + set_quality(gspca_dev); + mutex_unlock(&gspca_dev->usb_lock); +} + #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, /* interrupt packet */ @@ -2213,6 +2258,50 @@ static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, } #endif +/* check the JPEG compression */ +static void transfer_check(struct gspca_dev *gspca_dev, + u8 *data) +{ + struct sd *sd = (struct sd *) gspca_dev; + int new_qual, r; + + new_qual = 0; + + /* if USB error, discard the frame and decrease the quality */ + if (data[6] & 0x08) { /* USB FIFO full */ + gspca_dev->last_packet_type = DISCARD_PACKET; + new_qual = -5; + } else { + + /* else, compute the filling rate and a new JPEG quality */ + r = (sd->pktsz * 100) / + (sd->npkt * + gspca_dev->urb[0]->iso_frame_desc[0].length); + if (r >= 85) + new_qual = -3; + else if (r < 75) + new_qual = 2; + } + if (new_qual != 0) { + sd->nchg += new_qual; + if (sd->nchg < -6 || sd->nchg >= 12) { + sd->nchg = 0; + new_qual += sd->ctrls[QUALITY].val; + if (new_qual < QUALITY_MIN) + new_qual = QUALITY_MIN; + else if (new_qual > QUALITY_MAX) + new_qual = QUALITY_MAX; + if (new_qual != sd->ctrls[QUALITY].val) { + sd->ctrls[QUALITY].val = new_qual; + queue_work(sd->work_thread, &sd->work); + } + } + } else { + sd->nchg = 0; + } + sd->pktsz = sd->npkt = 0; +} + static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, /* isoc packet */ int len) /* iso packet length */ @@ -2248,6 +2337,11 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, (data[33] << 10); avg_lum >>= 9; atomic_set(&sd->avg_lum, avg_lum); + + if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv + & MODE_JPEG) + transfer_check(gspca_dev, data); + gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0); len -= 64; if (len == 0) @@ -2266,6 +2360,12 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, data, len); } } else { + /* if JPEG, count the packets and their size */ + if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv + & MODE_JPEG) { + sd->npkt++; + sd->pktsz += len; + } gspca_frame_add(gspca_dev, INTER_PACKET, data, len); } } @@ -2280,6 +2380,7 @@ static const struct sd_desc sd_desc = { .isoc_init = sd_isoc_init, .start = sd_start, .stopN = sd_stopN, + .stop0 = sd_stop0, .pkt_scan = sd_pkt_scan, #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) .int_pkt_scan = sd_int_pkt_scan, |