some v4l2 code lines are taken from Justin's bttv2 driver which is
     (c) 2000 Justin Schoeman <justin@suntiger.ee.up.ac.za>
 
+    Cropping and overscan support
+    Copyright (C) 2005, 2006 Michael H. Schimek <mschimek@gmx.at>
+    Sponsored by OPQ Systems AB
+
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
 static unsigned int irq_debug;
 static unsigned int gbuffers = 8;
 static unsigned int gbufsize = 0x208000;
+static unsigned int reset_crop = 1;
 
 static int video_nr = -1;
 static int radio_nr = -1;
 module_param(vbi_nr,            int, 0444);
 module_param(gbuffers,          int, 0444);
 module_param(gbufsize,          int, 0444);
+module_param(reset_crop,        int, 0444);
 
 module_param(v4l2,              int, 0644);
 module_param(bigendian,         int, 0644);
 MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)");
 MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8");
 MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000");
+MODULE_PARM_DESC(reset_crop,"reset cropping parameters at open(), default "
+                "is 1 (yes) for compatibility with older applications");
 MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)");
 MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)");
 MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)");
        }
 };
 
+/* minhdelayx1 first video pixel we can capture on a line and
+   hdelayx1    start of active video, both relative to rising edge of
+               /HRESET pulse (0H) in 1 / fCLKx1.
+   swidth      width of active video and
+   totalwidth  total line width, both in 1 / fCLKx1.
+   sqwidth     total line width in square pixels.
+   vdelay      start of active video in 2 * field lines relative to
+               trailing edge of /VRESET pulse (VDELAY register).
+   sheight     height of active video in 2 * field lines.
+   videostart0 ITU-R frame line number of the line corresponding
+               to vdelay in the first field. */
+#define CROPCAP(minhdelayx1, hdelayx1, swidth, totalwidth, sqwidth,     \
+               vdelay, sheight, videostart0)                            \
+       .cropcap.bounds.left = minhdelayx1,                              \
+       /* * 2 because vertically we count field lines times two, */     \
+       /* e.g. 23 * 2 to 23 * 2 + 576 in PAL-BGHI defrect. */           \
+       .cropcap.bounds.top = (videostart0) * 2 - (vdelay) + MIN_VDELAY, \
+       /* 4 is a safety margin at the end of the line. */               \
+       .cropcap.bounds.width = (totalwidth) - (minhdelayx1) - 4,        \
+       .cropcap.bounds.height = (sheight) + (vdelay) - MIN_VDELAY,      \
+       .cropcap.defrect.left = hdelayx1,                                \
+       .cropcap.defrect.top = (videostart0) * 2,                        \
+       .cropcap.defrect.width = swidth,                                 \
+       .cropcap.defrect.height = sheight,                               \
+       .cropcap.pixelaspect.numerator = totalwidth,                     \
+       .cropcap.pixelaspect.denominator = sqwidth,
+
 const struct bttv_tvnorm bttv_tvnorms[] = {
        /* PAL-BDGHI */
        /* max. active video is actually 922, but 924 is divisible by 4 and 3! */
                .hdelayx1       = 186,
                .hactivex1      = 924,
                .vdelay         = 0x20,
-               .vbipack        = 255,
+               .vbipack        = 255, /* min (2048 / 4, 0x1ff) & 0xff */
                .sram           = 0,
                /* ITU-R frame line number of the first VBI line
-                  we can capture, of the first and second field. */
-               .vbistart       = { 7,320 },
+                  we can capture, of the first and second field.
+                  The last line is determined by cropcap.bounds. */
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* Should be (768 * 1135 + 944 / 2) / 944.
+                          cropcap.defrect is used for image width
+                          checks, so we keep the old value 924. */
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
+               /* bt878 (and bt848?) can capture another
+                  line below active video. */
+               .cropcap.bounds.height = (576 + 2) + 0x20 - 2,
        },{
                .v4l2_id        = V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
                .name           = "NTSC",
                .hdelayx1       = 128,
                .hactivex1      = 910,
                .vdelay         = 0x1a,
-               .vbipack        = 144,
+               .vbipack        = 144, /* min (1600 / 4, 0x1ff) & 0xff */
                .sram           = 1,
                .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 128,
+                       /* Should be (640 * 910 + 780 / 2) / 780? */
+                       /* swidth */ 768,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
        },{
                .v4l2_id        = V4L2_STD_SECAM,
                .name           = "SECAM",
                .vbipack        = 255,
                .sram           = 0, /* like PAL, correct? */
                .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
        },{
                .v4l2_id        = V4L2_STD_PAL_Nc,
                .name           = "PAL-Nc",
                .vbipack        = 144,
                .sram           = -1,
                .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 130,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
        },{
                .v4l2_id        = V4L2_STD_PAL_M,
                .name           = "PAL-M",
                .vbipack        = 144,
                .sram           = -1,
                .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 135,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
        },{
                .v4l2_id        = V4L2_STD_PAL_N,
                .name           = "PAL-N",
                .vdelay         = 0x20,
                .vbipack        = 144,
                .sram           = -1,
-               .vbistart       = { 7, 320},
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ (768 * 1135 + 944 / 2) / 944,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
        },{
                .v4l2_id        = V4L2_STD_NTSC_M_JP,
                .name           = "NTSC-JP",
                .vdelay         = 0x16,
                .vbipack        = 144,
                .sram           = -1,
-               .vbistart       = {10, 273},
+               .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 135,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x16,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
        },{
                /* that one hopefully works with the strange timing
                 * which video recorders produce when playing a NTSC
                .vtotal         = 524,
                .sram           = -1,
                .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
        }
 };
 static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms);
 /* ----------------------------------------------------------------------- */
 /* resource management                                                     */
 
+/*
+   RESOURCE_    allocated by                freed by
+
+   VIDEO_READ   bttv_read 1)                bttv_read 2)
+
+   VIDEO_STREAM VIDIOC_STREAMON             VIDIOC_STREAMOFF
+                VIDIOC_QBUF 1)              bttv_release
+                VIDIOCMCAPTURE 1)
+
+   OVERLAY      VIDIOCCAPTURE on            VIDIOCCAPTURE off
+                VIDIOC_OVERLAY on           VIDIOC_OVERLAY off
+                3)                          bttv_release
+
+   VBI          VIDIOC_STREAMON             VIDIOC_STREAMOFF
+                VIDIOC_QBUF 1)              bttv_release
+                bttv_read, bttv_poll 1) 4)
+
+   1) The resource must be allocated when we enter buffer prepare functions
+      and remain allocated while buffers are in the DMA queue.
+   2) This is a single frame read.
+   3) VIDIOC_S_FBUF and VIDIOC_S_FMT (OVERLAY) still work when
+      RESOURCE_OVERLAY is allocated.
+   4) This is a continuous read, implies VIDIOC_STREAMON.
+
+   Note this driver permits video input and standard changes regardless if
+   resources are allocated.
+*/
+
+#define VBI_RESOURCES (RESOURCE_VBI)
+#define VIDEO_RESOURCES (RESOURCE_VIDEO_READ | \
+                        RESOURCE_VIDEO_STREAM | \
+                        RESOURCE_OVERLAY)
+
 static
 int check_alloc_btres(struct bttv *btv, struct bttv_fh *fh, int bit)
 {
+       int xbits; /* mutual exclusive resources */
+
        if (fh->resources & bit)
                /* have it already allocated */
                return 1;
 
+       xbits = bit;
+       if (bit & (RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM))
+               xbits |= RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM;
+
        /* is it free? */
-       mutex_lock(&btv->reslock);
-       if (btv->resources & bit) {
+       mutex_lock(&btv->lock);
+       if (btv->resources & xbits) {
                /* no, someone else uses it */
-               mutex_unlock(&btv->reslock);
-               return 0;
+               goto fail;
+       }
+
+       if ((bit & VIDEO_RESOURCES)
+           && 0 == (btv->resources & VIDEO_RESOURCES)) {
+               /* Do crop - use current, don't - use default parameters. */
+               __s32 top = btv->crop[!!fh->do_crop].rect.top;
+
+               if (btv->vbi_end > top)
+                       goto fail;
+
+               /* We cannot capture the same line as video and VBI data.
+                  Claim scan lines crop[].rect.top to bottom. */
+               btv->crop_start = top;
+       } else if (bit & VBI_RESOURCES) {
+               __s32 end = fh->vbi_fmt.end;
+
+               if (end > btv->crop_start)
+                       goto fail;
+
+               /* Claim scan lines above fh->vbi_fmt.end. */
+               btv->vbi_end = end;
        }
+
        /* it's free, grab it */
        fh->resources  |= bit;
        btv->resources |= bit;
-       mutex_unlock(&btv->reslock);
+       mutex_unlock(&btv->lock);
        return 1;
+
+ fail:
+       mutex_unlock(&btv->lock);
+       return 0;
 }
 
 static
        return (btv->resources & bit);
 }
 
+/* Call with btv->lock down. */
+static void
+disclaim_vbi_lines(struct bttv *btv)
+{
+       btv->vbi_end = 0;
+}
+
+/* Call with btv->lock down. */
+static void
+disclaim_video_lines(struct bttv *btv)
+{
+       const struct bttv_tvnorm *tvnorm;
+       u8 crop;
+
+       tvnorm = &bttv_tvnorms[btv->tvnorm];
+       btv->crop_start = tvnorm->cropcap.bounds.top
+               + tvnorm->cropcap.bounds.height;
+
+       /* VBI capturing ends at VDELAY, start of video capturing, no
+          matter how many lines the VBI RISC program expects. When video
+          capturing is off, it shall no longer "preempt" VBI capturing,
+          so we set VDELAY to maximum. */
+       crop = btread(BT848_E_CROP) | 0xc0;
+       btwrite(crop, BT848_E_CROP);
+       btwrite(0xfe, BT848_E_VDELAY_LO);
+       btwrite(crop, BT848_O_CROP);
+       btwrite(0xfe, BT848_O_VDELAY_LO);
+}
+
 static
 void free_btres(struct bttv *btv, struct bttv_fh *fh, int bits)
 {
                /* trying to free ressources not allocated by us ... */
                printk("bttv: BUG! (btres)\n");
        }
-       mutex_lock(&btv->reslock);
+       mutex_lock(&btv->lock);
        fh->resources  &= ~bits;
        btv->resources &= ~bits;
-       mutex_unlock(&btv->reslock);
+
+       bits = btv->resources;
+
+       if (0 == (bits & VIDEO_RESOURCES))
+               disclaim_video_lines(btv);
+
+       if (0 == (bits & VBI_RESOURCES))
+               disclaim_vbi_lines(btv);
+
+       mutex_unlock(&btv->lock);
 }
 
 /* ----------------------------------------------------------------------- */
                bttv_tda9880_setnorm(btv,btv->tvnorm);
 }
 
+static void
+bttv_crop_calc_limits(struct bttv_crop *c)
+{
+       /* Scale factor min. 1:1, max. 16:1. Min. image size
+          48 x 32. Scaled width must be a multiple of 4. */
+
+       if (1) {
+               /* For bug compatibility with VIDIOCGCAP and image
+                  size checks in earlier driver versions. */
+               c->min_scaled_width = 48;
+               c->min_scaled_height = 32;
+       } else {
+               c->min_scaled_width =
+                       (max(48, c->rect.width >> 4) + 3) & ~3;
+               c->min_scaled_height =
+                       max(32, c->rect.height >> 4);
+       }
+
+       c->max_scaled_width  = c->rect.width & ~3;
+       c->max_scaled_height = c->rect.height;
+}
+
+static void
+bttv_crop_reset(struct bttv_crop *c, int norm)
+{
+       c->rect = bttv_tvnorms[norm].cropcap.defrect;
+       bttv_crop_calc_limits(c);
+}
+
+/* Call with btv->lock down. */
 static int
 set_tvnorm(struct bttv *btv, unsigned int norm)
 {
        if (norm < 0 || norm >= BTTV_TVNORMS)
                return -EINVAL;
 
-       btv->tvnorm = norm;
        tvnorm = &bttv_tvnorms[norm];
 
+       if (btv->tvnorm < 0 ||
+           btv->tvnorm >= BTTV_TVNORMS ||
+           0 != memcmp(&bttv_tvnorms[btv->tvnorm].cropcap,
+                       &tvnorm->cropcap,
+                       sizeof (tvnorm->cropcap))) {
+               bttv_crop_reset(&btv->crop[0], norm);
+               btv->crop[1] = btv->crop[0]; /* current = default */
+
+               if (0 == (btv->resources & VIDEO_RESOURCES)) {
+                       btv->crop_start = tvnorm->cropcap.bounds.top
+                               + tvnorm->cropcap.bounds.height;
+               }
+       }
+
+       btv->tvnorm = norm;
+
        btwrite(tvnorm->adelay, BT848_ADELAY);
        btwrite(tvnorm->bdelay, BT848_BDELAY);
        btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH),
        return 0;
 }
 
+/* Call with btv->lock down. */
 static void
 set_input(struct bttv *btv, unsigned int input)
 {
        btv->loop_irq |= 1;
        bttv_set_dma(btv, 0x03);
        spin_unlock_irqrestore(&btv->s_lock,flags);
-       if (NULL == new)
-               free_btres(btv,fh,RESOURCE_OVERLAY);
        if (NULL != old) {
                dprintk("switch_overlay: old=%p state is %d\n",old,old->vb.state);
                bttv_dma_free(&fh->cap,btv, old);
                kfree(old);
        }
+       if (NULL == new)
+               free_btres(btv,fh,RESOURCE_OVERLAY);
        dprintk("switch_overlay: done\n");
        return retval;
 }
                               unsigned int width, unsigned int height,
                               enum v4l2_field field)
 {
+       struct bttv_fh *fh = q->priv_data;
        int redo_dma_risc = 0;
+       struct bttv_crop c;
+       int norm;
        int rc;
 
        /* check settings */
                if (width*height > buf->vb.bsize)
                        return -EINVAL;
                buf->vb.size = buf->vb.bsize;
+
+               /* Make sure tvnorm and vbi_end remain consistent
+                  until we're done. */
+               mutex_lock(&btv->lock);
+
+               norm = btv->tvnorm;
+
+               /* In this mode capturing always starts at defrect.top
+                  (default VDELAY), ignoring cropping parameters. */
+               if (btv->vbi_end > bttv_tvnorms[norm].cropcap.defrect.top) {
+                       mutex_unlock(&btv->lock);
+                       return -EINVAL;
+               }
+
+               mutex_unlock(&btv->lock);
+
+               c.rect = bttv_tvnorms[norm].cropcap.defrect;
        } else {
-               if (width  < 48 ||
-                   height < 32 ||
-                   width  > bttv_tvnorms[btv->tvnorm].swidth ||
-                   height > bttv_tvnorms[btv->tvnorm].sheight)
+               mutex_lock(&btv->lock);
+
+               norm = btv->tvnorm;
+               c = btv->crop[!!fh->do_crop];
+
+               mutex_unlock(&btv->lock);
+
+               if (width < c.min_scaled_width ||
+                   width > c.max_scaled_width ||
+                   height < c.min_scaled_height)
                        return -EINVAL;
+
+               switch (field) {
+               case V4L2_FIELD_TOP:
+               case V4L2_FIELD_BOTTOM:
+               case V4L2_FIELD_ALTERNATE:
+                       /* btv->crop counts frame lines. Max. scale
+                          factor is 16:1 for frames, 8:1 for fields. */
+                       if (height * 2 > c.max_scaled_height)
+                               return -EINVAL;
+                       break;
+
+               default:
+                       if (height > c.max_scaled_height)
+                               return -EINVAL;
+                       break;
+               }
+
                buf->vb.size = (width * height * fmt->depth) >> 3;
                if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
                        return -EINVAL;
        /* alloc + fill struct bttv_buffer (if changed) */
        if (buf->vb.width != width || buf->vb.height != height ||
            buf->vb.field != field ||
-           buf->tvnorm != btv->tvnorm || buf->fmt != fmt) {
+           buf->tvnorm != norm || buf->fmt != fmt ||
+           buf->crop.top != c.rect.top ||
+           buf->crop.left != c.rect.left ||
+           buf->crop.width != c.rect.width ||
+           buf->crop.height != c.rect.height) {
                buf->vb.width  = width;
                buf->vb.height = height;
                buf->vb.field  = field;
-               buf->tvnorm    = btv->tvnorm;
+               buf->tvnorm    = norm;
                buf->fmt       = fmt;
+               buf->crop      = c.rect;
                redo_dma_risc = 1;
        }
 
        return 0;
 }
 
-static int verify_window(const struct bttv_tvnorm *tvn,
-                        struct v4l2_window *win, int fixup)
+/* Given cropping boundaries b and the scaled width and height of a
+   single field or frame, which must not exceed hardware limits, this
+   function adjusts the cropping parameters c. */
+static void
+bttv_crop_adjust       (struct bttv_crop *             c,
+                        const struct v4l2_rect *       b,
+                        __s32                          width,
+                        __s32                          height,
+                        enum v4l2_field                field)
+{
+       __s32 frame_height = height << !V4L2_FIELD_HAS_BOTH(field);
+       __s32 max_left;
+       __s32 max_top;
+
+       if (width < c->min_scaled_width) {
+               /* Max. hor. scale factor 16:1. */
+               c->rect.width = width * 16;
+       } else if (width > c->max_scaled_width) {
+               /* Min. hor. scale factor 1:1. */
+               c->rect.width = width;
+
+               max_left = b->left + b->width - width;
+               max_left = min(max_left, (__s32) MAX_HDELAY);
+               if (c->rect.left > max_left)
+                       c->rect.left = max_left;
+       }
+
+       if (height < c->min_scaled_height) {
+               /* Max. vert. scale factor 16:1, single fields 8:1. */
+               c->rect.height = height * 16;
+       } else if (frame_height > c->max_scaled_height) {
+               /* Min. vert. scale factor 1:1.
+                  Top and height count field lines times two. */
+               c->rect.height = (frame_height + 1) & ~1;
+
+               max_top = b->top + b->height - c->rect.height;
+               if (c->rect.top > max_top)
+                       c->rect.top = max_top;
+       }
+
+       bttv_crop_calc_limits(c);
+}
+
+/* Returns an error if scaling to a frame or single field with the given
+   width and height is not possible with the current cropping parameters
+   and width aligned according to width_mask. If adjust_size is TRUE the
+   function may adjust the width and/or height instead, rounding width
+   to (width + width_bias) & width_mask. If adjust_crop is TRUE it may
+   also adjust the current cropping parameters to get closer to the
+   desired image size. */
+static int
+limit_scaled_size       (struct bttv_fh *               fh,
+                        __s32 *                        width,
+                        __s32 *                        height,
+                        enum v4l2_field                field,
+                        unsigned int                   width_mask,
+                        unsigned int                   width_bias,
+                        int                            adjust_size,
+                        int                            adjust_crop)
+{
+       struct bttv *btv = fh->btv;
+       const struct v4l2_rect *b;
+       struct bttv_crop *c;
+       __s32 min_width;
+       __s32 min_height;
+       __s32 max_width;
+       __s32 max_height;
+       int rc;
+
+       BUG_ON((int) width_mask >= 0 ||
+              width_bias >= (unsigned int) -width_mask);
+
+       /* Make sure tvnorm, vbi_end and the current cropping parameters
+          remain consistent until we're done. */
+       mutex_lock(&btv->lock);
+
+       b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds;
+
+       /* Do crop - use current, don't - use default parameters. */
+       c = &btv->crop[!!fh->do_crop];
+
+       if (fh->do_crop
+           && adjust_size
+           && adjust_crop
+           && !locked_btres(btv, VIDEO_RESOURCES)) {
+               min_width = 48;
+               min_height = 32;
+
+               /* We cannot scale up. When the scaled image is larger
+                  than crop.rect we adjust the crop.rect as required
+                  by the V4L2 spec, hence cropcap.bounds are our limit. */
+               max_width = min(b->width, (__s32) MAX_HACTIVE);
+               max_height = b->height;
+
+               /* We cannot capture the same line as video and VBI data.
+                  Note btv->vbi_end is really a minimum, see
+                  bttv_vbi_try_fmt(). */
+               if (btv->vbi_end > b->top) {
+                       max_height -= btv->vbi_end - b->top;
+                       rc = -EBUSY;
+                       if (min_height > max_height)
+                               goto fail;
+               }
+       } else {
+               rc = -EBUSY;
+               if (btv->vbi_end > c->rect.top)
+                       goto fail;
+
+               min_width  = c->min_scaled_width;
+               min_height = c->min_scaled_height;
+               max_width  = c->max_scaled_width;
+               max_height = c->max_scaled_height;
+
+               adjust_crop = 0;
+       }
+
+       min_width = (min_width - width_mask - 1) & width_mask;
+       max_width = max_width & width_mask;
+
+       /* Max. scale factor is 16:1 for frames, 8:1 for fields. */
+       min_height = min_height;
+       /* Min. scale factor is 1:1. */
+       max_height >>= !V4L2_FIELD_HAS_BOTH(field);
+
+       if (adjust_size) {
+               *width = clamp(*width, min_width, max_width);
+               *height = clamp(*height, min_height, max_height);
+
+               /* Round after clamping to avoid overflow. */
+               *width = (*width + width_bias) & width_mask;
+
+               if (adjust_crop) {
+                       bttv_crop_adjust(c, b, *width, *height, field);
+
+                       if (btv->vbi_end > c->rect.top) {
+                               /* Move the crop window out of the way. */
+                               c->rect.top = btv->vbi_end;
+                       }
+               }
+       } else {
+               rc = -EINVAL;
+               if (*width  < min_width ||
+                   *height < min_height ||
+                   *width  > max_width ||
+                   *height > max_height ||
+                   0 != (*width & ~width_mask))
+                       goto fail;
+       }
+
+       rc = 0; /* success */
+
+ fail:
+       mutex_unlock(&btv->lock);
+
+       return rc;
+}
+
+/* Returns an error if the given overlay window dimensions are not
+   possible with the current cropping parameters. If adjust_size is
+   TRUE the function may adjust the window width and/or height
+   instead, however it always rounds the horizontal position and
+   width as btcx_align() does. If adjust_crop is TRUE the function
+   may also adjust the current cropping parameters to get closer
+   to the desired window size. */
+static int
+verify_window          (struct bttv_fh *               fh,
+                        struct v4l2_window *           win,
+                        int                            adjust_size,
+                        int                            adjust_crop)
 {
        enum v4l2_field field;
-       int maxw, maxh;
+       unsigned int width_mask;
+       int rc;
 
        if (win->w.width  < 48 || win->w.height < 32)
                return -EINVAL;
                return -EINVAL;
 
        field = win->field;
-       maxw  = tvn->swidth;
-       maxh  = tvn->sheight;
 
        if (V4L2_FIELD_ANY == field) {
-               field = (win->w.height > maxh/2)
+               __s32 height2;
+
+               height2 = fh->btv->crop[!!fh->do_crop].rect.height >> 1;
+               field = (win->w.height > height2)
                        ? V4L2_FIELD_INTERLACED
                        : V4L2_FIELD_TOP;
        }
        switch (field) {
        case V4L2_FIELD_TOP:
        case V4L2_FIELD_BOTTOM:
-               maxh = maxh / 2;
-               break;
        case V4L2_FIELD_INTERLACED:
                break;
        default:
                return -EINVAL;
        }
 
-       if (!fixup && (win->w.width > maxw || win->w.height > maxh))
+       /* 4-byte alignment. */
+       if (NULL == fh->ovfmt)
                return -EINVAL;
+       width_mask = ~0;
+       switch (fh->ovfmt->depth) {
+       case 8:
+       case 24:
+               width_mask = ~3;
+               break;
+       case 16:
+               width_mask = ~1;
+               break;
+       case 32:
+               break;
+       default:
+               BUG();
+       }
+
+       win->w.width -= win->w.left & ~width_mask;
+       win->w.left = (win->w.left - width_mask - 1) & width_mask;
+
+       rc = limit_scaled_size(fh, &win->w.width, &win->w.height,
+                              field, width_mask,
+                              /* width_bias: round down */ 0,
+                              adjust_size, adjust_crop);
+       if (0 != rc)
+               return rc;
 
-       if (win->w.width > maxw)
-               win->w.width = maxw;
-       if (win->w.height > maxh)
-               win->w.height = maxh;
        win->field = field;
        return 0;
 }
                return -EINVAL;
        if (!(fh->ovfmt->flags & FORMAT_FLAGS_PACKED))
                return -EINVAL;
-       retval = verify_window(&bttv_tvnorms[btv->tvnorm],win,fixup);
+       retval = verify_window(fh, win,
+                              /* adjust_size */ fixup,
+                              /* adjust_crop */ fixup);
        if (0 != retval)
                return retval;
 
                struct bttv_buffer *new;
 
                new = videobuf_alloc(sizeof(*new));
+               new->crop = btv->crop[!!fh->do_crop].rect;
                bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
                retval = bttv_switch_overlay(btv,fh,new);
        }
 
        switch (fh->type) {
        case V4L2_BUF_TYPE_VIDEO_CAPTURE:
-               res = RESOURCE_VIDEO;
+               res = RESOURCE_VIDEO_STREAM;
                break;
        case V4L2_BUF_TYPE_VBI_CAPTURE:
                res = RESOURCE_VBI;
                f->fmt.win.field = fh->ov.field;
                return 0;
        case V4L2_BUF_TYPE_VBI_CAPTURE:
-               bttv_vbi_get_fmt(fh,f);
+               bttv_vbi_get_fmt(fh, &f->fmt.vbi);
                return 0;
        default:
                return -EINVAL;
 }
 
 static int bttv_try_fmt(struct bttv_fh *fh, struct bttv *btv,
-                       struct v4l2_format *f)
+                       struct v4l2_format *f, int adjust_crop)
 {
        switch (f->type) {
        case V4L2_BUF_TYPE_VIDEO_CAPTURE:
        {
                const struct bttv_format *fmt;
                enum v4l2_field field;
-               unsigned int maxw,maxh;
+               __s32 width, height;
+               int rc;
 
                fmt = format_by_fourcc(f->fmt.pix.pixelformat);
                if (NULL == fmt)
                        return -EINVAL;
 
-               /* fixup format */
-               maxw  = bttv_tvnorms[btv->tvnorm].swidth;
-               maxh  = bttv_tvnorms[btv->tvnorm].sheight;
                field = f->fmt.pix.field;
-               if (V4L2_FIELD_ANY == field)
-                       field = (f->fmt.pix.height > maxh/2)
+               if (V4L2_FIELD_ANY == field) {
+                       __s32 height2;
+
+                       height2 = btv->crop[!!fh->do_crop].rect.height >> 1;
+                       field = (f->fmt.pix.height > height2)
                                ? V4L2_FIELD_INTERLACED
                                : V4L2_FIELD_BOTTOM;
+               }
                if (V4L2_FIELD_SEQ_BT == field)
                        field = V4L2_FIELD_SEQ_TB;
                switch (field) {
                case V4L2_FIELD_TOP:
                case V4L2_FIELD_BOTTOM:
                case V4L2_FIELD_ALTERNATE:
-                       maxh = maxh/2;
-                       break;
                case V4L2_FIELD_INTERLACED:
                        break;
                case V4L2_FIELD_SEQ_TB:
                        return -EINVAL;
                }
 
+               width = f->fmt.pix.width;
+               height = f->fmt.pix.height;
+
+               rc = limit_scaled_size(fh, &width, &height, field,
+                                      /* width_mask: 4 pixels */ ~3,
+                                      /* width_bias: nearest */ 2,
+                                      /* adjust_size */ 1,
+                                      adjust_crop);
+               if (0 != rc)
+                       return rc;
+
                /* update data for the application */
                f->fmt.pix.field = field;
-               if (f->fmt.pix.width  < 48)
-                       f->fmt.pix.width  = 48;
-               if (f->fmt.pix.height < 32)
-                       f->fmt.pix.height = 32;
-               if (f->fmt.pix.width  > maxw)
-                       f->fmt.pix.width = maxw;
-               if (f->fmt.pix.height > maxh)
-                       f->fmt.pix.height = maxh;
-               pix_format_set_size (&f->fmt.pix, fmt,
-                                    f->fmt.pix.width & ~3,
-                                    f->fmt.pix.height);
+               pix_format_set_size(&f->fmt.pix, fmt, width, height);
 
                return 0;
        }
        case V4L2_BUF_TYPE_VIDEO_OVERLAY:
-               return verify_window(&bttv_tvnorms[btv->tvnorm],
-                                    &f->fmt.win, 1);
+               return verify_window(fh, &f->fmt.win,
+                                    /* adjust_size */ 1,
+                                    /* adjust_crop */ 0);
        case V4L2_BUF_TYPE_VBI_CAPTURE:
-               bttv_vbi_try_fmt(fh,f);
-               return 0;
+               return bttv_vbi_try_fmt(fh, &f->fmt.vbi);
        default:
                return -EINVAL;
        }
                retval = bttv_switch_type(fh,f->type);
                if (0 != retval)
                        return retval;
-               retval = bttv_try_fmt(fh,btv,f);
+               retval = bttv_try_fmt(fh,btv,f, /* adjust_crop */ 1);
                if (0 != retval)
                        return retval;
                fmt = format_by_fourcc(f->fmt.pix.pixelformat);
                retval = bttv_switch_type(fh,f->type);
                if (0 != retval)
                        return retval;
-               if (locked_btres(fh->btv, RESOURCE_VBI))
-                       return -EBUSY;
-               bttv_vbi_try_fmt(fh,f);
-               bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]);
-               bttv_vbi_get_fmt(fh,f);
-               return 0;
+               return bttv_vbi_set_fmt(fh, &f->fmt.vbi);
        default:
                return -EINVAL;
        }
                if (*on) {
                        fh->ov.tvnorm = btv->tvnorm;
                        new = videobuf_alloc(sizeof(*new));
+                       new->crop = btv->crop[!!fh->do_crop].rect;
                        bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
                } else {
                        new = NULL;
                struct video_mmap *vm = arg;
                struct bttv_buffer *buf;
                enum v4l2_field field;
+               __s32 height2;
+               int res;
 
                if (vm->frame >= VIDEO_MAX_FRAME)
                        return -EINVAL;
 
+               res = bttv_resource(fh);
+               if (!check_alloc_btres(btv, fh, res))
+                       return -EBUSY;
+
                mutex_lock(&fh->cap.lock);
                retval = -EINVAL;
                buf = (struct bttv_buffer *)fh->cap.bufs[vm->frame];
                    buf->vb.state == STATE_ACTIVE)
                        goto fh_unlock_and_return;
 
-               field = (vm->height > bttv_tvnorms[btv->tvnorm].sheight/2)
+               height2 = btv->crop[!!fh->do_crop].rect.height >> 1;
+               field = (vm->height > height2)
                        ? V4L2_FIELD_INTERLACED
                        : V4L2_FIELD_BOTTOM;
                retval = bttv_prepare_buffer(&fh->cap,btv,buf,
        }
 
        case VIDIOCGVBIFMT:
-       {
-               struct vbi_format *fmt = (void *) arg;
-               struct v4l2_format fmt2;
-
                if (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE) {
                        retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE);
                        if (0 != retval)
                                return retval;
                }
-               bttv_vbi_get_fmt(fh, &fmt2);
-
-               memset(fmt,0,sizeof(*fmt));
-               fmt->sampling_rate    = fmt2.fmt.vbi.sampling_rate;
-               fmt->samples_per_line = fmt2.fmt.vbi.samples_per_line;
-               fmt->sample_format    = VIDEO_PALETTE_RAW;
-               fmt->start[0]         = fmt2.fmt.vbi.start[0];
-               fmt->count[0]         = fmt2.fmt.vbi.count[0];
-               fmt->start[1]         = fmt2.fmt.vbi.start[1];
-               fmt->count[1]         = fmt2.fmt.vbi.count[1];
-               if (fmt2.fmt.vbi.flags & V4L2_VBI_UNSYNC)
-                       fmt->flags   |= VBI_UNSYNC;
-               if (fmt2.fmt.vbi.flags & V4L2_VBI_INTERLACED)
-                       fmt->flags   |= VBI_INTERLACED;
-               return 0;
-       }
-       case VIDIOCSVBIFMT:
-       {
-               struct vbi_format *fmt = (void *) arg;
-               struct v4l2_format fmt2;
 
-               retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE);
-               if (0 != retval)
-                       return retval;
-               bttv_vbi_get_fmt(fh, &fmt2);
-
-               if (fmt->sampling_rate    != fmt2.fmt.vbi.sampling_rate     ||
-                   fmt->samples_per_line != fmt2.fmt.vbi.samples_per_line  ||
-                   fmt->sample_format    != VIDEO_PALETTE_RAW              ||
-                   fmt->start[0]         != fmt2.fmt.vbi.start[0]          ||
-                   fmt->start[1]         != fmt2.fmt.vbi.start[1]          ||
-                   fmt->count[0]         != fmt->count[1]                  ||
-                   fmt->count[0]         <  1                              ||
-                   fmt->count[0]         >  32 /* VBI_MAXLINES */)
-                       return -EINVAL;
+               /* fall through */
 
-               bttv_vbi_setlines(fh,btv,fmt->count[0]);
-               return 0;
-       }
+       case VIDIOCSVBIFMT:
+               return v4l_compat_translate_ioctl(inode, file, cmd,
+                                                 arg, bttv_do_ioctl);
 
        case BTTV_VERSION:
        case VIDIOCGFREQ:
        case VIDIOC_TRY_FMT:
        {
                struct v4l2_format *f = arg;
-               return bttv_try_fmt(fh,btv,f);
+               return bttv_try_fmt(fh,btv,f, /* adjust_crop */ 0);
        }
        case VIDIOC_G_FMT:
        {
                if (0 == (fmt->flags & FORMAT_FLAGS_PACKED))
                        return -EINVAL;
 
-               mutex_lock(&fh->cap.lock);
                retval = -EINVAL;
                if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) {
-                       if (fb->fmt.width > bttv_tvnorms[btv->tvnorm].swidth)
-                               goto fh_unlock_and_return;
-                       if (fb->fmt.height > bttv_tvnorms[btv->tvnorm].sheight)
-                               goto fh_unlock_and_return;
+                       __s32 width = fb->fmt.width;
+                       __s32 height = fb->fmt.height;
+
+                       retval = limit_scaled_size(fh, &width, &height,
+                                                  V4L2_FIELD_INTERLACED,
+                                                  /* width_mask */ ~3,
+                                                  /* width_bias */ 2,
+                                                  /* adjust_size */ 0,
+                                                  /* adjust_crop */ 0);
+                       if (0 != retval)
+                               return retval;
                }
 
                /* ok, accept it */
+               mutex_lock(&fh->cap.lock);
                btv->fbuf.base       = fb->base;
                btv->fbuf.fmt.width  = fb->fmt.width;
                btv->fbuf.fmt.height = fb->fmt.height;
                                struct bttv_buffer *new;
 
                                new = videobuf_alloc(sizeof(*new));
+                               new->crop = btv->crop[!!fh->do_crop].rect;
                                bttv_overlay_risc(btv,&fh->ov,fh->ovfmt,new);
                                retval = bttv_switch_overlay(btv,fh,new);
                        }
                return videobuf_querybuf(bttv_queue(fh),arg);
 
        case VIDIOC_QBUF:
+       {
+               int res = bttv_resource(fh);
+
+               if (!check_alloc_btres(btv, fh, res))
+                       return -EBUSY;
                return videobuf_qbuf(bttv_queue(fh),arg);
+       }
 
        case VIDIOC_DQBUF:
                return videobuf_dqbuf(bttv_queue(fh),arg,
                return v4l2_prio_change(&btv->prio, &fh->prio, *prio);
        }
 
+       case VIDIOC_CROPCAP:
+       {
+               struct v4l2_cropcap *cap = arg;
+               enum v4l2_buf_type type;
+
+               type = cap->type;
+
+               if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+                   type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+                       return -EINVAL;
+
+               *cap = bttv_tvnorms[btv->tvnorm].cropcap;
+               cap->type = type;
+
+               return 0;
+       }
+       case VIDIOC_G_CROP:
+       {
+               struct v4l2_crop * crop = arg;
+
+               if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+                   crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+                       return -EINVAL;
+
+               /* No fh->do_crop = 1; because btv->crop[1] may be
+                  inconsistent with fh->width or fh->height and apps
+                  do not expect a change here. */
+
+               crop->c = btv->crop[!!fh->do_crop].rect;
+
+               return 0;
+       }
+       case VIDIOC_S_CROP:
+       {
+               struct v4l2_crop *crop = arg;
+               const struct v4l2_rect *b;
+               struct bttv_crop c;
+               __s32 b_left;
+               __s32 b_top;
+               __s32 b_right;
+               __s32 b_bottom;
+
+               if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+                   crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+                       return -EINVAL;
+
+               retval = v4l2_prio_check(&btv->prio,&fh->prio);
+               if (0 != retval)
+                       return retval;
+
+               /* Make sure tvnorm, vbi_end and the current cropping
+                  parameters remain consistent until we're done. Note
+                  read() may change vbi_end in check_alloc_btres(). */
+               mutex_lock(&btv->lock);
+
+               retval = -EBUSY;
+
+               if (locked_btres(fh->btv, VIDEO_RESOURCES))
+                       goto btv_unlock_and_return;
+
+               b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds;
+
+               b_left = b->left;
+               b_right = b_left + b->width;
+               b_bottom = b->top + b->height;
+
+               b_top = max(b->top, btv->vbi_end);
+               if (b_top + 32 >= b_bottom)
+                       goto btv_unlock_and_return;
+
+               /* Min. scaled size 48 x 32. */
+               c.rect.left = clamp(crop->c.left, b_left, b_right - 48);
+               c.rect.left = min(c.rect.left, (__s32) MAX_HDELAY);
+
+               c.rect.width = clamp(crop->c.width,
+                                    48, b_right - c.rect.left);
+
+               c.rect.top = clamp(crop->c.top, b_top, b_bottom - 32);
+               /* Top and height must be a multiple of two. */
+               c.rect.top = (c.rect.top + 1) & ~1;
+
+               c.rect.height = clamp(crop->c.height,
+                                     32, b_bottom - c.rect.top);
+               c.rect.height = (c.rect.height + 1) & ~1;
+
+               bttv_crop_calc_limits(&c);
+
+               btv->crop[1] = c;
+
+               mutex_unlock(&btv->lock);
+
+               fh->do_crop = 1;
+
+               mutex_lock(&fh->cap.lock);
+
+               if (fh->width < c.min_scaled_width) {
+                       fh->width = c.min_scaled_width;
+                       btv->init.width = c.min_scaled_width;
+               } else if (fh->width > c.max_scaled_width) {
+                       fh->width = c.max_scaled_width;
+                       btv->init.width = c.max_scaled_width;
+               }
+
+               if (fh->height < c.min_scaled_height) {
+                       fh->height = c.min_scaled_height;
+                       btv->init.height = c.min_scaled_height;
+               } else if (fh->height > c.max_scaled_height) {
+                       fh->height = c.max_scaled_height;
+                       btv->init.height = c.max_scaled_height;
+               }
+
+               mutex_unlock(&fh->cap.lock);
+
+               return 0;
+       }
+
        case VIDIOC_ENUMSTD:
        case VIDIOC_G_STD:
        case VIDIOC_S_STD:
  fh_unlock_and_return:
        mutex_unlock(&fh->cap.lock);
        return retval;
+
+ btv_unlock_and_return:
+       mutex_unlock(&btv->lock);
+       return retval;
 }
 
 static int bttv_ioctl(struct inode *inode, struct file *file,
 
        switch (cmd) {
        case BTTV_VBISIZE:
+       {
+               const struct bttv_tvnorm *tvnorm;
+
+               tvnorm = fh->vbi_fmt.tvnorm;
+
+               if (fh->vbi_fmt.fmt.start[0] != tvnorm->vbistart[0] ||
+                   fh->vbi_fmt.fmt.start[1] != tvnorm->vbistart[1] ||
+                   fh->vbi_fmt.fmt.count[0] != fh->vbi_fmt.fmt.count[1]) {
+                       /* BTTV_VBISIZE cannot express these parameters,
+                          however open() resets the paramters to defaults
+                          and apps shouldn't call BTTV_VBISIZE after
+                          VIDIOC_S_FMT. */
+                       return -EINVAL;
+               }
+
                bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE);
-               return fh->lines * 2 * 2048;
+               return (fh->vbi_fmt.fmt.count[0] * 2
+                       * fh->vbi_fmt.fmt.samples_per_line);
+       }
+
        default:
                return video_usercopy(inode, file, cmd, arg, bttv_do_ioctl);
        }
 
        switch (fh->type) {
        case V4L2_BUF_TYPE_VIDEO_CAPTURE:
-               if (locked_btres(fh->btv,RESOURCE_VIDEO))
+               if (!check_alloc_btres(fh->btv, fh, RESOURCE_VIDEO_READ)) {
+                       /* VIDEO_READ in use by another fh,
+                          or VIDEO_STREAM by any fh. */
                        return -EBUSY;
+               }
                retval = videobuf_read_one(&fh->cap, data, count, ppos,
                                           file->f_flags & O_NONBLOCK);
+               free_btres(fh->btv, fh, RESOURCE_VIDEO_READ);
                break;
        case V4L2_BUF_TYPE_VBI_CAPTURE:
                if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI))
                return videobuf_poll_stream(file, &fh->vbi, wait);
        }
 
-       if (check_btres(fh,RESOURCE_VIDEO)) {
+       if (check_btres(fh,RESOURCE_VIDEO_STREAM)) {
                /* streaming capture */
                if (list_empty(&fh->cap.stream))
                        return POLLERR;
                mutex_lock(&fh->cap.lock);
                if (NULL == fh->cap.read_buf) {
                        /* need to capture a new frame */
-                       if (locked_btres(fh->btv,RESOURCE_VIDEO)) {
+                       if (locked_btres(fh->btv,RESOURCE_VIDEO_STREAM)) {
                                mutex_unlock(&fh->cap.lock);
                                return POLLERR;
                        }
        i2c_vidiocschan(btv);
 
        btv->users++;
-       if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type)
-               bttv_vbi_setlines(fh,btv,16);
+
+       /* The V4L2 spec requires one global set of cropping parameters
+          which only change on request. These are stored in btv->crop[1].
+          However for compatibility with V4L apps and cropping unaware
+          V4L2 apps we now reset the cropping parameters as seen through
+          this fh, which is to say VIDIOC_G_CROP and scaling limit checks
+          will use btv->crop[0], the default cropping parameters for the
+          current video standard, and VIDIOC_S_FMT will not implicitely
+          change the cropping parameters until VIDIOC_S_CROP has been
+          called. */
+       fh->do_crop = !reset_crop; /* module parameter */
+
+       /* Likewise there should be one global set of VBI capture
+          parameters, but for compatibility with V4L apps and earlier
+          driver versions each fh has its own parameters. */
+       bttv_vbi_fmt_reset(&fh->vbi_fmt, btv->tvnorm);
+
        bttv_field_count(btv);
        return 0;
 }
                bttv_switch_overlay(btv,fh,NULL);
 
        /* stop video capture */
-       if (check_btres(fh, RESOURCE_VIDEO)) {
+       if (check_btres(fh, RESOURCE_VIDEO_STREAM)) {
                videobuf_streamoff(&fh->cap);
-               free_btres(btv,fh,RESOURCE_VIDEO);
+               free_btres(btv,fh,RESOURCE_VIDEO_STREAM);
        }
        if (fh->cap.read_buf) {
                buffer_release(&fh->cap,fh->cap.read_buf);
                kfree(fh->cap.read_buf);
        }
+       if (check_btres(fh, RESOURCE_VIDEO_READ)) {
+               free_btres(btv, fh, RESOURCE_VIDEO_READ);
+       }
 
        /* stop vbi capture */
        if (check_btres(fh, RESOURCE_VBI)) {
 
        /* initialize structs / fill in defaults */
        mutex_init(&btv->lock);
-       mutex_init(&btv->reslock);
        spin_lock_init(&btv->s_lock);
        spin_lock_init(&btv->gpio_lock);
        init_waitqueue_head(&btv->gpioq);
        btv->init.fmt         = format_by_palette(VIDEO_PALETTE_RGB24);
        btv->init.width       = 320;
        btv->init.height      = 240;
-       btv->init.lines       = 16;
        btv->input = 0;
 
        /* initialize hardware */
                bt848_sat(btv,32768);
                audio_mute(btv, 1);
                set_input(btv,0);
+               bttv_crop_reset(&btv->crop[0], btv->tvnorm);
+               btv->crop[1] = btv->crop[0]; /* current = default */
+               disclaim_vbi_lines(btv);
+               disclaim_video_lines(btv);
        }
 
        /* add subdevices */
 
 bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc,
                 struct scatterlist *sglist,
                 unsigned int offset, unsigned int bpl,
-                unsigned int padding, unsigned int lines)
+                unsigned int padding, unsigned int skip_lines,
+                unsigned int store_lines)
 {
        u32 instructions,line,todo;
        struct scatterlist *sg;
           one write per scan line + sync + jump (all 2 dwords).  padding
           can cause next bpl to start close to a page border.  First DMA
           region may be smaller than PAGE_SIZE */
-       instructions  = 1 + ((bpl + padding) * lines) / PAGE_SIZE + lines;
-       instructions += 2;
-       if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*8)) < 0)
+       instructions  = skip_lines * 4;
+       instructions += (1 + ((bpl + padding) * store_lines)
+                        / PAGE_SIZE + store_lines) * 8;
+       instructions += 2 * 8;
+       if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions)) < 0)
                return rc;
 
        /* sync instruction */
        *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
        *(rp++) = cpu_to_le32(0);
 
+       while (skip_lines-- > 0) {
+               *(rp++) = cpu_to_le32(BT848_RISC_SKIP | BT848_RISC_SOL |
+                                     BT848_RISC_EOL | bpl);
+       }
+
        /* scan lines */
        sg = sglist;
-       for (line = 0; line < lines; line++) {
+       for (line = 0; line < store_lines; line++) {
                if ((btv->opt_vcr_hack) &&
-                   (line >= (lines - VCR_HACK_LINES)))
+                   (line >= (store_lines - VCR_HACK_LINES)))
                        continue;
                while (offset && offset >= sg_dma_len(sg)) {
                        offset -= sg_dma_len(sg);
        /* estimate risc mem: worst case is one write per page border +
           one write per scan line (5 dwords)
           plus sync + jump (2 dwords) */
-       instructions  = (ybpl * ylines * 2) / PAGE_SIZE + ylines;
+       instructions  = ((3 + (ybpl + ypadding) * ylines * 2)
+                        / PAGE_SIZE) + ylines;
        instructions += 2;
        if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*4*5)) < 0)
                return rc;
 /* ---------------------------------------------------------- */
 
 static void
-bttv_calc_geo(struct bttv *btv, struct bttv_geometry *geo,
-             int width, int height, int interleaved, int norm)
+bttv_calc_geo_old(struct bttv *btv, struct bttv_geometry *geo,
+                 int width, int height, int interleaved,
+                 const struct bttv_tvnorm *tvnorm)
 {
-       const struct bttv_tvnorm *tvnorm = &bttv_tvnorms[norm];
        u32 xsf, sr;
        int vdelay;
 
        }
 }
 
+static void
+bttv_calc_geo          (struct bttv *                  btv,
+                        struct bttv_geometry *         geo,
+                        unsigned int                   width,
+                        unsigned int                   height,
+                        int                            both_fields,
+                        const struct bttv_tvnorm *     tvnorm,
+                        const struct v4l2_rect *       crop)
+{
+       unsigned int c_width;
+       unsigned int c_height;
+       u32 sr;
+
+       if ((crop->left == tvnorm->cropcap.defrect.left
+            && crop->top == tvnorm->cropcap.defrect.top
+            && crop->width == tvnorm->cropcap.defrect.width
+            && crop->height == tvnorm->cropcap.defrect.height
+            && width <= tvnorm->swidth /* see PAL-Nc et al */)
+           || bttv_tvcards[btv->c.type].muxsel[btv->input] < 0) {
+               bttv_calc_geo_old(btv, geo, width, height,
+                                 both_fields, tvnorm);
+               return;
+       }
+
+       /* For bug compatibility the image size checks permit scale
+          factors > 16. See bttv_crop_calc_limits(). */
+       c_width = min((unsigned int) crop->width, width * 16);
+       c_height = min((unsigned int) crop->height, height * 16);
+
+       geo->width = width;
+       geo->hscale = (c_width * 4096U + (width >> 1)) / width - 4096;
+       /* Even to store Cb first, odd for Cr. */
+       geo->hdelay = ((crop->left * width + c_width) / c_width) & ~1;
+
+       geo->sheight = c_height;
+       geo->vdelay = crop->top - tvnorm->cropcap.bounds.top + MIN_VDELAY;
+       sr = c_height >> !both_fields;
+       sr = (sr * 512U + (height >> 1)) / height - 512;
+       geo->vscale = (0x10000UL - sr) & 0x1fff;
+       geo->vscale |= both_fields ? (BT848_VSCALE_INT << 8) : 0;
+       geo->vtotal = tvnorm->vtotal;
+
+       geo->crop = (((geo->width   >> 8) & 0x03) |
+                    ((geo->hdelay  >> 6) & 0x0c) |
+                    ((geo->sheight >> 4) & 0x30) |
+                    ((geo->vdelay  >> 2) & 0xc0));
+
+       if (btv->opt_combfilter) {
+               geo->vtc  = (width < 193) ? 2 : ((width < 385) ? 1 : 0);
+               geo->comb = (width < 769) ? 1 : 0;
+       } else {
+               geo->vtc  = 0;
+               geo->comb = 0;
+       }
+}
+
 static void
 bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int odd)
 {
 bttv_buffer_activate_vbi(struct bttv *btv,
                         struct bttv_buffer *vbi)
 {
-       /* vbi capture */
+       struct btcx_riscmem *top;
+       struct btcx_riscmem *bottom;
+       int top_irq_flags;
+       int bottom_irq_flags;
+
+       top = NULL;
+       bottom = NULL;
+       top_irq_flags = 0;
+       bottom_irq_flags = 0;
+
        if (vbi) {
+               unsigned int crop, vdelay;
+
                vbi->vb.state = STATE_ACTIVE;
                list_del(&vbi->vb.queue);
-               bttv_risc_hook(btv, RISC_SLOT_O_VBI, &vbi->top,    0);
-               bttv_risc_hook(btv, RISC_SLOT_E_VBI, &vbi->bottom, 4);
-       } else {
-               bttv_risc_hook(btv, RISC_SLOT_O_VBI, NULL, 0);
-               bttv_risc_hook(btv, RISC_SLOT_E_VBI, NULL, 0);
+
+               /* VDELAY is start of video, end of VBI capturing. */
+               crop = btread(BT848_E_CROP);
+               vdelay = btread(BT848_E_VDELAY_LO) + ((crop & 0xc0) << 2);
+
+               if (vbi->geo.vdelay > vdelay) {
+                       vdelay = vbi->geo.vdelay & 0xfe;
+                       crop = (crop & 0x3f) | ((vbi->geo.vdelay >> 2) & 0xc0);
+
+                       btwrite(vdelay, BT848_E_VDELAY_LO);
+                       btwrite(crop,   BT848_E_CROP);
+                       btwrite(vdelay, BT848_O_VDELAY_LO);
+                       btwrite(crop,   BT848_O_CROP);
+               }
+
+               if (vbi->vbi_count[0] > 0) {
+                       top = &vbi->top;
+                       top_irq_flags = 4;
+               }
+
+               if (vbi->vbi_count[1] > 0) {
+                       top_irq_flags = 0;
+                       bottom = &vbi->bottom;
+                       bottom_irq_flags = 4;
+               }
        }
+
+       bttv_risc_hook(btv, RISC_SLOT_O_VBI, top, top_irq_flags);
+       bttv_risc_hook(btv, RISC_SLOT_E_VBI, bottom, bottom_irq_flags);
+
        return 0;
 }
 
                int bpf = bpl * (buf->vb.height >> 1);
 
                bttv_calc_geo(btv,&buf->geo,buf->vb.width,buf->vb.height,
-                             V4L2_FIELD_HAS_BOTH(buf->vb.field),buf->tvnorm);
+                             V4L2_FIELD_HAS_BOTH(buf->vb.field),
+                             tvnorm,&buf->crop);
 
                switch (buf->vb.field) {
                case V4L2_FIELD_TOP:
                        bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist,
-                                        0,bpl,0,buf->vb.height);
+                                        /* offset */ 0,bpl,
+                                        /* padding */ 0,/* skip_lines */ 0,
+                                        buf->vb.height);
                        break;
                case V4L2_FIELD_BOTTOM:
                        bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist,
-                                        0,bpl,0,buf->vb.height);
+                                        0,bpl,0,0,buf->vb.height);
                        break;
                case V4L2_FIELD_INTERLACED:
                        bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist,
-                                        0,bpl,bpl,buf->vb.height >> 1);
+                                        0,bpl,bpl,0,buf->vb.height >> 1);
                        bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist,
-                                        bpl,bpl,bpl,buf->vb.height >> 1);
+                                        bpl,bpl,bpl,0,buf->vb.height >> 1);
                        break;
                case V4L2_FIELD_SEQ_TB:
                        bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist,
-                                        0,bpl,0,buf->vb.height >> 1);
+                                        0,bpl,0,0,buf->vb.height >> 1);
                        bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist,
-                                        bpf,bpl,0,buf->vb.height >> 1);
+                                        bpf,bpl,0,0,buf->vb.height >> 1);
                        break;
                default:
                        BUG();
                switch (buf->vb.field) {
                case V4L2_FIELD_TOP:
                        bttv_calc_geo(btv,&buf->geo,buf->vb.width,
-                                     buf->vb.height,0,buf->tvnorm);
+                                     buf->vb.height,/* both_fields */ 0,
+                                     tvnorm,&buf->crop);
                        bttv_risc_planar(btv, &buf->top, buf->vb.dma.sglist,
                                         0,buf->vb.width,0,buf->vb.height,
                                         uoffset,voffset,buf->fmt->hshift,
                        break;
                case V4L2_FIELD_BOTTOM:
                        bttv_calc_geo(btv,&buf->geo,buf->vb.width,
-                                     buf->vb.height,0,buf->tvnorm);
+                                     buf->vb.height,0,
+                                     tvnorm,&buf->crop);
                        bttv_risc_planar(btv, &buf->bottom, buf->vb.dma.sglist,
                                         0,buf->vb.width,0,buf->vb.height,
                                         uoffset,voffset,buf->fmt->hshift,
                        break;
                case V4L2_FIELD_INTERLACED:
                        bttv_calc_geo(btv,&buf->geo,buf->vb.width,
-                                     buf->vb.height,1,buf->tvnorm);
+                                     buf->vb.height,1,
+                                     tvnorm,&buf->crop);
                        lines    = buf->vb.height >> 1;
                        ypadding = buf->vb.width;
                        cpadding = buf->vb.width >> buf->fmt->hshift;
                        break;
                case V4L2_FIELD_SEQ_TB:
                        bttv_calc_geo(btv,&buf->geo,buf->vb.width,
-                                     buf->vb.height,1,buf->tvnorm);
+                                     buf->vb.height,1,
+                                     tvnorm,&buf->crop);
                        lines    = buf->vb.height >> 1;
                        ypadding = buf->vb.width;
                        cpadding = buf->vb.width >> buf->fmt->hshift;
                /* build risc code */
                buf->vb.field = V4L2_FIELD_SEQ_TB;
                bttv_calc_geo(btv,&buf->geo,tvnorm->swidth,tvnorm->sheight,
-                             1,buf->tvnorm);
+                             1,tvnorm,&buf->crop);
                bttv_risc_packed(btv, &buf->top,  buf->vb.dma.sglist,
-                                0, RAW_BPL, 0, RAW_LINES);
+                                /* offset */ 0, RAW_BPL, /* padding */ 0,
+                                /* skip_lines */ 0, RAW_LINES);
                bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist,
-                                buf->vb.size/2 , RAW_BPL, 0, RAW_LINES);
+                                buf->vb.size/2 , RAW_BPL, 0, 0, RAW_LINES);
        }
 
        /* copy format info */
 
        /* calculate geometry */
        bttv_calc_geo(btv,&buf->geo,ov->w.width,ov->w.height,
-                     V4L2_FIELD_HAS_BOTH(ov->field), ov->tvnorm);
+                     V4L2_FIELD_HAS_BOTH(ov->field),
+                     &bttv_tvnorms[ov->tvnorm],&buf->crop);
 
        /* build risc code */
        switch (ov->field) {
 
 
     (c) 2002 Gerd Knorr <kraxel@bytesex.org>
 
+    Copyright (C) 2005, 2006 Michael H. Schimek <mschimek@gmx.at>
+    Sponsored by OPQ Systems AB
+
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
    to be about 244.  */
 #define VBI_OFFSET 244
 
+/* 2048 for compatibility with earlier driver versions. The driver
+   really stores 1024 + tvnorm->vbipack * 4 samples per line in the
+   buffer. Note tvnorm->vbipack is <= 0xFF (limit of VBIPACK_LO + HI
+   is 0x1FF DWORDs) and VBI read()s store a frame counter in the last
+   four bytes of the VBI image. */
+#define VBI_BPL 2048
+
+/* Compatibility. */
 #define VBI_DEFLINES 16
-#define VBI_MAXLINES 32
 
 static unsigned int vbibufs = 4;
 static unsigned int vbi_debug = 0;
 #define dprintk(fmt, arg...)   if (vbi_debug) \
        printk(KERN_DEBUG "bttv%d/vbi: " fmt, btv->c.nr , ## arg)
 
+#define IMAGE_SIZE(fmt) \
+       (((fmt)->count[0] + (fmt)->count[1]) * (fmt)->samples_per_line)
+
 /* ----------------------------------------------------------------------- */
 /* vbi risc code + mm                                                      */
 
-static int
-vbi_buffer_risc(struct bttv *btv, struct bttv_buffer *buf, int lines)
-{
-       int bpl = 2048;
-
-       bttv_risc_packed(btv, &buf->top, buf->vb.dma.sglist,
-                        0, bpl-4, 4, lines);
-       bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist,
-                        lines * bpl, bpl-4, 4, lines);
-       return 0;
-}
-
 static int vbi_buffer_setup(struct videobuf_queue *q,
                            unsigned int *count, unsigned int *size)
 {
 
        if (0 == *count)
                *count = vbibufs;
-       *size = fh->lines * 2 * 2048;
-       dprintk("setup: lines=%d\n",fh->lines);
+
+       *size = IMAGE_SIZE(&fh->vbi_fmt.fmt);
+
+       dprintk("setup: samples=%u start=%d,%d count=%u,%u\n",
+               fh->vbi_fmt.fmt.samples_per_line,
+               fh->vbi_fmt.fmt.start[0],
+               fh->vbi_fmt.fmt.start[1],
+               fh->vbi_fmt.fmt.count[0],
+               fh->vbi_fmt.fmt.count[1]);
+
        return 0;
 }
 
        struct bttv_fh *fh = q->priv_data;
        struct bttv *btv = fh->btv;
        struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
+       const struct bttv_tvnorm *tvnorm;
+       unsigned int skip_lines0, skip_lines1, min_vdelay;
+       int redo_dma_risc;
        int rc;
 
-       buf->vb.size = fh->lines * 2 * 2048;
+       buf->vb.size = IMAGE_SIZE(&fh->vbi_fmt.fmt);
        if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
                return -EINVAL;
 
+       tvnorm = fh->vbi_fmt.tvnorm;
+
+       /* There's no VBI_VDELAY register, RISC must skip the lines
+          we don't want. With default parameters we skip zero lines
+          as earlier driver versions did. The driver permits video
+          standard changes while capturing, so we use vbi_fmt.tvnorm
+          instead of btv->tvnorm to skip zero lines after video
+          standard changes as well. */
+
+       skip_lines0 = 0;
+       skip_lines1 = 0;
+
+       if (fh->vbi_fmt.fmt.count[0] > 0)
+               skip_lines0 = max(0, (fh->vbi_fmt.fmt.start[0]
+                                     - tvnorm->vbistart[0]));
+       if (fh->vbi_fmt.fmt.count[1] > 0)
+               skip_lines1 = max(0, (fh->vbi_fmt.fmt.start[1]
+                                     - tvnorm->vbistart[1]));
+
+       redo_dma_risc = 0;
+
+       if (buf->vbi_skip[0] != skip_lines0 ||
+           buf->vbi_skip[1] != skip_lines1 ||
+           buf->vbi_count[0] != fh->vbi_fmt.fmt.count[0] ||
+           buf->vbi_count[1] != fh->vbi_fmt.fmt.count[1]) {
+               buf->vbi_skip[0] = skip_lines0;
+               buf->vbi_skip[1] = skip_lines1;
+               buf->vbi_count[0] = fh->vbi_fmt.fmt.count[0];
+               buf->vbi_count[1] = fh->vbi_fmt.fmt.count[1];
+               redo_dma_risc = 1;
+       }
+
        if (STATE_NEEDS_INIT == buf->vb.state) {
+               redo_dma_risc = 1;
                if (0 != (rc = videobuf_iolock(q, &buf->vb, NULL)))
                        goto fail;
-               if (0 != (rc = vbi_buffer_risc(btv,buf,fh->lines)))
-                       goto fail;
        }
+
+       if (redo_dma_risc) {
+               unsigned int bpl, padding, offset;
+
+               bpl = 2044; /* max. vbipack */
+               padding = VBI_BPL - bpl;
+
+               if (fh->vbi_fmt.fmt.count[0] > 0) {
+                       rc = bttv_risc_packed(btv, &buf->top,
+                                             buf->vb.dma.sglist,
+                                             /* offset */ 0, bpl,
+                                             padding, skip_lines0,
+                                             fh->vbi_fmt.fmt.count[0]);
+                       if (0 != rc)
+                               goto fail;
+               }
+
+               if (fh->vbi_fmt.fmt.count[1] > 0) {
+                       offset = fh->vbi_fmt.fmt.count[0] * VBI_BPL;
+
+                       rc = bttv_risc_packed(btv, &buf->bottom,
+                                             buf->vb.dma.sglist,
+                                             offset, bpl,
+                                             padding, skip_lines1,
+                                             fh->vbi_fmt.fmt.count[1]);
+                       if (0 != rc)
+                               goto fail;
+               }
+       }
+
+       /* VBI capturing ends at VDELAY, start of video capturing,
+          no matter where the RISC program ends. VDELAY minimum is 2,
+          bounds.top is the corresponding first field line number
+          times two. VDELAY counts half field lines. */
+       min_vdelay = MIN_VDELAY;
+       if (fh->vbi_fmt.end >= tvnorm->cropcap.bounds.top)
+               min_vdelay += fh->vbi_fmt.end - tvnorm->cropcap.bounds.top;
+
+       /* For bttv_buffer_activate_vbi(). */
+       buf->geo.vdelay = min_vdelay;
+
        buf->vb.state = STATE_PREPARED;
        buf->vb.field = field;
        dprintk("buf prepare %p: top=%p bottom=%p field=%s\n",
 
 /* ----------------------------------------------------------------------- */
 
-void bttv_vbi_setlines(struct bttv_fh *fh, struct bttv *btv, int lines)
+static int
+try_fmt                        (struct v4l2_vbi_format *       f,
+                        const struct bttv_tvnorm *     tvnorm,
+                        __s32                          crop_start)
 {
-       int vdelay;
-
-       if (lines < 1)
-               lines = 1;
-       if (lines > VBI_MAXLINES)
-               lines = VBI_MAXLINES;
-       fh->lines = lines;
-
-       vdelay = btread(BT848_E_VDELAY_LO);
-       if (vdelay < lines*2) {
-               vdelay = lines*2;
-               btwrite(vdelay,BT848_E_VDELAY_LO);
-               btwrite(vdelay,BT848_O_VDELAY_LO);
+       __s32 min_start, max_start, max_end, f2_offset;
+       unsigned int i;
+
+       /* For compatibility with earlier driver versions we must pretend
+          the VBI and video capture window may overlap. In reality RISC
+          magic aborts VBI capturing at the first line of video capturing,
+          leaving the rest of the buffer unchanged, usually all zero.
+          VBI capturing must always start before video capturing. >> 1
+          because cropping counts field lines times two. */
+       min_start = tvnorm->vbistart[0];
+       max_start = (crop_start >> 1) - 1;
+       max_end = (tvnorm->cropcap.bounds.top
+                  + tvnorm->cropcap.bounds.height) >> 1;
+
+       if (min_start > max_start)
+               return -EBUSY;
+
+       BUG_ON(max_start >= max_end);
+
+       f->sampling_rate    = tvnorm->Fsc;
+       f->samples_per_line = VBI_BPL;
+       f->sample_format    = V4L2_PIX_FMT_GREY;
+       f->offset           = VBI_OFFSET;
+
+       f2_offset = tvnorm->vbistart[1] - tvnorm->vbistart[0];
+
+       for (i = 0; i < 2; ++i) {
+               if (0 == f->count[i]) {
+                       /* No data from this field. We leave f->start[i]
+                          alone because VIDIOCSVBIFMT is w/o and EINVALs
+                          when a driver does not support exactly the
+                          requested parameters. */
+               } else {
+                       s64 start, count;
+
+                       start = clamp(f->start[i], min_start, max_start);
+                       /* s64 to prevent overflow. */
+                       count = (s64) f->start[i] + f->count[i] - start;
+                       f->start[i] = start;
+                       f->count[i] = clamp(count, (s64) 1,
+                                           max_end - start);
+               }
+
+               min_start += f2_offset;
+               max_start += f2_offset;
+               max_end += f2_offset;
        }
+
+       if (0 == (f->count[0] | f->count[1])) {
+               /* As in earlier driver versions. */
+               f->start[0] = tvnorm->vbistart[0];
+               f->start[1] = tvnorm->vbistart[1];
+               f->count[0] = 1;
+               f->count[1] = 1;
+       }
+
+       f->flags = 0;
+
+       f->reserved[0] = 0;
+       f->reserved[1] = 0;
+
+       return 0;
 }
 
-void bttv_vbi_try_fmt(struct bttv_fh *fh, struct v4l2_format *f)
+int
+bttv_vbi_try_fmt       (struct bttv_fh *               fh,
+                        struct v4l2_vbi_format *       f)
 {
+       struct bttv *btv = fh->btv;
        const struct bttv_tvnorm *tvnorm;
-       s64 count0,count1,count;
+       __s32 crop_start;
 
-       tvnorm = &bttv_tvnorms[fh->btv->tvnorm];
-       f->type = V4L2_BUF_TYPE_VBI_CAPTURE;
-       f->fmt.vbi.sampling_rate    = tvnorm->Fsc;
-       f->fmt.vbi.samples_per_line = 2048;
-       f->fmt.vbi.sample_format    = V4L2_PIX_FMT_GREY;
-       f->fmt.vbi.offset           = VBI_OFFSET;
-       f->fmt.vbi.flags            = 0;
-
-       /* s64 to prevent overflow. */
-       count0 = (s64) f->fmt.vbi.start[0] + f->fmt.vbi.count[0]
-               - tvnorm->vbistart[0];
-       count1 = (s64) f->fmt.vbi.start[1] + f->fmt.vbi.count[1]
-               - tvnorm->vbistart[1];
-       count  = clamp (max (count0, count1), (s64) 1, (s64) VBI_MAXLINES);
-
-       f->fmt.vbi.start[0] = tvnorm->vbistart[0];
-       f->fmt.vbi.start[1] = tvnorm->vbistart[1];
-       f->fmt.vbi.count[0] = count;
-       f->fmt.vbi.count[1] = count;
-
-       f->fmt.vbi.reserved[0] = 0;
-       f->fmt.vbi.reserved[1] = 0;
+       mutex_lock(&btv->lock);
+
+       tvnorm = &bttv_tvnorms[btv->tvnorm];
+       crop_start = btv->crop_start;
+
+       mutex_unlock(&btv->lock);
+
+       return try_fmt(f, tvnorm, crop_start);
 }
 
-void bttv_vbi_get_fmt(struct bttv_fh *fh, struct v4l2_format *f)
+int
+bttv_vbi_set_fmt       (struct bttv_fh *               fh,
+                        struct v4l2_vbi_format *       f)
 {
+       struct bttv *btv = fh->btv;
        const struct bttv_tvnorm *tvnorm;
+       __s32 start1, end;
+       int rc;
+
+       mutex_lock(&btv->lock);
+
+       rc = -EBUSY;
+       if (fh->resources & RESOURCE_VBI)
+               goto fail;
+
+       tvnorm = &bttv_tvnorms[btv->tvnorm];
+
+       rc = try_fmt(f, tvnorm, btv->crop_start);
+       if (0 != rc)
+               goto fail;
+
+       start1 = f->start[1] - tvnorm->vbistart[1] + tvnorm->vbistart[0];
+
+       /* First possible line of video capturing. Should be
+          max(f->start[0] + f->count[0], start1 + f->count[1]) * 2
+          when capturing both fields. But for compatibility we must
+          pretend the VBI and video capture window may overlap,
+          so end = start + 1, the lowest possible value, times two
+          because vbi_fmt.end counts field lines times two. */
+       end = max(f->start[0], start1) * 2 + 2;
+
+       mutex_lock(&fh->vbi.lock);
+
+       fh->vbi_fmt.fmt    = *f;
+       fh->vbi_fmt.tvnorm = tvnorm;
+       fh->vbi_fmt.end    = end;
+
+       mutex_unlock(&fh->vbi.lock);
+
+       rc = 0;
+
+ fail:
+       mutex_unlock(&btv->lock);
+
+       return rc;
+}
+
+void
+bttv_vbi_get_fmt       (struct bttv_fh *               fh,
+                        struct v4l2_vbi_format *       f)
+{
+       const struct bttv_tvnorm *tvnorm;
+
+       *f = fh->vbi_fmt.fmt;
 
        tvnorm = &bttv_tvnorms[fh->btv->tvnorm];
-       memset(f,0,sizeof(*f));
-       f->type = V4L2_BUF_TYPE_VBI_CAPTURE;
-       f->fmt.vbi.sampling_rate    = tvnorm->Fsc;
-       f->fmt.vbi.samples_per_line = 2048;
-       f->fmt.vbi.sample_format    = V4L2_PIX_FMT_GREY;
-       f->fmt.vbi.offset           = VBI_OFFSET;
-       f->fmt.vbi.start[0]         = tvnorm->vbistart[0];
-       f->fmt.vbi.start[1]         = tvnorm->vbistart[1];
-       f->fmt.vbi.count[0]         = fh->lines;
-       f->fmt.vbi.count[1]         = fh->lines;
-       f->fmt.vbi.flags            = 0;
+
+       if (tvnorm != fh->vbi_fmt.tvnorm) {
+               __s32 max_end;
+               unsigned int i;
+
+               /* As in vbi_buffer_prepare() this imitates the
+                  behaviour of earlier driver versions after video
+                  standard changes, with default parameters anyway. */
+
+               max_end = (tvnorm->cropcap.bounds.top
+                          + tvnorm->cropcap.bounds.height) >> 1;
+
+               f->sampling_rate = tvnorm->Fsc;
+
+               for (i = 0; i < 2; ++i) {
+                       __s32 new_start;
+
+                       new_start = f->start[i]
+                               + tvnorm->vbistart[i]
+                               - fh->vbi_fmt.tvnorm->vbistart[i];
+
+                       f->start[i] = min(new_start, max_end - 1);
+                       f->count[i] = min((__s32) f->count[i],
+                                         max_end - f->start[i]);
+
+                       max_end += tvnorm->vbistart[1]
+                               - tvnorm->vbistart[0];
+               }
+       }
+}
+
+void
+bttv_vbi_fmt_reset     (struct bttv_vbi_fmt *          f,
+                        int                            norm)
+{
+       const struct bttv_tvnorm *tvnorm;
+       unsigned int real_samples_per_line;
+       unsigned int real_count;
+
+       tvnorm = &bttv_tvnorms[norm];
+
+       f->fmt.sampling_rate    = tvnorm->Fsc;
+       f->fmt.samples_per_line = VBI_BPL;
+       f->fmt.sample_format    = V4L2_PIX_FMT_GREY;
+       f->fmt.offset           = VBI_OFFSET;
+       f->fmt.start[0]         = tvnorm->vbistart[0];
+       f->fmt.start[1]         = tvnorm->vbistart[1];
+       f->fmt.count[0]         = VBI_DEFLINES;
+       f->fmt.count[1]         = VBI_DEFLINES;
+       f->fmt.flags            = 0;
+       f->fmt.reserved[0]      = 0;
+       f->fmt.reserved[1]      = 0;
+
+       /* For compatibility the buffer size must be 2 * VBI_DEFLINES *
+          VBI_BPL regardless of the current video standard. */
+       real_samples_per_line   = 1024 + tvnorm->vbipack * 4;
+       real_count              = ((tvnorm->cropcap.defrect.top >> 1)
+                                  - tvnorm->vbistart[0]);
+
+       BUG_ON(real_samples_per_line > VBI_BPL);
+       BUG_ON(real_count > VBI_DEFLINES);
+
+       f->tvnorm               = tvnorm;
+
+       /* See bttv_vbi_fmt_set(). */
+       f->end                  = tvnorm->vbistart[0] * 2 + 2;
 }
 
 /* ----------------------------------------------------------------------- */