blob: c72695826316caaf75537ed02fe153d0e8538fbb [file] [log] [blame]
/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/of.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/ioctl.h>
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <linux/atomic.h>
#include <linux/wait.h>
#include <linux/videodev2.h>
#include <linux/msm_ion.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include <media/v4l2-fh.h>
#include "camera.h"
#include "msm.h"
#include "msm_vb2.h"
#define fh_to_private(__fh) \
container_of(__fh, struct camera_v4l2_private, fh)
struct camera_v4l2_private {
struct v4l2_fh fh;
unsigned int stream_id;
struct vb2_queue vb2_q;
};
static void camera_pack_event(struct file *filep, int evt_id,
int command, struct v4l2_event *event)
{
struct msm_v4l2_event_data *event_data =
(struct msm_v4l2_event_data *)&event->u.data[0];
struct msm_video_device *pvdev = video_drvdata(filep);
struct camera_v4l2_private *sp = fh_to_private(filep->private_data);
/* always MSM_CAMERA_V4L2_EVENT_TYPE */
event->type = MSM_CAMERA_V4L2_EVENT_TYPE;
event->id = evt_id;
event_data->command = command;
event_data->session_id = pvdev->vdev->num;
event_data->stream_id = sp->stream_id;
}
static int camera_check_event_status(struct v4l2_event *event)
{
struct msm_v4l2_event_data *event_data =
(struct msm_v4l2_event_data *)&event->u.data[0];
if (event_data->status > MSM_CAMERA_ERR_EVT_BASE)
return -EFAULT;
return 0;
}
static int camera_v4l2_querycap(struct file *filep, void *fh,
struct v4l2_capability *cap)
{
int rc;
struct v4l2_event event;
/* can use cap->driver to make differentiation */
camera_pack_event(filep, MSM_CAMERA_GET_PARM,
MSM_CAMERA_PRIV_QUERY_CAP, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
return rc;
}
static int camera_v4l2_s_crop(struct file *filep, void *fh,
struct v4l2_crop *crop)
{
int rc = 0;
struct v4l2_event event;
if (crop->type == V4L2_BUF_TYPE_PRIVATE) {
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_S_CROP, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_g_crop(struct file *filep, void *fh,
struct v4l2_crop *crop)
{
int rc = 0;
struct v4l2_event event;
if (crop->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
camera_pack_event(filep, MSM_CAMERA_GET_PARM,
MSM_CAMERA_PRIV_G_CROP, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_queryctrl(struct file *filep, void *fh,
struct v4l2_queryctrl *ctrl)
{
int rc = 0;
struct v4l2_event event;
if (ctrl->type == V4L2_CTRL_TYPE_MENU) {
camera_pack_event(filep, MSM_CAMERA_GET_PARM,
ctrl->id, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_g_ctrl(struct file *filep, void *fh,
struct v4l2_control *ctrl)
{
int rc = 0;
struct v4l2_event event;
if (ctrl->id >= V4L2_CID_PRIVATE_BASE) {
camera_pack_event(filep, MSM_CAMERA_GET_PARM, ctrl->id, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_s_ctrl(struct file *filep, void *fh,
struct v4l2_control *ctrl)
{
int rc = 0;
struct v4l2_event event;
if (ctrl->id >= V4L2_CID_PRIVATE_BASE) {
camera_pack_event(filep, MSM_CAMERA_SET_PARM, ctrl->id, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_reqbufs(struct file *filep, void *fh,
struct v4l2_requestbuffers *req)
{
struct camera_v4l2_private *sp = fh_to_private(fh);
return vb2_reqbufs(&sp->vb2_q, req);
}
static int camera_v4l2_querybuf(struct file *filep, void *fh,
struct v4l2_buffer *pb)
{
return 0;
}
static int camera_v4l2_qbuf(struct file *filep, void *fh,
struct v4l2_buffer *pb)
{
struct camera_v4l2_private *sp = fh_to_private(fh);
return vb2_qbuf(&sp->vb2_q, pb);
}
static int camera_v4l2_dqbuf(struct file *filep, void *fh,
struct v4l2_buffer *pb)
{
struct camera_v4l2_private *sp = fh_to_private(fh);
return vb2_dqbuf(&sp->vb2_q, pb, filep->f_flags & O_NONBLOCK);
}
static int camera_v4l2_streamon(struct file *filep, void *fh,
enum v4l2_buf_type buf_type)
{
struct v4l2_event event;
int rc;
struct camera_v4l2_private *sp = fh_to_private(fh);
rc = vb2_streamon(&sp->vb2_q, buf_type);
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_STREAM_ON, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
return rc;
}
static int camera_v4l2_streamoff(struct file *filep, void *fh,
enum v4l2_buf_type buf_type)
{
struct v4l2_event event;
int rc;
struct camera_v4l2_private *sp = fh_to_private(fh);
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_STREAM_OFF, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
vb2_streamoff(&sp->vb2_q, buf_type);
return rc;
}
static int camera_v4l2_g_fmt_cap_private(struct file *filep, void *fh,
struct v4l2_format *pfmt)
{
int rc = -EINVAL;
if (pfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
struct v4l2_event event;
camera_pack_event(filep, MSM_CAMERA_GET_PARM,
MSM_CAMERA_PRIV_G_FMT, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
return rc;
rc = camera_check_event_status(&event);
}
return rc;
}
static int camera_v4l2_s_fmt_cap_private(struct file *filep, void *fh,
struct v4l2_format *pfmt)
{
int rc = 0;
int i = 0;
struct v4l2_event event;
struct camera_v4l2_private *sp = fh_to_private(fh);
struct msm_v4l2_format_data *user_fmt;
if (pfmt->type == V4L2_BUF_TYPE_PRIVATE) {
if (WARN_ON(!sp->vb2_q.drv_priv))
return -ENOMEM;
memcpy(sp->vb2_q.drv_priv, pfmt->fmt.raw_data,
sizeof(struct msm_v4l2_format_data));
user_fmt = (struct msm_v4l2_format_data *)sp->vb2_q.drv_priv;
pr_debug("%s: num planes :%c\n", __func__,
user_fmt->num_planes);
for (i = 0; i < user_fmt->num_planes; i++)
pr_debug("%s: plane size[%d]\n", __func__,
user_fmt->plane_sizes[i]);
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_S_FMT, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
goto set_fmt_fail;
rc = camera_check_event_status(&event);
if (rc < 0)
goto set_fmt_fail;
}
return rc;
set_fmt_fail:
kfree(sp->vb2_q.drv_priv);
return rc;
}
static int camera_v4l2_try_fmt_cap_private(struct file *filep, void *fh,
struct v4l2_format *pfmt)
{
return 0;
}
int camera_v4l2_g_fmt_vid_cap_mplane(struct file *file, void *fh,
struct v4l2_format *f)
{
return 0;
}
static int camera_v4l2_g_parm(struct file *filep, void *fh,
struct v4l2_streamparm *a)
{
/* TODO */
return 0;
}
static int camera_v4l2_s_parm(struct file *filep, void *fh,
struct v4l2_streamparm *parm)
{
int rc = 0;
struct v4l2_event event;
struct msm_v4l2_event_data *event_data =
(struct msm_v4l2_event_data *)&event.u.data[0];
struct camera_v4l2_private *sp = fh_to_private(fh);
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_NEW_STREAM, &event);
rc = msm_create_stream(event_data->session_id,
event_data->stream_id, &sp->vb2_q);
if (rc < 0)
return rc;
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
goto error;
rc = camera_check_event_status(&event);
if (rc < 0)
goto error;
/* use stream_id as stream index */
parm->parm.capture.extendedmode = sp->stream_id;
return rc;
error:
msm_delete_stream(event_data->session_id,
event_data->stream_id);
return rc;
}
static int camera_v4l2_subscribe_event(struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
int rc = 0;
struct camera_v4l2_private *sp = fh_to_private(fh);
rc = v4l2_event_subscribe(&sp->fh, sub, 5);
return rc;
}
static int camera_v4l2_unsubscribe_event(struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
int rc = 0;
struct camera_v4l2_private *sp = fh_to_private(fh);
rc = v4l2_event_unsubscribe(&sp->fh, sub);
return rc;
}
static const struct v4l2_ioctl_ops camera_v4l2_ioctl_ops = {
.vidioc_querycap = camera_v4l2_querycap,
.vidioc_s_crop = camera_v4l2_s_crop,
.vidioc_g_crop = camera_v4l2_g_crop,
.vidioc_queryctrl = camera_v4l2_queryctrl,
.vidioc_g_ctrl = camera_v4l2_g_ctrl,
.vidioc_s_ctrl = camera_v4l2_s_ctrl,
.vidioc_reqbufs = camera_v4l2_reqbufs,
.vidioc_querybuf = camera_v4l2_querybuf,
.vidioc_qbuf = camera_v4l2_qbuf,
.vidioc_dqbuf = camera_v4l2_dqbuf,
.vidioc_streamon = camera_v4l2_streamon,
.vidioc_streamoff = camera_v4l2_streamoff,
.vidioc_g_fmt_type_private = camera_v4l2_g_fmt_cap_private,
.vidioc_s_fmt_type_private = camera_v4l2_s_fmt_cap_private,
.vidioc_try_fmt_type_private = camera_v4l2_try_fmt_cap_private,
.vidioc_g_fmt_vid_cap_mplane = camera_v4l2_g_fmt_vid_cap_mplane,
/* Stream type-dependent parameter ioctls */
.vidioc_g_parm = camera_v4l2_g_parm,
.vidioc_s_parm = camera_v4l2_s_parm,
/* event subscribe/unsubscribe */
.vidioc_subscribe_event = camera_v4l2_subscribe_event,
.vidioc_unsubscribe_event = camera_v4l2_unsubscribe_event,
};
static int camera_v4l2_fh_open(struct file *filep)
{
struct msm_video_device *pvdev = video_drvdata(filep);
struct camera_v4l2_private *sp;
sp = kzalloc(sizeof(*sp), GFP_KERNEL);
if (!sp)
return -ENOMEM;
filep->private_data = &sp->fh;
/* stream_id = open id */
sp->stream_id = atomic_read(&pvdev->opened);
v4l2_fh_init(&sp->fh, pvdev->vdev);
v4l2_fh_add(&sp->fh);
return 0;
}
static int camera_v4l2_fh_release(struct file *filep)
{
struct camera_v4l2_private *sp = fh_to_private(filep->private_data);
if (sp) {
v4l2_fh_del(&sp->fh);
v4l2_fh_exit(&sp->fh);
}
kfree(sp);
return 0;
}
static int camera_v4l2_vb2_q_init(struct file *filep)
{
struct camera_v4l2_private *sp = fh_to_private(filep->private_data);
struct vb2_queue *q = &sp->vb2_q;
memset(q, 0, sizeof(struct vb2_queue));
/* free up this buffer when stream is done */
q->drv_priv =
kzalloc(sizeof(struct msm_v4l2_format_data), GFP_KERNEL);
if (!q->drv_priv)
return -ENOMEM;
q->mem_ops = msm_vb2_get_q_mem_ops();
q->ops = msm_vb2_get_q_ops();
/* default queue type */
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
q->io_modes = VB2_USERPTR;
q->io_flags = 0;
q->buf_struct_size = sizeof(struct msm_vb2_buffer);
vb2_queue_init(q);
return 0;
}
static void camera_v4l2_vb2_q_release(struct file *filep)
{
struct camera_v4l2_private *sp = filep->private_data;
kfree(sp->vb2_q.drv_priv);
vb2_queue_release(&sp->vb2_q);
}
static int camera_v4l2_open(struct file *filep)
{
int rc = 0;
struct v4l2_event event;
struct msm_video_device *pvdev = video_drvdata(filep);
BUG_ON(!pvdev);
rc = camera_v4l2_fh_open(filep);
if (rc < 0)
goto fh_open_fail;
/* every stream has a vb2 queue */
rc = camera_v4l2_vb2_q_init(filep);
if (rc < 0)
goto vb2_q_fail;
if (!atomic_read(&pvdev->opened)) {
/* create a new session when first opened */
rc = msm_create_session(pvdev->vdev->num, pvdev->vdev);
if (rc < 0)
goto session_fail;
rc = msm_create_command_ack_q(pvdev->vdev->num, 0);
if (rc < 0)
goto command_ack_q_fail;
camera_pack_event(filep, MSM_CAMERA_NEW_SESSION, 0, &event);
rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
if (rc < 0)
goto post_fail;
rc = camera_check_event_status(&event);
if (rc < 0)
goto post_fail;
} else {
rc = msm_create_command_ack_q(pvdev->vdev->num,
atomic_read(&pvdev->opened));
if (rc < 0)
goto session_fail;
}
atomic_add(1, &pvdev->opened);
return rc;
post_fail:
msm_delete_command_ack_q(pvdev->vdev->num, 0);
command_ack_q_fail:
msm_destroy_session(pvdev->vdev->num);
session_fail:
camera_v4l2_vb2_q_release(filep);
vb2_q_fail:
camera_v4l2_fh_release(filep);
fh_open_fail:
return rc;
}
static unsigned int camera_v4l2_poll(struct file *filep,
struct poll_table_struct *wait)
{
int rc = 0;
struct camera_v4l2_private *sp = fh_to_private(filep->private_data);
rc = vb2_poll(&sp->vb2_q, filep, wait);
poll_wait(filep, &sp->fh.wait, wait);
if (v4l2_event_pending(&sp->fh))
rc |= POLLPRI;
return rc;
}
static int camera_v4l2_close(struct file *filep)
{
int rc = 0;
struct v4l2_event event;
struct msm_video_device *pvdev = video_drvdata(filep);
struct camera_v4l2_private *sp = fh_to_private(filep->private_data);
BUG_ON(!pvdev);
atomic_sub_return(1, &pvdev->opened);
if (atomic_read(&pvdev->opened) == 0) {
camera_pack_event(filep, MSM_CAMERA_DEL_SESSION, 0, &event);
/* Donot wait, imaging server may have crashed */
msm_post_event(&event, -1);
/* This should take care of both normal close
* and application crashes */
msm_destroy_session(pvdev->vdev->num);
} else {
camera_pack_event(filep, MSM_CAMERA_SET_PARM,
MSM_CAMERA_PRIV_DEL_STREAM, &event);
/* Donot wait, imaging server may have crashed */
msm_post_event(&event, MSM_POST_EVT_TIMEOUT);
msm_delete_command_ack_q(pvdev->vdev->num,
sp->stream_id);
msm_delete_stream(pvdev->vdev->num, sp->stream_id);
}
camera_v4l2_vb2_q_release(filep);
camera_v4l2_fh_release(filep);
return rc;
}
static struct v4l2_file_operations camera_v4l2_fops = {
.owner = THIS_MODULE,
.open = camera_v4l2_open,
.poll = camera_v4l2_poll,
.release = camera_v4l2_close,
.ioctl = video_ioctl2,
};
int camera_init_v4l2(struct device *dev, unsigned int *session)
{
struct msm_video_device *pvdev;
struct v4l2_device *v4l2_dev;
int rc = 0;
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto init_end;
}
pvdev->vdev = video_device_alloc();
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
v4l2_dev = kzalloc(sizeof(struct v4l2_device), GFP_KERNEL);
if (WARN_ON(!v4l2_dev)) {
rc = -ENOMEM;
goto v4l2_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
v4l2_dev->mdev = kzalloc(sizeof(struct media_device),
GFP_KERNEL);
if (!v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME,
sizeof(v4l2_dev->mdev->model));
v4l2_dev->mdev->dev = dev;
rc = media_device_register(v4l2_dev->mdev);
if (WARN_ON(rc < 0))
goto media_fail;
rc = media_entity_init(&pvdev->vdev->entity, 0, NULL, 0);
if (WARN_ON(rc < 0))
goto entity_fail;
pvdev->vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
pvdev->vdev->entity.group_id = QCAMERA_VNODE_GROUP_ID;
#endif
v4l2_dev->notify = NULL;
pvdev->vdev->v4l2_dev = v4l2_dev;
rc = v4l2_device_register(dev, pvdev->vdev->v4l2_dev);
if (WARN_ON(rc < 0))
goto register_fail;
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1);
if (WARN_ON(rc < 0))
goto video_register_fail;
#if defined(CONFIG_MEDIA_CONTROLLER)
/* FIXME: How to get rid of this messy? */
pvdev->vdev->entity.name = video_device_node_name(pvdev->vdev);
#endif
*session = pvdev->vdev->num;
atomic_set(&pvdev->opened, 0);
video_set_drvdata(pvdev->vdev, pvdev);
goto init_end;
video_register_fail:
v4l2_device_unregister(pvdev->vdev->v4l2_dev);
register_fail:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_entity_cleanup(&pvdev->vdev->entity);
entity_fail:
media_device_unregister(v4l2_dev->mdev);
media_fail:
kfree(v4l2_dev->mdev);
mdev_fail:
#endif
kfree(v4l2_dev);
v4l2_fail:
video_device_release(pvdev->vdev);
video_fail:
kfree(pvdev);
init_end:
return rc;
}