V4L/DVB: ivtv: Automatic firmware reload
authorIan Armstrong <ian@iarmst.demon.co.uk>
Sat, 12 Jun 2010 16:41:57 +0000 (13:41 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 2 Aug 2010 17:48:13 +0000 (14:48 -0300)
If the firmware has failed, this patch will automatically reload &
restart the card. The previous card state will be restored on a
successful restart.  Firmware reload will only happen if neither the
encoder or decoder is active.  If the card is busy then behaviour is as
before, returning -EIO on device access until the reload can occur. On
cards that support video output, coloured bars will be displayed during
the reload.

Andy Walls (ivtv maintainer and patch committer) made minor tweaks to
comments and the logged messages, but nothing substantial otherwise.

Signed-off-by: Ian Armstrong <ian@iarmst.demon.co.uk>
Signed-off-by: Andy Walls <awalls@md.metrocast.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/video/ivtv/ivtv-driver.c
drivers/media/video/ivtv/ivtv-driver.h
drivers/media/video/ivtv/ivtv-fileops.c
drivers/media/video/ivtv/ivtv-firmware.c
drivers/media/video/ivtv/ivtv-mailbox.c
drivers/media/video/ivtv/ivtv-mailbox.h
drivers/media/video/ivtv/ivtv-streams.c
drivers/media/video/ivtv/ivtvfb.c

index 3737797f20ea1bc972b0f4796668ec4ae57e3ff4..90daa6e751d83dff10658368edb891ede0eaee81 100644 (file)
@@ -1444,6 +1444,7 @@ EXPORT_SYMBOL(ivtv_udma_unmap);
 EXPORT_SYMBOL(ivtv_udma_alloc);
 EXPORT_SYMBOL(ivtv_udma_prepare);
 EXPORT_SYMBOL(ivtv_init_on_first_open);
+EXPORT_SYMBOL(ivtv_firmware_check);
 
 module_init(module_start);
 module_exit(module_cleanup);
index c038dc8beb3c2de9749265836e6f4fdad1531085..bd084df4448ac0ccc81071373c0aeb48b9142762 100644 (file)
@@ -737,6 +737,7 @@ struct ivtv {
        struct v4l2_rect osd_rect;      /* current OSD position and size */
        struct v4l2_rect main_rect;     /* current Main window position and size */
        struct osd_info *osd_info;      /* ivtvfb private OSD info */
+       void (*ivtvfb_restore)(struct ivtv *itv); /* Used for a warm start */
 };
 
 static inline struct ivtv *to_ivtv(struct v4l2_device *v4l2_dev)
index 3fb21e1406a1cf390f5ce81e64604c1cc3d57f31..a6a2cdb81566f2aa6f68b614fedb8aea54f976af 100644 (file)
@@ -536,8 +536,12 @@ int ivtv_start_decoding(struct ivtv_open_id *id, int speed)
                        return -EBUSY;
                }
                rc = ivtv_start_v4l2_decode_stream(s, 0);
-               if (rc < 0)
-                       return rc;
+               if (rc < 0) {
+                       if (rc == -EAGAIN)
+                               rc = ivtv_start_v4l2_decode_stream(s, 0);
+                       if (rc < 0)
+                               return rc;
+               }
        }
        if (s->type == IVTV_DEC_STREAM_TYPE_MPG)
                return ivtv_set_speed(itv, speed);
@@ -926,19 +930,21 @@ static int ivtv_serialized_open(struct ivtv_stream *s, struct file *filp)
        IVTV_DEBUG_FILE("open %s\n", s->name);
 
 #ifdef CONFIG_VIDEO_ADV_DEBUG
+       /* Unless ivtv_fw_debug is set, error out if firmware dead. */
        if (ivtv_fw_debug) {
                IVTV_WARN("Opening %s with dead firmware lockout disabled\n",
                          video_device_node_name(vdev));
                IVTV_WARN("Selected firmware errors will be ignored\n");
-       }
-
-       /* Unless ivtv_fw_debug is set, error out if firmware dead. */
-       if (ivtv_firmware_check(itv, "ivtv_serialized_open") && !ivtv_fw_debug)
-               return -EIO;
+       } else {
 #else
-       if (ivtv_firmware_check(itv, "ivtv_serialized_open"))
-               return -EIO;
+       if (1) {
 #endif
+               res = ivtv_firmware_check(itv, "ivtv_serialized_open");
+               if (res == -EAGAIN)
+                       res = ivtv_firmware_check(itv, "ivtv_serialized_open");
+               if (res < 0)
+                       return -EIO;
+       }
 
        if (s->type == IVTV_DEC_STREAM_TYPE_MPG &&
                test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_YUV].s_flags))
index efb288d34ca31dff351f69d18d40115f9633cea6..d8bf2b01729dc9a8bcdcfc20e63480990b4a4729 100644 (file)
 #include "ivtv-mailbox.h"
 #include "ivtv-firmware.h"
 #include "ivtv-yuv.h"
+#include "ivtv-ioctl.h"
+#include "ivtv-cards.h"
 #include <linux/firmware.h>
+#include <media/saa7127.h>
 
 #define IVTV_MASK_SPU_ENABLE           0xFFFFFFFE
 #define IVTV_MASK_VPU_ENABLE15                 0xFFFFFFF6
@@ -272,6 +275,58 @@ void ivtv_init_mpeg_decoder(struct ivtv *itv)
        ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 4, 0, 0, 0, 1);
 }
 
+/* Try to restart the card & restore previous settings */
+int ivtv_firmware_restart(struct ivtv *itv)
+{
+       int rc = 0;
+       v4l2_std_id std;
+       struct ivtv_open_id fh;
+       fh.itv = itv;
+
+       if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)
+               /* Display test image during restart */
+               ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing,
+                   SAA7127_INPUT_TYPE_TEST_IMAGE,
+                   itv->card->video_outputs[itv->active_output].video_output,
+                   0);
+
+       mutex_lock(&itv->udma.lock);
+
+       rc = ivtv_firmware_init(itv);
+       if (rc) {
+               mutex_unlock(&itv->udma.lock);
+               return rc;
+       }
+
+       /* Allow settings to reload */
+       ivtv_mailbox_cache_invalidate(itv);
+
+       /* Restore video standard */
+       std = itv->std;
+       itv->std = 0;
+       ivtv_s_std(NULL, &fh, &std);
+
+       if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) {
+               ivtv_init_mpeg_decoder(itv);
+
+               /* Restore framebuffer if active */
+               if (itv->ivtvfb_restore)
+                       itv->ivtvfb_restore(itv);
+
+               /* Restore alpha settings */
+               ivtv_set_osd_alpha(itv);
+
+               /* Restore normal output */
+               ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing,
+                   SAA7127_INPUT_TYPE_NORMAL,
+                   itv->card->video_outputs[itv->active_output].video_output,
+                   0);
+       }
+
+       mutex_unlock(&itv->udma.lock);
+       return rc;
+}
+
 /* Check firmware running state. The checks fall through
    allowing multiple failures to be logged. */
 int ivtv_firmware_check(struct ivtv *itv, char *where)
@@ -315,5 +370,26 @@ int ivtv_firmware_check(struct ivtv *itv, char *where)
                }
        }
 
+       /* If something failed & currently idle, try to reload */
+       if (res && !atomic_read(&itv->capturing) &&
+                                               !atomic_read(&itv->decoding)) {
+               IVTV_INFO("Detected in %s that firmware had failed - "
+                         "Reloading\n", where);
+               res = ivtv_firmware_restart(itv);
+               /*
+                * Even if restarted ok, still signal a problem had occured.
+                * The caller can come through this function again to check
+                * if things are really ok after the restart.
+                */
+               if (!res) {
+                       IVTV_INFO("Firmware restart okay\n");
+                       res = -EAGAIN;
+               } else {
+                       IVTV_INFO("Firmware restart failed\n");
+               }
+       } else if (res) {
+               res = -EIO;
+       }
+
        return res;
 }
index 84577f6f41a24dd8f35956d041d2f0487f09af1e..e3ce967637853d906756009cce321c930c2f82c9 100644 (file)
@@ -377,3 +377,11 @@ void ivtv_api_get_data(struct ivtv_mailbox_data *mbdata, int mb,
        for (i = 0; i < argc; i++, p++)
                data[i] = readl(p);
 }
+
+/* Wipe api cache */
+void ivtv_mailbox_cache_invalidate(struct ivtv *itv)
+{
+       int i;
+       for (i = 0; i < 256; i++)
+               itv->api_cache[i].last_jiffies = 0;
+}
index 8247662c928ed916e4e8f2e015f4651e55f1d1a3..2c834d2cb56f2b233a1e610fc7cb4153c17dcabb 100644 (file)
@@ -30,5 +30,6 @@ int ivtv_api(struct ivtv *itv, int cmd, int args, u32 data[]);
 int ivtv_vapi_result(struct ivtv *itv, u32 data[CX2341X_MBOX_MAX_DATA], int cmd, int args, ...);
 int ivtv_vapi(struct ivtv *itv, int cmd, int args, ...);
 int ivtv_api_func(void *priv, u32 cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]);
+void ivtv_mailbox_cache_invalidate(struct ivtv *itv);
 
 #endif
index f0dd011a2cccda6fb35b86d930dc81a1b5e4190d..55df4190c28d03e7b32459b359de0c302ee7570b 100644 (file)
@@ -676,10 +676,7 @@ static int ivtv_setup_v4l2_decode_stream(struct ivtv_stream *s)
        ivtv_msleep_timeout(10, 0);
 
        /* Known failure point for firmware, so check */
-       if (ivtv_firmware_check(itv, "ivtv_setup_v4l2_decode_stream") < 0)
-               return -EIO;
-
-       return 0;
+       return ivtv_firmware_check(itv, "ivtv_setup_v4l2_decode_stream");
 }
 
 int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset)
index 9ff3425891edc7f678452317d33351ab80a4034c..2c2d862ca89f0a542ee7524b9ba649d9adbea950 100644 (file)
@@ -53,6 +53,7 @@
 #include "ivtv-i2c.h"
 #include "ivtv-udma.h"
 #include "ivtv-mailbox.h"
+#include "ivtv-firmware.h"
 
 /* card parameters */
 static int ivtvfb_card_id = -1;
@@ -178,6 +179,12 @@ struct osd_info {
        struct fb_info ivtvfb_info;
        struct fb_var_screeninfo ivtvfb_defined;
        struct fb_fix_screeninfo ivtvfb_fix;
+
+       /* Used for a warm start */
+       struct fb_var_screeninfo fbvar_cur;
+       int blank_cur;
+       u32 palette_cur[256];
+       u32 pan_cur;
 };
 
 struct ivtv_osd_coords {
@@ -199,6 +206,7 @@ static int ivtvfb_get_framebuffer(struct ivtv *itv, u32 *fbbase,
        u32 data[CX2341X_MBOX_MAX_DATA];
        int rc;
 
+       ivtv_firmware_check(itv, "ivtvfb_get_framebuffer");
        rc = ivtv_vapi_result(itv, data, CX2341X_OSD_GET_FRAMEBUFFER, 0);
        *fbbase = data[0];
        *fblength = data[1];
@@ -581,8 +589,10 @@ static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var)
        ivtv_window.height = var->yres;
 
        /* Minimum margin cannot be 0, as X won't allow such a mode */
-       if (!var->upper_margin) var->upper_margin++;
-       if (!var->left_margin) var->left_margin++;
+       if (!var->upper_margin)
+               var->upper_margin++;
+       if (!var->left_margin)
+               var->left_margin++;
        ivtv_window.top = var->upper_margin - 1;
        ivtv_window.left = var->left_margin - 1;
 
@@ -595,6 +605,9 @@ static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var)
        /* Force update of yuv registers */
        itv->yuv_info.yuv_forced_update = 1;
 
+       /* Keep a copy of these settings */
+       memcpy(&oi->fbvar_cur, var, sizeof(oi->fbvar_cur));
+
        IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n",
                      var->xres, var->yres,
                      var->xres_virtual, var->yres_virtual,
@@ -829,6 +842,8 @@ static int ivtvfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *inf
        itv->yuv_info.osd_y_pan = var->yoffset;
        /* Force update of yuv registers */
        itv->yuv_info.yuv_forced_update = 1;
+       /* Remember this value */
+       itv->osd_info->pan_cur = osd_pan_index;
        return 0;
 }
 
@@ -842,6 +857,7 @@ static int ivtvfb_set_par(struct fb_info *info)
        rc = ivtvfb_set_var(itv, &info->var);
        ivtvfb_pan_display(&info->var, info);
        ivtvfb_get_fix(itv, &info->fix);
+       ivtv_firmware_check(itv, "ivtvfb_set_par");
        return rc;
 }
 
@@ -859,6 +875,7 @@ static int ivtvfb_setcolreg(unsigned regno, unsigned red, unsigned green,
        if (info->var.bits_per_pixel <= 8) {
                write_reg(regno, 0x02a30);
                write_reg(color, 0x02a34);
+               itv->osd_info->palette_cur[regno] = color;
                return 0;
        }
        if (regno >= 16)
@@ -911,6 +928,7 @@ static int ivtvfb_blank(int blank_mode, struct fb_info *info)
                ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0);
                break;
        }
+       itv->osd_info->blank_cur = blank_mode;
        return 0;
 }
 
@@ -929,6 +947,21 @@ static struct fb_ops ivtvfb_ops = {
        .fb_blank       = ivtvfb_blank,
 };
 
+/* Restore hardware after firmware restart */
+static void ivtvfb_restore(struct ivtv *itv)
+{
+       struct osd_info *oi = itv->osd_info;
+       int i;
+
+       ivtvfb_set_var(itv, &oi->fbvar_cur);
+       ivtvfb_blank(oi->blank_cur, &oi->ivtvfb_info);
+       for (i = 0; i < 256; i++) {
+               write_reg(i, 0x02a30);
+               write_reg(oi->palette_cur[i], 0x02a34);
+       }
+       write_reg(oi->pan_cur, 0x02a0c);
+}
+
 /* Initialization */
 
 
@@ -1192,6 +1225,9 @@ static int ivtvfb_init_card(struct ivtv *itv)
        /* Enable the osd */
        ivtvfb_blank(FB_BLANK_UNBLANK, &itv->osd_info->ivtvfb_info);
 
+       /* Enable restart */
+       itv->ivtvfb_restore = ivtvfb_restore;
+
        /* Allocate DMA */
        ivtv_udma_alloc(itv);
        return 0;
@@ -1226,6 +1262,7 @@ static int ivtvfb_callback_cleanup(struct device *dev, void *p)
                        return 0;
                }
                IVTVFB_INFO("Unregister framebuffer %d\n", itv->instance);
+               itv->ivtvfb_restore = NULL;
                ivtvfb_blank(FB_BLANK_VSYNC_SUSPEND, &oi->ivtvfb_info);
                ivtvfb_release_buffers(itv);
                itv->osd_video_pbase = 0;