V4L/DVB: gspca - main: Change version to 2.9.0.
[safe/jmp/linux-2.6] / drivers / media / video / cx25840 / cx25840-core.c
index 9108f74..796f12d 100644 (file)
@@ -259,6 +259,13 @@ static void cx23885_initialize(struct i2c_client *client)
        struct cx25840_state *state = to_state(i2c_get_clientdata(client));
        struct workqueue_struct *q;
 
+       /*
+        * Come out of digital power down
+        * The CX23888, at least, needs this, otherwise registers aside from
+        * 0x0-0x2 can't be read or written.
+        */
+       cx25840_write(client, 0x000, 0);
+
        /* Internal Reset */
        cx25840_and_or(client, 0x102, ~0x01, 0x01);
        cx25840_and_or(client, 0x102, ~0x01, 0x00);
@@ -269,18 +276,45 @@ static void cx23885_initialize(struct i2c_client *client)
        /* DIF in reset? */
        cx25840_write(client, 0x398, 0);
 
-       /* Trust the default xtal, no division */
-       /* This changes for the cx23888 products */
+       /*
+        * Trust the default xtal, no division
+        * '885: 28.636363... MHz
+        * '887: 25.000000 MHz
+        * '888: 50.000000 MHz
+        */
        cx25840_write(client, 0x2, 0x76);
 
-       /* Bring down the regulator for AUX clk */
+       /* Power up all the PLL's and DLL */
        cx25840_write(client, 0x1, 0x40);
 
-       /* Sys PLL frac */
-       cx25840_write4(client, 0x11c, 0x01d1744c);
-
-       /* Sys PLL int */
-       cx25840_write4(client, 0x118, 0x00000416);
+       /* Sys PLL */
+       switch (state->id) {
+       case V4L2_IDENT_CX23888_AV:
+               /*
+                * 50.0 MHz * (0xb + 0xe8ba26/0x2000000)/4 = 5 * 28.636363 MHz
+                * 572.73 MHz before post divide
+                */
+               cx25840_write4(client, 0x11c, 0x00e8ba26);
+               cx25840_write4(client, 0x118, 0x0000040b);
+               break;
+       case V4L2_IDENT_CX23887_AV:
+               /*
+                * 25.0 MHz * (0x16 + 0x1d1744c/0x2000000)/4 = 5 * 28.636363 MHz
+                * 572.73 MHz before post divide
+                */
+               cx25840_write4(client, 0x11c, 0x01d1744c);
+               cx25840_write4(client, 0x118, 0x00000416);
+               break;
+       case V4L2_IDENT_CX23885_AV:
+       default:
+               /*
+                * 28.636363 MHz * (0x14 + 0x0/0x2000000)/4 = 5 * 28.636363 MHz
+                * 572.73 MHz before post divide
+                */
+               cx25840_write4(client, 0x11c, 0x00000000);
+               cx25840_write4(client, 0x118, 0x00000414);
+               break;
+       }
 
        /* Disable DIF bypass */
        cx25840_write4(client, 0x33c, 0x00000001);
@@ -288,11 +322,15 @@ static void cx23885_initialize(struct i2c_client *client)
        /* DIF Src phase inc */
        cx25840_write4(client, 0x340, 0x0df7df83);
 
-       /* Vid PLL frac */
-       cx25840_write4(client, 0x10c, 0x01b6db7b);
-
-       /* Vid PLL int */
-       cx25840_write4(client, 0x108, 0x00000512);
+       /*
+        * Vid PLL
+        * Setup for a BT.656 pixel clock of 13.5 Mpixels/second
+        *
+        * 28.636363 MHz * (0xf + 0x02be2c9/0x2000000)/4 = 8 * 13.5 MHz
+        * 432.0 MHz before post divide
+        */
+       cx25840_write4(client, 0x10c, 0x002be2c9);
+       cx25840_write4(client, 0x108, 0x0000040f);
 
        /* Luma */
        cx25840_write4(client, 0x414, 0x00107d12);
@@ -300,11 +338,43 @@ static void cx23885_initialize(struct i2c_client *client)
        /* Chroma */
        cx25840_write4(client, 0x420, 0x3d008282);
 
-       /* Aux PLL frac */
-       cx25840_write4(client, 0x114, 0x017dbf48);
-
-       /* Aux PLL int */
-       cx25840_write4(client, 0x110, 0x000a030e);
+       /*
+        * Aux PLL
+        * Initial setup for audio sample clock:
+        * 48 ksps, 16 bits/sample, x160 multiplier = 122.88 MHz
+        * Intial I2S output/master clock(?):
+        * 48 ksps, 16 bits/sample, x16 multiplier = 12.288 MHz
+        */
+       switch (state->id) {
+       case V4L2_IDENT_CX23888_AV:
+               /*
+                * 50.0 MHz * (0x7 + 0x0bedfa4/0x2000000)/3 = 122.88 MHz
+                * 368.64 MHz before post divide
+                * 122.88 MHz / 0xa = 12.288 MHz
+                */
+               cx25840_write4(client, 0x114, 0x00bedfa4);
+               cx25840_write4(client, 0x110, 0x000a0307);
+               break;
+       case V4L2_IDENT_CX23887_AV:
+               /*
+                * 25.0 MHz * (0xe + 0x17dbf48/0x2000000)/3 = 122.88 MHz
+                * 368.64 MHz before post divide
+                * 122.88 MHz / 0xa = 12.288 MHz
+                */
+               cx25840_write4(client, 0x114, 0x017dbf48);
+               cx25840_write4(client, 0x110, 0x000a030e);
+               break;
+       case V4L2_IDENT_CX23885_AV:
+       default:
+               /*
+                * 28.636363 MHz * (0xc + 0x1bf0c9e/0x2000000)/3 = 122.88 MHz
+                * 368.64 MHz before post divide
+                * 122.88 MHz / 0xa = 12.288 MHz
+                */
+               cx25840_write4(client, 0x114, 0x01bf0c9e);
+               cx25840_write4(client, 0x110, 0x000a030c);
+               break;
+       };
 
        /* ADC2 input select */
        cx25840_write(client, 0x102, 0x10);
@@ -321,6 +391,15 @@ static void cx23885_initialize(struct i2c_client *client)
        /* Select AFE clock pad output source */
        cx25840_write(client, 0x144, 0x05);
 
+       /* Drive GPIO2 direction and values for HVR1700
+        * where an onboard mux selects the output of demodulator
+        * vs the 417. Failure to set this results in no DTV.
+        * It's safe to set this across all Hauppauge boards
+        * currently, regardless of the board type.
+        */
+       cx25840_write(client, 0x160, 0x1d);
+       cx25840_write(client, 0x164, 0x00);
+
        /* Do the firmware load in a work handler to prevent.
           Otherwise the kernel is blocked waiting for the
           bit-banging i2c interface to finish uploading the
@@ -374,15 +453,12 @@ static void cx231xx_initialize(struct i2c_client *client)
        /* DIF Src phase inc */
        cx25840_write4(client, 0x340, 0x0df7df83);
 
-
        /* Luma */
        cx25840_write4(client, 0x414, 0x00107d12);
 
        /* Chroma */
        cx25840_write4(client, 0x420, 0x3d008282);
 
-
-
        /* ADC2 input select */
        cx25840_write(client, 0x102, 0x10);
 
@@ -395,7 +471,6 @@ static void cx231xx_initialize(struct i2c_client *client)
        /* White crush, Chroma AGC & Chroma Killer enabled */
        cx25840_write(client, 0x401, 0xe8);
 
-
        /* Do the firmware load in a work handler to prevent.
           Otherwise the kernel is blocked waiting for the
           bit-banging i2c interface to finish uploading the
@@ -489,42 +564,42 @@ void cx25840_std_setup(struct i2c_client *client)
        }
 
        /* DEBUG: Displays configured PLL frequency */
-    if (!state->is_cx231xx) {
-       pll_int = cx25840_read(client, 0x108);
-       pll_frac = cx25840_read4(client, 0x10c) & 0x1ffffff;
-       pll_post = cx25840_read(client, 0x109);
-       v4l_dbg(1, cx25840_debug, client,
-                               "PLL regs = int: %u, frac: %u, post: %u\n",
-                               pll_int, pll_frac, pll_post);
-
-       if (pll_post) {
-               int fin, fsc;
-               int pll = (28636363L * ((((u64)pll_int) << 25L) + pll_frac)) >> 25L;
-
-               pll /= pll_post;
-               v4l_dbg(1, cx25840_debug, client, "PLL = %d.%06d MHz\n",
-                               pll / 1000000, pll % 1000000);
-               v4l_dbg(1, cx25840_debug, client, "PLL/8 = %d.%06d MHz\n",
-                               pll / 8000000, (pll / 8) % 1000000);
-
-               fin = ((u64)src_decimation * pll) >> 12;
+       if (!is_cx231xx(state)) {
+               pll_int = cx25840_read(client, 0x108);
+               pll_frac = cx25840_read4(client, 0x10c) & 0x1ffffff;
+               pll_post = cx25840_read(client, 0x109);
                v4l_dbg(1, cx25840_debug, client,
-                               "ADC Sampling freq = %d.%06d MHz\n",
-                               fin / 1000000, fin % 1000000);
-
-               fsc = (((u64)sc) * pll) >> 24L;
-               v4l_dbg(1, cx25840_debug, client,
-                               "Chroma sub-carrier freq = %d.%06d MHz\n",
-                               fsc / 1000000, fsc % 1000000);
-
-               v4l_dbg(1, cx25840_debug, client, "hblank %i, hactive %i, "
-                       "vblank %i, vactive %i, vblank656 %i, src_dec %i, "
-                       "burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x, "
-                       "sc 0x%06x\n",
-                       hblank, hactive, vblank, vactive, vblank656,
-                       src_decimation, burst, luma_lpf, uv_lpf, comb, sc);
+                       "PLL regs = int: %u, frac: %u, post: %u\n",
+                       pll_int, pll_frac, pll_post);
+
+               if (pll_post) {
+                       int fin, fsc;
+                       int pll = (28636363L * ((((u64)pll_int) << 25L) + pll_frac)) >> 25L;
+
+                       pll /= pll_post;
+                       v4l_dbg(1, cx25840_debug, client, "PLL = %d.%06d MHz\n",
+                                       pll / 1000000, pll % 1000000);
+                       v4l_dbg(1, cx25840_debug, client, "PLL/8 = %d.%06d MHz\n",
+                                       pll / 8000000, (pll / 8) % 1000000);
+
+                       fin = ((u64)src_decimation * pll) >> 12;
+                       v4l_dbg(1, cx25840_debug, client,
+                                       "ADC Sampling freq = %d.%06d MHz\n",
+                                       fin / 1000000, fin % 1000000);
+
+                       fsc = (((u64)sc) * pll) >> 24L;
+                       v4l_dbg(1, cx25840_debug, client,
+                                       "Chroma sub-carrier freq = %d.%06d MHz\n",
+                                       fsc / 1000000, fsc % 1000000);
+
+                       v4l_dbg(1, cx25840_debug, client, "hblank %i, hactive %i, "
+                               "vblank %i, vactive %i, vblank656 %i, src_dec %i, "
+                               "burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x, "
+                               "sc 0x%06x\n",
+                               hblank, hactive, vblank, vactive, vblank656,
+                               src_decimation, burst, luma_lpf, uv_lpf, comb, sc);
+               }
        }
-    }
 
        /* Sets horizontal blanking delay and active lines */
        cx25840_write(client, 0x470, hblank);
@@ -610,13 +685,30 @@ static void input_change(struct i2c_client *client)
                }
                cx25840_write(client, 0x80b, 0x00);
        } else if (std & V4L2_STD_PAL) {
-               /* Follow tuner change procedure for PAL */
+               /* Autodetect audio standard and audio system */
                cx25840_write(client, 0x808, 0xff);
-               cx25840_write(client, 0x80b, 0x10);
+               /* Since system PAL-L is pretty much non-existant and
+                  not used by any public broadcast network, force
+                  6.5 MHz carrier to be interpreted as System DK,
+                  this avoids DK audio detection instability */
+              cx25840_write(client, 0x80b, 0x00);
        } else if (std & V4L2_STD_SECAM) {
-               /* Select autodetect for SECAM */
+               /* Autodetect audio standard and audio system */
                cx25840_write(client, 0x808, 0xff);
-               cx25840_write(client, 0x80b, 0x10);
+               /* If only one of SECAM-DK / SECAM-L is required, then force
+                 6.5MHz carrier, else autodetect it */
+               if ((std & V4L2_STD_SECAM_DK) &&
+                   !(std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) {
+                       /* 6.5 MHz carrier to be interpreted as System DK */
+                       cx25840_write(client, 0x80b, 0x00);
+              } else if (!(std & V4L2_STD_SECAM_DK) &&
+                         (std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) {
+                       /* 6.5 MHz carrier to be interpreted as System L */
+                       cx25840_write(client, 0x80b, 0x08);
+              } else {
+                       /* 6.5 MHz carrier to be autodetected */
+                       cx25840_write(client, 0x80b, 0x10);
+              }
        }
 
        cx25840_and_or(client, 0x810, ~0x01, 0);
@@ -628,6 +720,10 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
        struct cx25840_state *state = to_state(i2c_get_clientdata(client));
        u8 is_composite = (vid_input >= CX25840_COMPOSITE1 &&
                           vid_input <= CX25840_COMPOSITE8);
+       u8 is_component = (vid_input & CX25840_COMPONENT_ON) ==
+                       CX25840_COMPONENT_ON;
+       int luma = vid_input & 0xf0;
+       int chroma = vid_input & 0xf00;
        u8 reg;
 
        v4l_dbg(1, cx25840_debug, client,
@@ -640,18 +736,14 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
                reg = vid_input & 0xff;
                if ((vid_input & CX25840_SVIDEO_ON) == CX25840_SVIDEO_ON)
                        is_composite = 0;
-               else
+               else if ((vid_input & CX25840_COMPONENT_ON) == 0)
                        is_composite = 1;
 
                v4l_dbg(1, cx25840_debug, client, "mux cfg 0x%x comp=%d\n",
                        reg, is_composite);
-       } else
-       if (is_composite) {
+       } else if (is_composite) {
                reg = 0xf0 + (vid_input - CX25840_COMPOSITE1);
        } else {
-               int luma = vid_input & 0xf0;
-               int chroma = vid_input & 0xf00;
-
                if ((vid_input & ~0xff0) ||
                    luma < CX25840_SVIDEO_LUMA1 || luma > CX25840_SVIDEO_LUMA8 ||
                    chroma < CX25840_SVIDEO_CHROMA4 || chroma > CX25840_SVIDEO_CHROMA8) {
@@ -673,7 +765,7 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
         * configuration in reg (for the cx23885) so we have no
         * need to attempt to flip bits for earlier av decoders.
         */
-       if (!state->is_cx23885 && !state->is_cx231xx) {
+       if (!is_cx2388x(state) && !is_cx231xx(state)) {
                switch (aud_input) {
                case CX25840_AUDIO_SERIAL:
                        /* do nothing, use serial audio input */
@@ -693,10 +785,13 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
 
        cx25840_write(client, 0x103, reg);
 
-       /* Set INPUT_MODE to Composite (0) or S-Video (1) */
-       cx25840_and_or(client, 0x401, ~0x6, is_composite ? 0 : 0x02);
+       /* Set INPUT_MODE to Composite, S-Video or Component */
+       if (is_component)
+               cx25840_and_or(client, 0x401, ~0x6, 0x6);
+       else
+               cx25840_and_or(client, 0x401, ~0x6, is_composite ? 0 : 0x02);
 
-       if (!state->is_cx23885 && !state->is_cx231xx) {
+       if (!is_cx2388x(state) && !is_cx231xx(state)) {
                /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */
                cx25840_and_or(client, 0x102, ~0x2, (reg & 0x80) == 0 ? 2 : 0);
                /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2&CH3 */
@@ -705,22 +800,31 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
                else
                        cx25840_and_or(client, 0x102, ~0x4, 0);
        } else {
-               if (is_composite)
+               /* Set DUAL_MODE_ADC2 to 1 if component*/
+               cx25840_and_or(client, 0x102, ~0x4, is_component ? 0x4 : 0x0);
+               if (is_composite) {
                        /* ADC2 input select channel 2 */
                        cx25840_and_or(client, 0x102, ~0x2, 0);
-               else
-                       /* ADC2 input select channel 3 */
-                       cx25840_and_or(client, 0x102, ~0x2, 2);
+               } else if (!is_component) {
+                       /* S-Video */
+                       if (chroma >= CX25840_SVIDEO_CHROMA7) {
+                               /* ADC2 input select channel 3 */
+                               cx25840_and_or(client, 0x102, ~0x2, 2);
+                       } else {
+                               /* ADC2 input select channel 2 */
+                               cx25840_and_or(client, 0x102, ~0x2, 0);
+                       }
+               }
        }
 
        state->vid_input = vid_input;
        state->aud_input = aud_input;
-       if (!state->is_cx25836) {
+       if (!is_cx2583x(state)) {
                cx25840_audio_set_path(client);
                input_change(client);
        }
 
-       if (state->is_cx23885) {
+       if (is_cx2388x(state)) {
                /* Audio channel 1 src : Parallel 1 */
                cx25840_write(client, 0x124, 0x03);
 
@@ -736,7 +840,7 @@ static int set_input(struct i2c_client *client, enum cx25840_video_input vid_inp
                 */
                cx25840_write(client, 0x918, 0xa0);
                cx25840_write(client, 0x919, 0x01);
-       } else if (state->is_cx231xx) {
+       } else if (is_cx231xx(state)) {
                /* Audio channel 1 src : Parallel 1 */
                cx25840_write(client, 0x124, 0x03);
 
@@ -800,7 +904,7 @@ static int set_v4lstd(struct i2c_client *client)
        cx25840_and_or(client, 0x400, ~0xf, fmt);
        cx25840_and_or(client, 0x403, ~0x3, pal_m);
        cx25840_std_setup(client);
-       if (!state->is_cx25836)
+       if (!is_cx2583x(state))
                input_change(client);
        return 0;
 }
@@ -809,7 +913,7 @@ static int set_v4lstd(struct i2c_client *client)
 
 static int cx25840_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
 {
-    struct cx25840_state *state = to_state(sd);
+       struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
        switch (ctrl->id) {
@@ -863,7 +967,7 @@ static int cx25840_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
        case V4L2_CID_AUDIO_TREBLE:
        case V4L2_CID_AUDIO_BALANCE:
        case V4L2_CID_AUDIO_MUTE:
-               if (state->is_cx25836)
+               if (is_cx2583x(state))
                        return -EINVAL;
                return cx25840_audio_s_ctrl(sd, ctrl);
 
@@ -876,7 +980,7 @@ static int cx25840_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
 
 static int cx25840_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
 {
-    struct cx25840_state *state = to_state(sd);
+       struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
        switch (ctrl->id) {
@@ -900,7 +1004,7 @@ static int cx25840_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
        case V4L2_CID_AUDIO_TREBLE:
        case V4L2_CID_AUDIO_BALANCE:
        case V4L2_CID_AUDIO_MUTE:
-               if (state->is_cx25836)
+               if (is_cx2583x(state))
                        return -EINVAL;
                return cx25840_audio_g_ctrl(sd, ctrl);
        default:
@@ -1186,7 +1290,7 @@ static void log_audio_status(struct i2c_client *client)
 
 /* ----------------------------------------------------------------------- */
 
-/* This init operation must be called to load the driver's firmware.
+/* This load_fw operation must be called to load the driver's firmware.
    Without this the audio standard detection will fail and you will
    only get mono.
 
@@ -1196,19 +1300,19 @@ static void log_audio_status(struct i2c_client *client)
    postponing it is that loading this firmware takes a long time (seconds)
    due to the slow i2c bus speed. So it will speed up the boot process if
    you can avoid loading the fw as long as the video device isn't used.  */
-static int cx25840_init(struct v4l2_subdev *sd, u32 val)
+static int cx25840_load_fw(struct v4l2_subdev *sd)
 {
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
        if (!state->is_initialized) {
-               /* initialize on first use */
+               /* initialize and load firmware */
                state->is_initialized = 1;
-               if (state->is_cx25836)
+               if (is_cx2583x(state))
                        cx25836_initialize(client);
-               else if (state->is_cx23885)
+               else if (is_cx2388x(state))
                        cx23885_initialize(client);
-       else if (state->is_cx231xx)
+               else if (is_cx231xx(state))
                        cx231xx_initialize(client);
                else
                        cx25840_initialize(client);
@@ -1243,30 +1347,59 @@ static int cx25840_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *
 }
 #endif
 
+static int cx25840_s_audio_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct cx25840_state *state = to_state(sd);
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       u8 v;
+
+       if (is_cx2583x(state) || is_cx2388x(state) || is_cx231xx(state))
+               return 0;
+
+       v4l_dbg(1, cx25840_debug, client, "%s audio output\n",
+                       enable ? "enable" : "disable");
+
+       if (enable) {
+               v = cx25840_read(client, 0x115) | 0x80;
+               cx25840_write(client, 0x115, v);
+               v = cx25840_read(client, 0x116) | 0x03;
+               cx25840_write(client, 0x116, v);
+       } else {
+               v = cx25840_read(client, 0x115) & ~(0x80);
+               cx25840_write(client, 0x115, v);
+               v = cx25840_read(client, 0x116) & ~(0x03);
+               cx25840_write(client, 0x116, v);
+       }
+       return 0;
+}
+
 static int cx25840_s_stream(struct v4l2_subdev *sd, int enable)
 {
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
+       u8 v;
 
-       v4l_dbg(1, cx25840_debug, client, "%s output\n",
+       v4l_dbg(1, cx25840_debug, client, "%s video output\n",
                        enable ? "enable" : "disable");
        if (enable) {
-               if (state->is_cx23885 || state->is_cx231xx) {
-                       u8 v = (cx25840_read(client, 0x421) | 0x0b);
+               if (is_cx2388x(state) || is_cx231xx(state)) {
+                       v = cx25840_read(client, 0x421) | 0x0b;
                        cx25840_write(client, 0x421, v);
                } else {
-                       cx25840_write(client, 0x115,
-                                       state->is_cx25836 ? 0x0c : 0x8c);
-                       cx25840_write(client, 0x116,
-                                       state->is_cx25836 ? 0x04 : 0x07);
+                       v = cx25840_read(client, 0x115) | 0x0c;
+                       cx25840_write(client, 0x115, v);
+                       v = cx25840_read(client, 0x116) | 0x04;
+                       cx25840_write(client, 0x116, v);
                }
        } else {
-               if (state->is_cx23885 || state->is_cx231xx) {
-                       u8 v = cx25840_read(client, 0x421) & ~(0x0b);
+               if (is_cx2388x(state) || is_cx231xx(state)) {
+                       v = cx25840_read(client, 0x421) & ~(0x0b);
                        cx25840_write(client, 0x421, v);
                } else {
-                       cx25840_write(client, 0x115, 0x00);
-                       cx25840_write(client, 0x116, 0x00);
+                       v = cx25840_read(client, 0x115) & ~(0x0c);
+                       cx25840_write(client, 0x115, v);
+                       v = cx25840_read(client, 0x116) & ~(0x04);
+                       cx25840_write(client, 0x116, v);
                }
        }
        return 0;
@@ -1287,7 +1420,7 @@ static int cx25840_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
        default:
                break;
        }
-       if (state->is_cx25836)
+       if (is_cx2583x(state))
                return -EINVAL;
 
        switch (qc->id) {
@@ -1326,22 +1459,24 @@ static int cx25840_s_radio(struct v4l2_subdev *sd)
        return 0;
 }
 
-static int cx25840_s_video_routing(struct v4l2_subdev *sd, const struct v4l2_routing *route)
+static int cx25840_s_video_routing(struct v4l2_subdev *sd,
+                                  u32 input, u32 output, u32 config)
 {
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
-       return set_input(client, route->input, state->aud_input);
+       return set_input(client, input, state->aud_input);
 }
 
-static int cx25840_s_audio_routing(struct v4l2_subdev *sd, const struct v4l2_routing *route)
+static int cx25840_s_audio_routing(struct v4l2_subdev *sd,
+                                  u32 input, u32 output, u32 config)
 {
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
-       if (state->is_cx25836)
+       if (is_cx2583x(state))
                return -EINVAL;
-       return set_input(client, state->vid_input, route->input);
+       return set_input(client, state->vid_input, input);
 }
 
 static int cx25840_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *freq)
@@ -1349,7 +1484,7 @@ static int cx25840_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *fr
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
-       if (!state->is_cx25836)
+       if (!is_cx2583x(state))
                input_change(client);
        return 0;
 }
@@ -1366,7 +1501,7 @@ static int cx25840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
                return 0;
 
        vt->signal = vpres ? 0xffff : 0x0;
-       if (state->is_cx25836)
+       if (is_cx2583x(state))
                return 0;
 
        vt->capability |=
@@ -1397,7 +1532,7 @@ static int cx25840_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
-       if (state->radio || state->is_cx25836)
+       if (state->radio || is_cx2583x(state))
                return 0;
 
        switch (vt->audmode) {
@@ -1438,11 +1573,11 @@ static int cx25840_reset(struct v4l2_subdev *sd, u32 val)
        struct cx25840_state *state = to_state(sd);
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
-       if (state->is_cx25836)
+       if (is_cx2583x(state))
                cx25836_initialize(client);
-       else if (state->is_cx23885)
+       else if (is_cx2388x(state))
                cx23885_initialize(client);
-       else if (state->is_cx231xx)
+       else if (is_cx231xx(state))
                cx231xx_initialize(client);
        else
                cx25840_initialize(client);
@@ -1463,7 +1598,7 @@ static int cx25840_log_status(struct v4l2_subdev *sd)
        struct i2c_client *client = v4l2_get_subdevdata(sd);
 
        log_video_status(client);
-       if (!state->is_cx25836)
+       if (!is_cx2583x(state))
                log_audio_status(client);
        return 0;
 }
@@ -1476,8 +1611,9 @@ static const struct v4l2_subdev_core_ops cx25840_core_ops = {
        .g_ctrl = cx25840_g_ctrl,
        .s_ctrl = cx25840_s_ctrl,
        .queryctrl = cx25840_queryctrl,
+       .s_std = cx25840_s_std,
        .reset = cx25840_reset,
-       .init = cx25840_init,
+       .load_fw = cx25840_load_fw,
 #ifdef CONFIG_VIDEO_ADV_DEBUG
        .g_register = cx25840_g_register,
        .s_register = cx25840_s_register,
@@ -1486,7 +1622,6 @@ static const struct v4l2_subdev_core_ops cx25840_core_ops = {
 
 static const struct v4l2_subdev_tuner_ops cx25840_tuner_ops = {
        .s_frequency = cx25840_s_frequency,
-       .s_std = cx25840_s_std,
        .s_radio = cx25840_s_radio,
        .g_tuner = cx25840_g_tuner,
        .s_tuner = cx25840_s_tuner,
@@ -1495,6 +1630,7 @@ static const struct v4l2_subdev_tuner_ops cx25840_tuner_ops = {
 static const struct v4l2_subdev_audio_ops cx25840_audio_ops = {
        .s_clock_freq = cx25840_s_clock_freq,
        .s_routing = cx25840_s_audio_routing,
+       .s_stream = cx25840_s_audio_stream,
 };
 
 static const struct v4l2_subdev_video_ops cx25840_video_ops = {
@@ -1514,12 +1650,50 @@ static const struct v4l2_subdev_ops cx25840_ops = {
 
 /* ----------------------------------------------------------------------- */
 
+static u32 get_cx2388x_ident(struct i2c_client *client)
+{
+       u32 ret;
+
+       /* Come out of digital power down */
+       cx25840_write(client, 0x000, 0);
+
+       /* Detecting whether the part is cx23885/7/8 is more
+        * difficult than it needs to be. No ID register. Instead we
+        * probe certain registers indicated in the datasheets to look
+        * for specific defaults that differ between the silicon designs. */
+
+       /* It's either 885/7 if the IR Tx Clk Divider register exists */
+       if (cx25840_read4(client, 0x204) & 0xffff) {
+               /* CX23885 returns bogus repetitive byte values for the DIF,
+                * which doesn't exist for it. (Ex. 8a8a8a8a or 31313131) */
+               ret = cx25840_read4(client, 0x300);
+               if (((ret & 0xffff0000) >> 16) == (ret & 0xffff)) {
+                       /* No DIF */
+                       ret = V4L2_IDENT_CX23885_AV;
+               } else {
+                       /* CX23887 has a broken DIF, but the registers
+                        * appear valid (but unsed), good enough to detect. */
+                       ret = V4L2_IDENT_CX23887_AV;
+               }
+       } else if (cx25840_read4(client, 0x300) & 0x0fffffff) {
+               /* DIF PLL Freq Word reg exists; chip must be a CX23888 */
+               ret = V4L2_IDENT_CX23888_AV;
+       } else {
+               v4l_err(client, "Unable to detect h/w, assuming cx23887\n");
+               ret = V4L2_IDENT_CX23887_AV;
+       }
+
+       /* Back into digital power down */
+       cx25840_write(client, 0x000, 2);
+       return ret;
+}
+
 static int cx25840_probe(struct i2c_client *client,
                         const struct i2c_device_id *did)
 {
        struct cx25840_state *state;
        struct v4l2_subdev *sd;
-       u32 id;
+       u32 id = V4L2_IDENT_NONE;
        u16 device_id;
 
        /* Check if the adapter supports the needed features */
@@ -1536,17 +1710,22 @@ static int cx25840_probe(struct i2c_client *client,
         * 0x83 for the cx2583x and 0x84 for the cx2584x */
        if ((device_id & 0xff00) == 0x8300) {
                id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
-       }
-       else if ((device_id & 0xff00) == 0x8400) {
+       } else if ((device_id & 0xff00) == 0x8400) {
                id = V4L2_IDENT_CX25840 + ((device_id >> 4) & 0xf);
-       } /* else if (device_id == 0x0000) {
-               id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
-       } */ else if (device_id == 0x1313) {
-               id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
+       } else if (device_id == 0x0000) {
+               id = get_cx2388x_ident(client);
        } else if ((device_id & 0xfff0) == 0x5A30) {
-               id = V4L2_IDENT_CX25840 + ((device_id >> 4) & 0xf);
-       }
-       else {
+               /* The CX23100 (0x5A3C = 23100) doesn't have an A/V decoder */
+               id = V4L2_IDENT_CX2310X_AV;
+       } else if ((device_id & 0xff) == (device_id >> 8)) {
+               v4l_err(client,
+                       "likely a confused/unresponsive cx2388[578] A/V decoder"
+                       " found @ 0x%x (%s)\n",
+                       client->addr << 1, client->adapter->name);
+               v4l_err(client, "A method to reset it from the cx25840 driver"
+                       " software is not known at this time\n");
+               return -ENODEV;
+       } else {
                v4l_dbg(1, cx25840_debug, client, "cx25840 not found\n");
                return -ENODEV;
        }
@@ -1557,17 +1736,45 @@ static int cx25840_probe(struct i2c_client *client,
 
        sd = &state->sd;
        v4l2_i2c_subdev_init(sd, client, &cx25840_ops);
-       /* Note: revision '(device_id & 0x0f) == 2' was never built. The
-          marking skips from 0x1 == 22 to 0x3 == 23. */
-       v4l_info(client, "cx25%3x-2%x found @ 0x%x (%s)\n",
-                   (device_id & 0xfff0) >> 4,
-                   (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1 : (device_id & 0x0f),
-                   client->addr << 1, client->adapter->name);
+       switch (id) {
+       case V4L2_IDENT_CX23885_AV:
+               v4l_info(client, "cx23885 A/V decoder found @ 0x%x (%s)\n",
+                        client->addr << 1, client->adapter->name);
+               break;
+       case V4L2_IDENT_CX23887_AV:
+               v4l_info(client, "cx23887 A/V decoder found @ 0x%x (%s)\n",
+                        client->addr << 1, client->adapter->name);
+               break;
+       case V4L2_IDENT_CX23888_AV:
+               v4l_info(client, "cx23888 A/V decoder found @ 0x%x (%s)\n",
+                        client->addr << 1, client->adapter->name);
+               break;
+       case V4L2_IDENT_CX2310X_AV:
+               v4l_info(client, "cx%d A/V decoder found @ 0x%x (%s)\n",
+                        device_id, client->addr << 1, client->adapter->name);
+               break;
+       case V4L2_IDENT_CX25840:
+       case V4L2_IDENT_CX25841:
+       case V4L2_IDENT_CX25842:
+       case V4L2_IDENT_CX25843:
+               /* Note: revision '(device_id & 0x0f) == 2' was never built. The
+                  marking skips from 0x1 == 22 to 0x3 == 23. */
+               v4l_info(client, "cx25%3x-2%x found @ 0x%x (%s)\n",
+                        (device_id & 0xfff0) >> 4,
+                        (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1
+                                               : (device_id & 0x0f),
+                        client->addr << 1, client->adapter->name);
+               break;
+       case V4L2_IDENT_CX25836:
+       case V4L2_IDENT_CX25837:
+       default:
+               v4l_info(client, "cx25%3x-%x found @ 0x%x (%s)\n",
+                        (device_id & 0xfff0) >> 4, device_id & 0x0f,
+                        client->addr << 1, client->adapter->name);
+               break;
+       }
 
        state->c = client;
-       state->is_cx25836 = ((device_id & 0xff00) == 0x8300);
-       state->is_cx23885 = (device_id == 0x0000) || (device_id == 0x1313);
-    state->is_cx231xx = (device_id == 0x5A3E);
        state->vid_input = CX25840_COMPOSITE7;
        state->aud_input = CX25840_AUDIO8;
        state->audclk_freq = 48000;
@@ -1580,12 +1787,6 @@ static int cx25840_probe(struct i2c_client *client,
        state->id = id;
        state->rev = device_id;
 
-       if (state->is_cx23885) {
-               /* Drive GPIO2 direction and values */
-               cx25840_write(client, 0x160, 0x1d);
-               cx25840_write(client, 0x164, 0x00);
-       }
-
        return 0;
 }