]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/media/video/sh_mobile_ceu_camera.c
Merge branch 'devel' of git://git.kernel.org/pub/scm/linux/kernel/git/ycmiao/pxa...
[linux-2.6-omap-h63xx.git] / drivers / media / video / sh_mobile_ceu_camera.c
index 536b1a9b310ca602aa256493e742290c915f5674..ddcb81d0b81a36df1ebebe0b6663c0eed1887040 100644 (file)
@@ -29,7 +29,6 @@
 #include <linux/version.h>
 #include <linux/device.h>
 #include <linux/platform_device.h>
-#include <linux/mutex.h>
 #include <linux/videodev2.h>
 #include <linux/clk.h>
 
@@ -75,8 +74,6 @@
 #define CDBYR2 0x98 /* Capture data bottom-field address Y register 2 */
 #define CDBCR2 0x9c /* Capture data bottom-field address C register 2 */
 
-static DEFINE_MUTEX(camera_lock);
-
 /* per video frame buffer */
 struct sh_mobile_ceu_buffer {
        struct videobuf_buffer vb; /* v4l buffer must be first */
@@ -97,18 +94,20 @@ struct sh_mobile_ceu_dev {
        spinlock_t lock;
        struct list_head capture;
        struct videobuf_buffer *active;
+       int is_interlace;
 
        struct sh_mobile_ceu_info *pdata;
+
+       const struct soc_camera_data_format *camera_fmt;
 };
 
 static void ceu_write(struct sh_mobile_ceu_dev *priv,
-                     unsigned long reg_offs, unsigned long data)
+                     unsigned long reg_offs, u32 data)
 {
        iowrite32(data, priv->base + reg_offs);
 }
 
-static unsigned long ceu_read(struct sh_mobile_ceu_dev *priv,
-                             unsigned long reg_offs)
+static u32 ceu_read(struct sh_mobile_ceu_dev *priv, unsigned long reg_offs)
 {
        return ioread32(priv->base + reg_offs);
 }
@@ -156,21 +155,52 @@ static void free_buffer(struct videobuf_queue *vq,
        buf->vb.state = VIDEOBUF_NEEDS_INIT;
 }
 
+#define CEU_CETCR_MAGIC 0x0317f313 /* acknowledge magical interrupt sources */
+#define CEU_CETCR_IGRW (1 << 4) /* prohibited register access interrupt bit */
+#define CEU_CEIER_CPEIE (1 << 0) /* one-frame capture end interrupt */
+#define CEU_CAPCR_CTNCP (1 << 16) /* continuous capture mode (if set) */
+
+
 static void sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev)
 {
-       ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~1);
-       ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & 0x0317f313);
-       ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | 1);
-
-       ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~0x10000);
+       struct soc_camera_device *icd = pcdev->icd;
+       dma_addr_t phys_addr_top, phys_addr_bottom;
 
-       ceu_write(pcdev, CETCR, 0x0317f313 ^ 0x10);
+       /* The hardware is _very_ picky about this sequence. Especially
+        * the CEU_CETCR_MAGIC value. It seems like we need to acknowledge
+        * several not-so-well documented interrupt sources in CETCR.
+        */
+       ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_CPEIE);
+       ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & CEU_CETCR_MAGIC);
+       ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_CPEIE);
+       ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP);
+       ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW);
+
+       if (!pcdev->active)
+               return;
+
+       phys_addr_top = videobuf_to_dma_contig(pcdev->active);
+       ceu_write(pcdev, CDAYR, phys_addr_top);
+       if (pcdev->is_interlace) {
+               phys_addr_bottom = phys_addr_top + icd->width;
+               ceu_write(pcdev, CDBYR, phys_addr_bottom);
+       }
 
-       if (pcdev->active) {
-               pcdev->active->state = VIDEOBUF_ACTIVE;
-               ceu_write(pcdev, CDAYR, videobuf_to_dma_contig(pcdev->active));
-               ceu_write(pcdev, CAPSR, 0x1); /* start capture */
+       switch (icd->current_fmt->fourcc) {
+       case V4L2_PIX_FMT_NV12:
+       case V4L2_PIX_FMT_NV21:
+       case V4L2_PIX_FMT_NV16:
+       case V4L2_PIX_FMT_NV61:
+               phys_addr_top += icd->width * icd->height;
+               ceu_write(pcdev, CDACR, phys_addr_top);
+               if (pcdev->is_interlace) {
+                       phys_addr_bottom = phys_addr_top + icd->width;
+                       ceu_write(pcdev, CDBCR, phys_addr_bottom);
+               }
        }
+
+       pcdev->active->state = VIDEOBUF_ACTIVE;
+       ceu_write(pcdev, CAPSR, 0x1); /* start capture */
 }
 
 static int sh_mobile_ceu_videobuf_prepare(struct videobuf_queue *vq,
@@ -292,14 +322,13 @@ static irqreturn_t sh_mobile_ceu_irq(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+/* Called with .video_lock held */
 static int sh_mobile_ceu_add_device(struct soc_camera_device *icd)
 {
        struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
        struct sh_mobile_ceu_dev *pcdev = ici->priv;
        int ret = -EBUSY;
 
-       mutex_lock(&camera_lock);
-
        if (pcdev->icd)
                goto err;
 
@@ -319,11 +348,10 @@ static int sh_mobile_ceu_add_device(struct soc_camera_device *icd)
 
        pcdev->icd = icd;
 err:
-       mutex_unlock(&camera_lock);
-
        return ret;
 }
 
+/* Called with .video_lock held */
 static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd)
 {
        struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
@@ -362,8 +390,9 @@ static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd,
 {
        struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
        struct sh_mobile_ceu_dev *pcdev = ici->priv;
-       int ret, buswidth, width, cfszr_width, cdwdr_width;
+       int ret, buswidth, width, height, cfszr_width, cdwdr_width;
        unsigned long camera_flags, common_flags, value;
+       int yuv_mode, yuv_lineskip;
 
        camera_flags = icd->ops->query_bus_param(icd);
        common_flags = soc_camera_bus_param_compatible(camera_flags,
@@ -389,27 +418,71 @@ static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd,
        ceu_write(pcdev, CRCNTR, 0);
        ceu_write(pcdev, CRCMPR, 0);
 
-       value = 0x00000010;
-       value |= (common_flags & SOCAM_VSYNC_ACTIVE_LOW) ? (1 << 1) : 0;
-       value |= (common_flags & SOCAM_HSYNC_ACTIVE_LOW) ? (1 << 0) : 0;
-       value |= (buswidth == 16) ? (1 << 12) : 0;
+       value = 0x00000010; /* data fetch by default */
+       yuv_mode = yuv_lineskip = 0;
+
+       switch (icd->current_fmt->fourcc) {
+       case V4L2_PIX_FMT_NV12:
+       case V4L2_PIX_FMT_NV21:
+               yuv_lineskip = 1; /* skip for NV12/21, no skip for NV16/61 */
+               /* fall-through */
+       case V4L2_PIX_FMT_NV16:
+       case V4L2_PIX_FMT_NV61:
+               yuv_mode = 1;
+               switch (pcdev->camera_fmt->fourcc) {
+               case V4L2_PIX_FMT_UYVY:
+                       value = 0x00000000; /* Cb0, Y0, Cr0, Y1 */
+                       break;
+               case V4L2_PIX_FMT_VYUY:
+                       value = 0x00000100; /* Cr0, Y0, Cb0, Y1 */
+                       break;
+               case V4L2_PIX_FMT_YUYV:
+                       value = 0x00000200; /* Y0, Cb0, Y1, Cr0 */
+                       break;
+               case V4L2_PIX_FMT_YVYU:
+                       value = 0x00000300; /* Y0, Cr0, Y1, Cb0 */
+                       break;
+               default:
+                       BUG();
+               }
+       }
+
+       if (icd->current_fmt->fourcc == V4L2_PIX_FMT_NV21 ||
+           icd->current_fmt->fourcc == V4L2_PIX_FMT_NV61)
+               value ^= 0x00000100; /* swap U, V to change from NV1x->NVx1 */
+
+       value |= common_flags & SOCAM_VSYNC_ACTIVE_LOW ? 1 << 1 : 0;
+       value |= common_flags & SOCAM_HSYNC_ACTIVE_LOW ? 1 << 0 : 0;
+       value |= buswidth == 16 ? 1 << 12 : 0;
        ceu_write(pcdev, CAMCR, value);
 
        ceu_write(pcdev, CAPCR, 0x00300000);
-       ceu_write(pcdev, CAIFR, 0);
+       ceu_write(pcdev, CAIFR, (pcdev->is_interlace) ? 0x101 : 0);
 
        mdelay(1);
 
-       width = icd->width * (icd->current_fmt->depth / 8);
-       width = (buswidth == 16) ? width / 2 : width;
-       cfszr_width = (buswidth == 8) ? width / 2 : width;
-       cdwdr_width = (buswidth == 16) ? width * 2 : width;
+       if (yuv_mode) {
+               width = icd->width * 2;
+               width = buswidth == 16 ? width / 2 : width;
+               cfszr_width = cdwdr_width = icd->width;
+       } else {
+               width = icd->width * ((icd->current_fmt->depth + 7) >> 3);
+               width = buswidth == 16 ? width / 2 : width;
+               cfszr_width = buswidth == 8 ? width / 2 : width;
+               cdwdr_width = buswidth == 16 ? width * 2 : width;
+       }
+
+       height = icd->height;
+       if (pcdev->is_interlace) {
+               height /= 2;
+               cdwdr_width *= 2;
+       }
 
        ceu_write(pcdev, CAMOR, 0);
-       ceu_write(pcdev, CAPWR, (icd->height << 16) | width);
-       ceu_write(pcdev, CFLCR, 0); /* data fetch mode - no scaling */
-       ceu_write(pcdev, CFSZR, (icd->height << 16) | cfszr_width);
-       ceu_write(pcdev, CLFCR, 0); /* data fetch mode - no lowpass filter */
+       ceu_write(pcdev, CAPWR, (height << 16) | width);
+       ceu_write(pcdev, CFLCR, 0); /* no scaling */
+       ceu_write(pcdev, CFSZR, (height << 16) | cfszr_width);
+       ceu_write(pcdev, CLFCR, 0); /* no lowpass filter */
 
        /* A few words about byte order (observed in Big Endian mode)
         *
@@ -423,19 +496,20 @@ static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd,
         * using 7 we swap the data bytes to match the incoming order:
         * D0, D1, D2, D3, D4, D5, D6, D7
         */
-       ceu_write(pcdev, CDOCR, 0x00000017);
+       value = 0x00000017;
+       if (yuv_lineskip)
+               value &= ~0x00000010; /* convert 4:2:2 -> 4:2:0 */
+
+       ceu_write(pcdev, CDOCR, value);
 
        ceu_write(pcdev, CDWDR, cdwdr_width);
        ceu_write(pcdev, CFWCR, 0); /* keep "datafetch firewall" disabled */
 
        /* not in bundle mode: skip CBDSR, CDAYR2, CDACR2, CDBYR2, CDBCR2 */
-       /* in data fetch mode: no need for CDACR, CDBYR, CDBCR */
-
        return 0;
 }
 
-static int sh_mobile_ceu_try_bus_param(struct soc_camera_device *icd,
-                                      __u32 pixfmt)
+static int sh_mobile_ceu_try_bus_param(struct soc_camera_device *icd)
 {
        struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
        struct sh_mobile_ceu_dev *pcdev = ici->priv;
@@ -450,15 +524,120 @@ static int sh_mobile_ceu_try_bus_param(struct soc_camera_device *icd,
        return 0;
 }
 
-static int sh_mobile_ceu_set_fmt_cap(struct soc_camera_device *icd,
-                                    __u32 pixfmt, struct v4l2_rect *rect)
+static const struct soc_camera_data_format sh_mobile_ceu_formats[] = {
+       {
+               .name           = "NV12",
+               .depth          = 12,
+               .fourcc         = V4L2_PIX_FMT_NV12,
+               .colorspace     = V4L2_COLORSPACE_JPEG,
+       },
+       {
+               .name           = "NV21",
+               .depth          = 12,
+               .fourcc         = V4L2_PIX_FMT_NV21,
+               .colorspace     = V4L2_COLORSPACE_JPEG,
+       },
+       {
+               .name           = "NV16",
+               .depth          = 16,
+               .fourcc         = V4L2_PIX_FMT_NV16,
+               .colorspace     = V4L2_COLORSPACE_JPEG,
+       },
+       {
+               .name           = "NV61",
+               .depth          = 16,
+               .fourcc         = V4L2_PIX_FMT_NV61,
+               .colorspace     = V4L2_COLORSPACE_JPEG,
+       },
+};
+
+static int sh_mobile_ceu_get_formats(struct soc_camera_device *icd, int idx,
+                                    struct soc_camera_format_xlate *xlate)
+{
+       struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+       int ret, k, n;
+       int formats = 0;
+
+       ret = sh_mobile_ceu_try_bus_param(icd);
+       if (ret < 0)
+               return 0;
+
+       switch (icd->formats[idx].fourcc) {
+       case V4L2_PIX_FMT_UYVY:
+       case V4L2_PIX_FMT_VYUY:
+       case V4L2_PIX_FMT_YUYV:
+       case V4L2_PIX_FMT_YVYU:
+               n = ARRAY_SIZE(sh_mobile_ceu_formats);
+               formats += n;
+               for (k = 0; xlate && k < n; k++) {
+                       xlate->host_fmt = &sh_mobile_ceu_formats[k];
+                       xlate->cam_fmt = icd->formats + idx;
+                       xlate->buswidth = icd->formats[idx].depth;
+                       xlate++;
+                       dev_dbg(&ici->dev, "Providing format %s using %s\n",
+                               sh_mobile_ceu_formats[k].name,
+                               icd->formats[idx].name);
+               }
+       default:
+               /* Generic pass-through */
+               formats++;
+               if (xlate) {
+                       xlate->host_fmt = icd->formats + idx;
+                       xlate->cam_fmt = icd->formats + idx;
+                       xlate->buswidth = icd->formats[idx].depth;
+                       xlate++;
+                       dev_dbg(&ici->dev,
+                               "Providing format %s in pass-through mode\n",
+                               icd->formats[idx].name);
+               }
+       }
+
+       return formats;
+}
+
+static int sh_mobile_ceu_set_fmt(struct soc_camera_device *icd,
+                                __u32 pixfmt, struct v4l2_rect *rect)
 {
-       return icd->ops->set_fmt_cap(icd, pixfmt, rect);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+       struct sh_mobile_ceu_dev *pcdev = ici->priv;
+       const struct soc_camera_format_xlate *xlate;
+       int ret;
+
+       if (!pixfmt)
+               return icd->ops->set_fmt(icd, pixfmt, rect);
+
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (!xlate) {
+               dev_warn(&ici->dev, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+
+       ret = icd->ops->set_fmt(icd, xlate->cam_fmt->fourcc, rect);
+
+       if (!ret) {
+               icd->buswidth = xlate->buswidth;
+               icd->current_fmt = xlate->host_fmt;
+               pcdev->camera_fmt = xlate->cam_fmt;
+       }
+
+       return ret;
 }
 
-static int sh_mobile_ceu_try_fmt_cap(struct soc_camera_device *icd,
-                                    struct v4l2_format *f)
+static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd,
+                                struct v4l2_format *f)
 {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+       struct sh_mobile_ceu_dev *pcdev = ici->priv;
+       const struct soc_camera_format_xlate *xlate;
+       __u32 pixfmt = f->fmt.pix.pixelformat;
+       int ret;
+
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (!xlate) {
+               dev_warn(&ici->dev, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+
        /* FIXME: calculate using depth and bus width */
 
        if (f->fmt.pix.height < 4)
@@ -472,8 +651,31 @@ static int sh_mobile_ceu_try_fmt_cap(struct soc_camera_device *icd,
        f->fmt.pix.width &= ~0x01;
        f->fmt.pix.height &= ~0x03;
 
+       f->fmt.pix.bytesperline = f->fmt.pix.width *
+               DIV_ROUND_UP(xlate->host_fmt->depth, 8);
+       f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+
        /* limit to sensor capabilities */
-       return icd->ops->try_fmt_cap(icd, f);
+       ret = icd->ops->try_fmt(icd, f);
+       if (ret < 0)
+               return ret;
+
+       switch (f->fmt.pix.field) {
+       case V4L2_FIELD_INTERLACED:
+               pcdev->is_interlace = 1;
+               break;
+       case V4L2_FIELD_ANY:
+               f->fmt.pix.field = V4L2_FIELD_NONE;
+               /* fall-through */
+       case V4L2_FIELD_NONE:
+               pcdev->is_interlace = 0;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
 }
 
 static int sh_mobile_ceu_reqbufs(struct soc_camera_file *icf,
@@ -532,7 +734,7 @@ static void sh_mobile_ceu_init_videobuf(struct videobuf_queue *q,
                                       &sh_mobile_ceu_videobuf_ops,
                                       &ici->dev, &pcdev->lock,
                                       V4L2_BUF_TYPE_VIDEO_CAPTURE,
-                                      V4L2_FIELD_NONE,
+                                      V4L2_FIELD_ANY,
                                       sizeof(struct sh_mobile_ceu_buffer),
                                       icd);
 }
@@ -541,12 +743,12 @@ static struct soc_camera_host_ops sh_mobile_ceu_host_ops = {
        .owner          = THIS_MODULE,
        .add            = sh_mobile_ceu_add_device,
        .remove         = sh_mobile_ceu_remove_device,
-       .set_fmt_cap    = sh_mobile_ceu_set_fmt_cap,
-       .try_fmt_cap    = sh_mobile_ceu_try_fmt_cap,
+       .get_formats    = sh_mobile_ceu_get_formats,
+       .set_fmt        = sh_mobile_ceu_set_fmt,
+       .try_fmt        = sh_mobile_ceu_try_fmt,
        .reqbufs        = sh_mobile_ceu_reqbufs,
        .poll           = sh_mobile_ceu_poll,
        .querycap       = sh_mobile_ceu_querycap,
-       .try_bus_param  = sh_mobile_ceu_try_bus_param,
        .set_bus_param  = sh_mobile_ceu_set_bus_param,
        .init_videobuf  = sh_mobile_ceu_init_videobuf,
 };
@@ -616,7 +818,7 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev)
 
        /* request irq */
        err = request_irq(pcdev->irq, sh_mobile_ceu_irq, IRQF_DISABLED,
-                         pdev->dev.bus_id, pcdev);
+                         dev_name(&pdev->dev), pcdev);
        if (err) {
                dev_err(&pdev->dev, "Unable to register CEU interrupt.\n");
                goto exit_release_mem;
@@ -633,8 +835,8 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev)
        pcdev->ici.priv = pcdev;
        pcdev->ici.dev.parent = &pdev->dev;
        pcdev->ici.nr = pdev->id;
-       pcdev->ici.drv_name = pdev->dev.bus_id,
-       pcdev->ici.ops = &sh_mobile_ceu_host_ops,
+       pcdev->ici.drv_name = dev_name(&pdev->dev);
+       pcdev->ici.ops = &sh_mobile_ceu_host_ops;
 
        err = soc_camera_host_register(&pcdev->ici);
        if (err)