0x10/-      Master gain 0-31
    -/0x10      Another gain 0-15, limited influence (1-2x gain I guess)
    0x21                Bitfield: 0-1 unused, 2-3 vflip/hflip, 4-5 unknown, 6-7 unused
+   -/0x27      Seems to toggle various gains on / off, Setting bit 7 seems to
+               completely disable the analog amplification block. Set to 0x68
+               for max gain, 0x14 for minimal gain.
 */
 
 #define MODULE_NAME "pac7311"
        unsigned char brightness;
        unsigned char contrast;
        unsigned char colors;
+       unsigned char gain;
+       unsigned char exposure;
        unsigned char autogain;
        __u8 hflip;
        __u8 vflip;
 static int sd_gethflip(struct gspca_dev *gspca_dev, __s32 *val);
 static int sd_setvflip(struct gspca_dev *gspca_dev, __s32 val);
 static int sd_getvflip(struct gspca_dev *gspca_dev, __s32 *val);
+static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val);
+static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val);
+static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val);
+static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val);
 
 static struct ctrl sd_ctrls[] = {
+/* This control is pac7302 only */
+#define BRIGHTNESS_IDX 0
        {
            {
                .id      = V4L2_CID_BRIGHTNESS,
            .set = sd_setbrightness,
            .get = sd_getbrightness,
        },
+/* This control is for both the 7302 and the 7311 */
        {
            {
                .id      = V4L2_CID_CONTRAST,
            .set = sd_setcontrast,
            .get = sd_getcontrast,
        },
+/* This control is pac7302 only */
+#define SATURATION_IDX 2
        {
            {
                .id      = V4L2_CID_SATURATION,
            .set = sd_setcolors,
            .get = sd_getcolors,
        },
+/* All controls below are for both the 7302 and the 7311 */
+       {
+           {
+               .id      = V4L2_CID_GAIN,
+               .type    = V4L2_CTRL_TYPE_INTEGER,
+               .name    = "Gain",
+               .minimum = 0,
+#define GAIN_MAX 255
+               .maximum = GAIN_MAX,
+               .step    = 1,
+#define GAIN_DEF 127
+#define GAIN_KNEE 255 /* Gain seems to cause little noise on the pac73xx */
+               .default_value = GAIN_DEF,
+           },
+           .set = sd_setgain,
+           .get = sd_getgain,
+       },
+       {
+           {
+               .id      = V4L2_CID_EXPOSURE,
+               .type    = V4L2_CTRL_TYPE_INTEGER,
+               .name    = "Exposure",
+               .minimum = 0,
+#define EXPOSURE_MAX 255
+               .maximum = EXPOSURE_MAX,
+               .step    = 1,
+#define EXPOSURE_DEF  16 /*  32 ms / 30 fps */
+#define EXPOSURE_KNEE 50 /* 100 ms / 10 fps */
+               .default_value = EXPOSURE_DEF,
+           },
+           .set = sd_setexposure,
+           .get = sd_getexposure,
+       },
        {
            {
                .id      = V4L2_CID_AUTOGAIN,
            .set = sd_setautogain,
            .get = sd_getautogain,
        },
-/* next controls work with pac7302 only */
-#define HFLIP_IDX 4
        {
            {
                .id      = V4L2_CID_HFLIP,
            .set = sd_sethflip,
            .get = sd_gethflip,
        },
-#define VFLIP_IDX 5
        {
            {
                .id      = V4L2_CID_VFLIP,
        0x09, 0x00, 0xaa, 0xaa, 0x07, 0x00, 0x00, 0x62,
        0x08, 0xaa, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x03, 0xa0, 0x01, 0xf4, 0xaa,
-       0xaa, 0x00, 0x08, 0xaa, 0x03, 0xaa, 0x00, 0x01,
+       0xaa, 0x00, 0x08, 0xaa, 0x03, 0xaa, 0x00, 0x68,
        0xca, 0x10, 0x06, 0x78, 0x00, 0x00, 0x00, 0x00,
        0x23, 0x28, 0x04, 0x11, 0x00, 0x00
 };
                        usb_sndctrlpipe(gspca_dev->dev, 0),
                        0,                      /* request */
                        USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
-                       value, index, gspca_dev->usb_buf, 1,
+                       0, index, gspca_dev->usb_buf, 1,
                        500);
 }
 
 
                cam->cam_mode = vga_mode;
                cam->nmodes = ARRAY_SIZE(vga_mode);
-               gspca_dev->ctrl_dis = (1 << HFLIP_IDX)
-                               | (1 << VFLIP_IDX);
+               gspca_dev->ctrl_dis = (1 << BRIGHTNESS_IDX)
+                               | (1 << SATURATION_IDX);
        }
 
        sd->brightness = BRIGHTNESS_DEF;
        sd->contrast = CONTRAST_DEF;
        sd->colors = COLOR_DEF;
+       sd->gain = GAIN_DEF;
+       sd->exposure = EXPOSURE_DEF;
        sd->autogain = AUTOGAIN_DEF;
        sd->hflip = HFLIP_DEF;
        sd->vflip = VFLIP_DEF;
        return 0;
 }
 
-/* rev 12a only */
+/* This function is used by pac7302 only */
 static void setbrightcont(struct gspca_dev *gspca_dev)
 {
        struct sd *sd = (struct sd *) gspca_dev;
        reg_w(gspca_dev, 0xdc, 0x01);
 }
 
-/* This function is used by pac7302 only */
-static void setbrightness(struct gspca_dev *gspca_dev)
+/* This function is used by pac7311 only */
+static void setcontrast(struct gspca_dev *gspca_dev)
 {
        struct sd *sd = (struct sd *) gspca_dev;
-       int brightness;
-
-       if (sd->sensor == SENSOR_PAC7302) {
-               setbrightcont(gspca_dev);
-               return;
-       }
-/* HDG: this is not brightness but gain, I'll add gain and exposure controls
-   in a next patch */
-       return;
 
-       brightness = BRIGHTNESS_MAX - sd->brightness;
        reg_w(gspca_dev, 0xff, 0x04);
-       reg_w(gspca_dev, 0x0e, 0x00);
-       reg_w(gspca_dev, 0x0f, brightness);
+       reg_w(gspca_dev, 0x10, sd->contrast >> 4);
        /* load registers to sensor (Bit 0, auto clear) */
        reg_w(gspca_dev, 0x11, 0x01);
-       PDEBUG(D_CONF|D_STREAM, "brightness: %i", brightness);
 }
 
-static void setcontrast(struct gspca_dev *gspca_dev)
+/* This function is used by pac7302 only */
+static void setcolors(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+       int i, v;
+       static const int a[9] =
+               {217, -212, 0, -101, 170, -67, -38, -315, 355};
+       static const int b[9] =
+               {19, 106, 0, 19, 106, 1, 19, 106, 1};
+
+       reg_w(gspca_dev, 0xff, 0x03);   /* page 3 */
+       reg_w(gspca_dev, 0x11, 0x01);
+       reg_w(gspca_dev, 0xff, 0x00);   /* page 0 */
+       reg_w(gspca_dev, 0xff, 0x00);   /* page 0 */
+       for (i = 0; i < 9; i++) {
+               v = a[i] * sd->colors / COLOR_MAX + b[i];
+               reg_w(gspca_dev, 0x0f + 2 * i, (v >> 8) & 0x07);
+               reg_w(gspca_dev, 0x0f + 2 * i + 1, v);
+       }
+       reg_w(gspca_dev, 0xdc, 0x01);
+       PDEBUG(D_CONF|D_STREAM, "color: %i", sd->colors);
+}
+
+static void setgain(struct gspca_dev *gspca_dev)
 {
        struct sd *sd = (struct sd *) gspca_dev;
 
        if (sd->sensor == SENSOR_PAC7302) {
-               setbrightcont(gspca_dev);
-               return;
+               reg_w(gspca_dev, 0xff, 0x03);           /* page 3 */
+               reg_w(gspca_dev, 0x10, sd->gain >> 3);
+       } else {
+               int gain = GAIN_MAX - sd->gain;
+               if (gain < 1)
+                       gain = 1;
+               else if (gain > 245)
+                       gain = 245;
+               reg_w(gspca_dev, 0xff, 0x04);           /* page 4 */
+               reg_w(gspca_dev, 0x0e, 0x00);
+               reg_w(gspca_dev, 0x0f, gain);
        }
-       reg_w(gspca_dev, 0xff, 0x04);
-       reg_w(gspca_dev, 0x10, sd->contrast >> 4);
        /* load registers to sensor (Bit 0, auto clear) */
        reg_w(gspca_dev, 0x11, 0x01);
 }
 
-/* This function is used by pac7302 only */
-static void setcolors(struct gspca_dev *gspca_dev)
+static void setexposure(struct gspca_dev *gspca_dev)
 {
        struct sd *sd = (struct sd *) gspca_dev;
+       __u8 reg;
+
+       /* register 2 of frame 3/4 contains the clock divider configuring the
+          no fps according to the formula: 60 / reg. sd->exposure is the
+          desired exposure time in ms. */
+       reg = 120 * sd->exposure / 1000;
+       if (reg < 2)
+               reg = 2;
+       else if (reg > 63)
+               reg = 63;
 
        if (sd->sensor == SENSOR_PAC7302) {
-               int i, v;
-               static const int a[9] =
-                       {217, -212, 0, -101, 170, -67, -38, -315, 355};
-               static const int b[9] =
-                       {19, 106, 0, 19, 106, 1, 19, 106, 1};
-
-               reg_w(gspca_dev, 0xff, 0x03);   /* page 3 */
-               reg_w(gspca_dev, 0x11, 0x01);
-               reg_w(gspca_dev, 0xff, 0x00);   /* page 0 */
-               reg_w(gspca_dev, 0xff, 0x00);   /* page 0 */
-               for (i = 0; i < 9; i++) {
-                       v = a[i] * sd->colors / COLOR_MAX + b[i];
-                       reg_w(gspca_dev, 0x0f + 2 * i, (v >> 8) & 0x07);
-                       reg_w(gspca_dev, 0x0f + 2 * i + 1, v);
-               }
-               reg_w(gspca_dev, 0xdc, 0x01);
-               PDEBUG(D_CONF|D_STREAM, "color: %i", sd->colors);
+               /* On the pac7302 reg2 MUST be a multiple of 3, so round it to
+                  the nearest multiple of 3 */
+               reg = ((reg + 1) / 3) * 3;
+               reg_w(gspca_dev, 0xff, 0x03);           /* page 3 */
+               reg_w(gspca_dev, 0x02, reg);
+       } else {
+               reg_w(gspca_dev, 0xff, 0x04);           /* page 4 */
+               reg_w(gspca_dev, 0x02, reg);
+               /* Page 1 register 8 must always be 0x08 except when not in
+                  640x480 mode and Page3/4 reg 2 <= 3 then it must be 9 */
+               reg_w(gspca_dev, 0xff, 0x01);
+               if (gspca_dev->cam.cam_mode[(int)gspca_dev->curr_mode].priv &&
+                               reg <= 3)
+                       reg_w(gspca_dev, 0x08, 0x09);
+               else
+                       reg_w(gspca_dev, 0x08, 0x08);
        }
+       /* load registers to sensor (Bit 0, auto clear) */
+       reg_w(gspca_dev, 0x11, 0x01);
 }
 
 static void sethvflip(struct gspca_dev *gspca_dev)
 
        if (sd->sensor == SENSOR_PAC7302) {
                reg_w(gspca_dev, 0xff, 0x03);           /* page 3 */
-               data = (sd->hflip ? 0x00 : 0x08)
+               data = (sd->hflip ? 0x08 : 0x00)
                        | (sd->vflip ? 0x04 : 0x00);
        } else {
-               reg_w(gspca_dev, 0xff, 0x04);           /* page 3 */
+               reg_w(gspca_dev, 0xff, 0x04);           /* page 4 */
                data = (sd->hflip ? 0x04 : 0x00)
                        | (sd->vflip ? 0x08 : 0x00);
        }
        reg_w(gspca_dev, 0x21, data);
+       /* load registers to sensor (Bit 0, auto clear) */
        reg_w(gspca_dev, 0x11, 0x01);
 }
 
 
        sd->sof_read = 0;
 
-       if (sd->sensor == SENSOR_PAC7302)
+       if (sd->sensor == SENSOR_PAC7302) {
                reg_w_var(gspca_dev, start_7302);
-       else
+               setbrightcont(gspca_dev);
+               setcolors(gspca_dev);
+       } else {
                reg_w_var(gspca_dev, start_7311);
-
-       setcontrast(gspca_dev);
-       setbrightness(gspca_dev);
-       setcolors(gspca_dev);
+               setcontrast(gspca_dev);
+       }
+       setgain(gspca_dev);
+       setexposure(gspca_dev);
+       sethvflip(gspca_dev);
 
        /* set correct resolution */
        switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
        case 2:                                 /* 160x120 pac7311 */
-               reg_w(gspca_dev, 0xff, 0x04);
-               reg_w(gspca_dev, 0x02, 0x03);
                reg_w(gspca_dev, 0xff, 0x01);
-               reg_w(gspca_dev, 0x08, 0x09);
                reg_w(gspca_dev, 0x17, 0x20);
-               reg_w(gspca_dev, 0x1b, 0x00);
                reg_w(gspca_dev, 0x87, 0x10);
                break;
        case 1:                                 /* 320x240 pac7311 */
-               reg_w(gspca_dev, 0xff, 0x04);
-               reg_w(gspca_dev, 0x02, 0x03);
                reg_w(gspca_dev, 0xff, 0x01);
-               reg_w(gspca_dev, 0x08, 0x09);
                reg_w(gspca_dev, 0x17, 0x30);
                reg_w(gspca_dev, 0x87, 0x11);
                break;
        case 0:                                 /* 640x480 */
                if (sd->sensor == SENSOR_PAC7302)
                        break;
-               reg_w(gspca_dev, 0xff, 0x04);
-               reg_w(gspca_dev, 0x02, 0x07);
                reg_w(gspca_dev, 0xff, 0x01);
-               reg_w(gspca_dev, 0x08, 0x08);
                reg_w(gspca_dev, 0x17, 0x00);
                reg_w(gspca_dev, 0x87, 0x12);
                break;
        }
 
-       /* start stream */
-       reg_w(gspca_dev, 0xff, 0x01);
-       if (sd->sensor == SENSOR_PAC7302) {
-               sethvflip(gspca_dev);
-               reg_w(gspca_dev, 0x78, 0x01);
-               reg_w(gspca_dev, 0xff, 0x01);
-               reg_w(gspca_dev, 0x78, 0x01);
-       } else {
-               reg_w(gspca_dev, 0x78, 0x44);
-               reg_w(gspca_dev, 0x78, 0x45);
-       }
-
        sd->sof_read = 0;
        sd->autogain_ignore_frames = 0;
        atomic_set(&sd->avg_lum, -1);
+
+       /* start stream */
+       reg_w(gspca_dev, 0xff, 0x01);
+       if (sd->sensor == SENSOR_PAC7302)
+               reg_w(gspca_dev, 0x78, 0x01);
+       else
+               reg_w(gspca_dev, 0x78, 0x05);
 }
 
 static void sd_stopN(struct gspca_dev *gspca_dev)
        }
 }
 
+/* Include pac common sof detection functions */
+#include "pac_common.h"
+
 static void do_autogain(struct gspca_dev *gspca_dev)
 {
+       struct sd *sd = (struct sd *) gspca_dev;
+       int avg_lum = atomic_read(&sd->avg_lum);
+       int desired_lum;
+
+       if (avg_lum == -1)
+               return;
+
+       if (sd->sensor == SENSOR_PAC7302)
+               desired_lum = 70 + sd->brightness * 2;
+       else
+               desired_lum = 200;
+
+       if (sd->autogain_ignore_frames > 0)
+               sd->autogain_ignore_frames--;
+       else if (gspca_auto_gain_n_exposure(gspca_dev, avg_lum, desired_lum,
+                       10, GAIN_KNEE, EXPOSURE_KNEE))
+               sd->autogain_ignore_frames = PAC_AUTOGAIN_IGNORE_FRAMES;
 }
 
 static const unsigned char pac7311_jpeg_header1[] = {
   0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00
 };
 
-/* Include pac common sof detection functions */
-#include "pac_common.h"
-
 /* this function is run at interrupt level */
 static void sd_pkt_scan(struct gspca_dev *gspca_dev,
                        struct gspca_frame *frame,      /* target */
 
        sd->brightness = val;
        if (gspca_dev->streaming)
-               setbrightness(gspca_dev);
+               setbrightcont(gspca_dev);
        return 0;
 }
 
        struct sd *sd = (struct sd *) gspca_dev;
 
        sd->contrast = val;
-       if (gspca_dev->streaming)
-               setcontrast(gspca_dev);
+       if (gspca_dev->streaming) {
+               if (sd->sensor == SENSOR_PAC7302)
+                       setbrightcont(gspca_dev);
+               else
+                       setcontrast(gspca_dev);
+       }
        return 0;
 }
 
        return 0;
 }
 
+static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       sd->gain = val;
+       if (gspca_dev->streaming)
+               setgain(gspca_dev);
+       return 0;
+}
+
+static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       *val = sd->gain;
+       return 0;
+}
+
+static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       sd->exposure = val;
+       if (gspca_dev->streaming)
+               setexposure(gspca_dev);
+       return 0;
+}
+
+static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       *val = sd->exposure;
+       return 0;
+}
+
 static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val)
 {
        struct sd *sd = (struct sd *) gspca_dev;
 
        sd->autogain = val;
+       /* when switching to autogain set defaults to make sure
+          we are on a valid point of the autogain gain /
+          exposure knee graph, and give this change time to
+          take effect before doing autogain. */
+       if (sd->autogain) {
+               sd->exposure = EXPOSURE_DEF;
+               sd->gain = GAIN_DEF;
+               if (gspca_dev->streaming) {
+                       sd->autogain_ignore_frames =
+                               PAC_AUTOGAIN_IGNORE_FRAMES;
+                       setexposure(gspca_dev);
+                       setgain(gspca_dev);
+               }
+       }
 
        return 0;
 }
        {USB_DEVICE(0x093a, 0x260e), .driver_info = SENSOR_PAC7311},
        {USB_DEVICE(0x093a, 0x260f), .driver_info = SENSOR_PAC7311},
        {USB_DEVICE(0x093a, 0x2621), .driver_info = SENSOR_PAC7302},
+       {USB_DEVICE(0x093a, 0x2624), .driver_info = SENSOR_PAC7302},
        {}
 };
 MODULE_DEVICE_TABLE(usb, device_table);