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
        u8 flags;
 };
 
+static void qual_upd(struct work_struct *work);
+
 struct i2c_reg_u8 {
        u8 reg;
        u8 val;
 
        gspca_dev->cam.ctrls = sd->ctrls;
 
+       INIT_WORK(&sd->work, qual_upd);
 
        return 0;
 }
 
        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;
 }
 
        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;
                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 */
 }
 #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 */
                            (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)
                                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);
        }
 }
        .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,