aboutsummaryrefslogtreecommitdiff
path: root/drivers/media/video/sh_mobile_ceu_camera.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/sh_mobile_ceu_camera.c')
-rw-r--r--drivers/media/video/sh_mobile_ceu_camera.c148
1 files changed, 131 insertions, 17 deletions
diff --git a/drivers/media/video/sh_mobile_ceu_camera.c b/drivers/media/video/sh_mobile_ceu_camera.c
index 134e86bf6d9..3ae5c9c58cb 100644
--- a/drivers/media/video/sh_mobile_ceu_camera.c
+++ b/drivers/media/video/sh_mobile_ceu_camera.c
@@ -17,6 +17,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
+#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
@@ -106,6 +107,7 @@ struct sh_mobile_ceu_dev {
struct vb2_alloc_ctx *alloc_ctx;
struct sh_mobile_ceu_info *pdata;
+ struct completion complete;
u32 cflcr;
@@ -114,6 +116,7 @@ struct sh_mobile_ceu_dev {
unsigned int image_mode:1;
unsigned int is_16bit:1;
+ unsigned int frozen:1;
};
struct sh_mobile_ceu_cam {
@@ -273,7 +276,8 @@ static int sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev)
ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_MASK);
status = ceu_read(pcdev, CETCR);
ceu_write(pcdev, CETCR, ~status & CEU_CETCR_MAGIC);
- ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_MASK);
+ if (!pcdev->frozen)
+ ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_MASK);
ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP);
ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW);
@@ -287,6 +291,11 @@ static int sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev)
ret = -EIO;
}
+ if (pcdev->frozen) {
+ complete(&pcdev->complete);
+ return ret;
+ }
+
if (!pcdev->active)
return ret;
@@ -378,12 +387,11 @@ static void sh_mobile_ceu_videobuf_queue(struct vb2_buffer *vb)
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
struct sh_mobile_ceu_dev *pcdev = ici->priv;
struct sh_mobile_ceu_buffer *buf = to_ceu_vb(vb);
- unsigned long flags;
dev_dbg(icd->dev.parent, "%s (vb=0x%p) 0x%p %lu\n", __func__,
vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
- spin_lock_irqsave(&pcdev->lock, flags);
+ spin_lock_irq(&pcdev->lock);
list_add_tail(&buf->queue, &pcdev->capture);
if (!pcdev->active) {
@@ -395,7 +403,7 @@ static void sh_mobile_ceu_videobuf_queue(struct vb2_buffer *vb)
pcdev->active = vb;
sh_mobile_ceu_capture(pcdev);
}
- spin_unlock_irqrestore(&pcdev->lock, flags);
+ spin_unlock_irq(&pcdev->lock);
}
static void sh_mobile_ceu_videobuf_release(struct vb2_buffer *vb)
@@ -404,9 +412,8 @@ static void sh_mobile_ceu_videobuf_release(struct vb2_buffer *vb)
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
struct sh_mobile_ceu_buffer *buf = to_ceu_vb(vb);
struct sh_mobile_ceu_dev *pcdev = ici->priv;
- unsigned long flags;
- spin_lock_irqsave(&pcdev->lock, flags);
+ spin_lock_irq(&pcdev->lock);
if (pcdev->active == vb) {
/* disable capture (release DMA buffer), reset */
@@ -417,7 +424,7 @@ static void sh_mobile_ceu_videobuf_release(struct vb2_buffer *vb)
/* Doesn't hurt also if the list is empty */
list_del_init(&buf->queue);
- spin_unlock_irqrestore(&pcdev->lock, flags);
+ spin_unlock_irq(&pcdev->lock);
}
static int sh_mobile_ceu_videobuf_init(struct vb2_buffer *vb)
@@ -427,6 +434,25 @@ static int sh_mobile_ceu_videobuf_init(struct vb2_buffer *vb)
return 0;
}
+static int sh_mobile_ceu_stop_streaming(struct vb2_queue *q)
+{
+ struct soc_camera_device *icd = container_of(q, struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+ struct sh_mobile_ceu_dev *pcdev = ici->priv;
+ struct list_head *buf_head, *tmp;
+
+ spin_lock_irq(&pcdev->lock);
+
+ pcdev->active = NULL;
+
+ list_for_each_safe(buf_head, tmp, &pcdev->capture)
+ list_del_init(buf_head);
+
+ spin_unlock_irq(&pcdev->lock);
+
+ return sh_mobile_ceu_soft_reset(pcdev);
+}
+
static struct vb2_ops sh_mobile_ceu_videobuf_ops = {
.queue_setup = sh_mobile_ceu_videobuf_setup,
.buf_prepare = sh_mobile_ceu_videobuf_prepare,
@@ -435,6 +461,7 @@ static struct vb2_ops sh_mobile_ceu_videobuf_ops = {
.buf_init = sh_mobile_ceu_videobuf_init,
.wait_prepare = soc_camera_unlock,
.wait_finish = soc_camera_lock,
+ .stop_streaming = sh_mobile_ceu_stop_streaming,
};
static irqreturn_t sh_mobile_ceu_irq(int irq, void *data)
@@ -500,7 +527,6 @@ static void sh_mobile_ceu_remove_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;
- unsigned long flags;
BUG_ON(icd != pcdev->icd);
@@ -509,13 +535,13 @@ static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd)
sh_mobile_ceu_soft_reset(pcdev);
/* make sure active buffer is canceled */
- spin_lock_irqsave(&pcdev->lock, flags);
+ spin_lock_irq(&pcdev->lock);
if (pcdev->active) {
list_del_init(&to_ceu_vb(pcdev->active)->queue);
vb2_buffer_done(pcdev->active, VB2_BUF_STATE_ERROR);
pcdev->active = NULL;
}
- spin_unlock_irqrestore(&pcdev->lock, flags);
+ spin_unlock_irq(&pcdev->lock);
pm_runtime_put_sync(ici->v4l2_dev.dev);
@@ -891,8 +917,8 @@ static int sh_mobile_ceu_get_formats(struct soc_camera_device *icd, unsigned int
fmt = soc_mbus_get_fmtdesc(code);
if (!fmt) {
- dev_err(dev, "Invalid format code #%u: %d\n", idx, code);
- return -EINVAL;
+ dev_warn(dev, "unsupported format code #%u: %d\n", idx, code);
+ return 0;
}
if (!pcdev->pdata->csi2_dev) {
@@ -1330,7 +1356,7 @@ static int client_scale(struct soc_camera_device *icd,
/*
* CEU can scale and crop, but we don't want to waste bandwidth and kill the
* framerate by always requesting the maximum image from the client. See
- * Documentation/video4linux/sh_mobile_camera_ceu.txt for a description of
+ * Documentation/video4linux/sh_mobile_ceu_camera.txt for a description of
* scaling and cropping algorithms and for the meaning of referenced here steps.
*/
static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd,
@@ -1377,10 +1403,6 @@ static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd,
if (mf.width > 2560 || mf.height > 1920)
return -EINVAL;
- /* Cache camera output window */
- cam->width = mf.width;
- cam->height = mf.height;
-
/* 4. Calculate camera scales */
scale_cam_h = calc_generic_scale(cam_rect->width, mf.width);
scale_cam_v = calc_generic_scale(cam_rect->height, mf.height);
@@ -1389,6 +1411,39 @@ static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd,
interm_width = scale_down(rect->width, scale_cam_h);
interm_height = scale_down(rect->height, scale_cam_v);
+ if (interm_width < icd->user_width) {
+ u32 new_scale_h;
+
+ new_scale_h = calc_generic_scale(rect->width, icd->user_width);
+
+ mf.width = scale_down(cam_rect->width, new_scale_h);
+ }
+
+ if (interm_height < icd->user_height) {
+ u32 new_scale_v;
+
+ new_scale_v = calc_generic_scale(rect->height, icd->user_height);
+
+ mf.height = scale_down(cam_rect->height, new_scale_v);
+ }
+
+ if (interm_width < icd->user_width || interm_height < icd->user_height) {
+ ret = v4l2_device_call_until_err(sd->v4l2_dev, (int)icd, video,
+ s_mbus_fmt, &mf);
+ if (ret < 0)
+ return ret;
+
+ dev_geo(dev, "New camera output %ux%u\n", mf.width, mf.height);
+ scale_cam_h = calc_generic_scale(cam_rect->width, mf.width);
+ scale_cam_v = calc_generic_scale(cam_rect->height, mf.height);
+ interm_width = scale_down(rect->width, scale_cam_h);
+ interm_height = scale_down(rect->height, scale_cam_v);
+ }
+
+ /* Cache camera output window */
+ cam->width = mf.width;
+ cam->height = mf.height;
+
if (pcdev->image_mode) {
out_width = min(interm_width, icd->user_width);
out_height = min(interm_height, icd->user_height);
@@ -1704,6 +1759,63 @@ static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd,
return ret;
}
+static int sh_mobile_ceu_set_livecrop(struct soc_camera_device *icd,
+ struct v4l2_crop *a)
+{
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+ struct sh_mobile_ceu_dev *pcdev = ici->priv;
+ u32 out_width = icd->user_width, out_height = icd->user_height;
+ int ret;
+
+ /* Freeze queue */
+ pcdev->frozen = 1;
+ /* Wait for frame */
+ ret = wait_for_completion_interruptible(&pcdev->complete);
+ /* Stop the client */
+ ret = v4l2_subdev_call(sd, video, s_stream, 0);
+ if (ret < 0)
+ dev_warn(icd->dev.parent,
+ "Client failed to stop the stream: %d\n", ret);
+ else
+ /* Do the crop, if it fails, there's nothing more we can do */
+ sh_mobile_ceu_set_crop(icd, a);
+
+ dev_geo(icd->dev.parent, "Output after crop: %ux%u\n", icd->user_width, icd->user_height);
+
+ if (icd->user_width != out_width || icd->user_height != out_height) {
+ struct v4l2_format f = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .fmt.pix = {
+ .width = out_width,
+ .height = out_height,
+ .pixelformat = icd->current_fmt->host_fmt->fourcc,
+ .field = pcdev->field,
+ .colorspace = icd->colorspace,
+ },
+ };
+ ret = sh_mobile_ceu_set_fmt(icd, &f);
+ if (!ret && (out_width != f.fmt.pix.width ||
+ out_height != f.fmt.pix.height))
+ ret = -EINVAL;
+ if (!ret) {
+ icd->user_width = out_width;
+ icd->user_height = out_height;
+ ret = sh_mobile_ceu_set_bus_param(icd,
+ icd->current_fmt->host_fmt->fourcc);
+ }
+ }
+
+ /* Thaw the queue */
+ pcdev->frozen = 0;
+ spin_lock_irq(&pcdev->lock);
+ sh_mobile_ceu_capture(pcdev);
+ spin_unlock_irq(&pcdev->lock);
+ /* Start the client */
+ ret = v4l2_subdev_call(sd, video, s_stream, 1);
+ return ret;
+}
+
static unsigned int sh_mobile_ceu_poll(struct file *file, poll_table *pt)
{
struct soc_camera_device *icd = file->private_data;
@@ -1790,6 +1902,7 @@ static struct soc_camera_host_ops sh_mobile_ceu_host_ops = {
.put_formats = sh_mobile_ceu_put_formats,
.get_crop = sh_mobile_ceu_get_crop,
.set_crop = sh_mobile_ceu_set_crop,
+ .set_livecrop = sh_mobile_ceu_set_livecrop,
.set_fmt = sh_mobile_ceu_set_fmt,
.try_fmt = sh_mobile_ceu_try_fmt,
.set_ctrl = sh_mobile_ceu_set_ctrl,
@@ -1856,6 +1969,7 @@ static int __devinit sh_mobile_ceu_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&pcdev->capture);
spin_lock_init(&pcdev->lock);
+ init_completion(&pcdev->complete);
pcdev->pdata = pdev->dev.platform_data;
if (!pcdev->pdata) {