mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 12:17:12 +09:00
media: rockchip/cif: add cif driver
This patch adds a new cif driver that is based on the media controller, async subdev and vb2. It now works in oneframe mode to receive yuv or bayer raw data. Change-Id: I34047715405ac8b4eaafc71cc8983b9afa3c0006 Signed-off-by: Xing Zheng <zhengxing@rock-chips.com> Signed-off-by: Shunqian Zheng <zhengsq@rock-chips.com>
This commit is contained in:
committed by
Tao Huang
parent
9794d0994a
commit
1052901f87
@@ -122,6 +122,7 @@ source "drivers/media/platform/s5p-tv/Kconfig"
|
||||
source "drivers/media/platform/am437x/Kconfig"
|
||||
source "drivers/media/platform/xilinx/Kconfig"
|
||||
source "drivers/media/platform/rk-isp10/Kconfig"
|
||||
source "drivers/media/platform/rockchip/cif/Kconfig"
|
||||
source "drivers/media/platform/rockchip/isp1/Kconfig"
|
||||
|
||||
endif # V4L_PLATFORM_DRIVERS
|
||||
|
||||
@@ -51,6 +51,7 @@ obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/
|
||||
|
||||
obj-y += omap/
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip/cif/
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rockchip/isp1/
|
||||
|
||||
obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/
|
||||
|
||||
11
drivers/media/platform/rockchip/cif/Kconfig
Normal file
11
drivers/media/platform/rockchip/cif/Kconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
config VIDEO_ROCKCHIP_CIF
|
||||
tristate "Rockchip Camera Interface driver"
|
||||
depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
depends on MEDIA_CAMERA_SUPPORT
|
||||
select VIDEOBUF2_DMA_CONTIG
|
||||
select V4L2_FWNODE
|
||||
default n
|
||||
help
|
||||
Support for CIF on the rockchip SoCs like rk312x, rk3288.
|
||||
3
drivers/media/platform/rockchip/cif/Makefile
Normal file
3
drivers/media/platform/rockchip/cif/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += video_rkcif.o
|
||||
video_rkcif-objs += dev.o capture.o
|
||||
969
drivers/media/platform/rockchip/cif/capture.c
Normal file
969
drivers/media/platform/rockchip/cif/capture.c
Normal file
@@ -0,0 +1,969 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Rockchip CIF Driver
|
||||
*
|
||||
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/v4l2-fh.h>
|
||||
#include <media/v4l2-fwnode.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/videobuf2-dma-contig.h>
|
||||
|
||||
#include "dev.h"
|
||||
#include "regs.h"
|
||||
|
||||
#define CIF_REQ_BUFS_MIN 1
|
||||
#define CIF_MIN_WIDTH 64
|
||||
#define CIF_MIN_HEIGHT 64
|
||||
#define CIF_MAX_WIDTH 8192
|
||||
#define CIF_MAX_HEIGHT 8192
|
||||
|
||||
#define RKCIF_PLANE_Y 0
|
||||
#define RKCIF_PLANE_CBCR 1
|
||||
|
||||
#define STREAM_PAD_SINK 0
|
||||
#define STREAM_PAD_SOURCE 1
|
||||
|
||||
/* TODO: pingpong mode is not yet supported */
|
||||
|
||||
/* Get xsubs and ysubs for fourcc formats
|
||||
*
|
||||
* @xsubs: horizontal color samples in a 4*4 matrix, for yuv
|
||||
* @ysubs: vertical color samples in a 4*4 matrix, for yuv
|
||||
*/
|
||||
static int fcc_xysubs(u32 fcc, u32 *xsubs, u32 *ysubs)
|
||||
{
|
||||
switch (fcc) {
|
||||
case V4L2_PIX_FMT_NV16:
|
||||
case V4L2_PIX_FMT_NV61:
|
||||
*xsubs = 2;
|
||||
*ysubs = 1;
|
||||
break;
|
||||
case V4L2_PIX_FMT_NV21:
|
||||
case V4L2_PIX_FMT_NV12:
|
||||
*xsubs = 2;
|
||||
*ysubs = 2;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct cif_output_fmt out_fmts[] = {
|
||||
{
|
||||
.fourcc = V4L2_PIX_FMT_NV16,
|
||||
.cplanes = 2,
|
||||
.mplanes = 1,
|
||||
.fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_UVUV,
|
||||
.bpp = { 8, 16 },
|
||||
}, {
|
||||
.fourcc = V4L2_PIX_FMT_NV61,
|
||||
.fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_VUVU,
|
||||
.cplanes = 2,
|
||||
.mplanes = 1,
|
||||
.bpp = { 8, 16 },
|
||||
}, {
|
||||
.fourcc = V4L2_PIX_FMT_NV12,
|
||||
.fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_UVUV,
|
||||
.cplanes = 2,
|
||||
.mplanes = 1,
|
||||
.bpp = { 8, 16 },
|
||||
}, {
|
||||
.fourcc = V4L2_PIX_FMT_NV21,
|
||||
.fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_VUVU,
|
||||
.cplanes = 2,
|
||||
.mplanes = 1,
|
||||
.bpp = { 8, 16 },
|
||||
},
|
||||
|
||||
/* TODO: We can support NV12M/NV21M/NV16M/NV61M too */
|
||||
};
|
||||
|
||||
static const struct cif_input_fmt in_fmts[] = {
|
||||
{
|
||||
.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
|
||||
.fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YUYV,
|
||||
.fmt_type = CIF_FMT_TYPE_YUV,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
|
||||
.fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YVYU,
|
||||
.fmt_type = CIF_FMT_TYPE_YUV,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
|
||||
.fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_UYVY,
|
||||
.fmt_type = CIF_FMT_TYPE_YUV,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
|
||||
.fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_VYUY,
|
||||
.fmt_type = CIF_FMT_TYPE_YUV,
|
||||
},
|
||||
};
|
||||
|
||||
/* Get active sensor by enabled media link */
|
||||
static struct rkcif_sensor_info *get_active_sensor(struct rkcif_stream *stream)
|
||||
{
|
||||
struct media_entity *remote_entity;
|
||||
struct media_pad *local, *remote;
|
||||
struct v4l2_subdev *sd;
|
||||
u32 i;
|
||||
|
||||
local = &stream->vdev.entity.pads[0];
|
||||
remote = media_entity_remote_pad(local);
|
||||
if (!remote) {
|
||||
v4l2_err(&stream->cifdev->v4l2_dev, "Not sensor linked\n");
|
||||
return NULL;
|
||||
}
|
||||
remote_entity = remote->entity;
|
||||
sd = media_entity_to_v4l2_subdev(remote_entity);
|
||||
|
||||
for (i = 0; i < stream->cifdev->num_sensors; ++i)
|
||||
if (stream->cifdev->sensors[i].sd == sd)
|
||||
return &stream->cifdev->sensors[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct
|
||||
cif_input_fmt *get_input_fmt(struct v4l2_subdev *sd, struct v4l2_rect *rect)
|
||||
{
|
||||
struct v4l2_subdev_format fmt;
|
||||
int ret;
|
||||
u32 i;
|
||||
|
||||
ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
|
||||
if (ret < 0) {
|
||||
v4l2_warn(sd->v4l2_dev,
|
||||
"sensor fmt invalid, set to default size\n");
|
||||
goto set_default;
|
||||
}
|
||||
|
||||
v4l2_dbg(1, rkcif_debug, sd->v4l2_dev,
|
||||
"remote fmt: mbus code:%d, size:%dx%d\n",
|
||||
fmt.format.code, fmt.format.width, fmt.format.height);
|
||||
rect->left = 0;
|
||||
rect->top = 0;
|
||||
rect->width = fmt.format.width;
|
||||
rect->height = fmt.format.height;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(in_fmts); i++)
|
||||
if (fmt.format.code == in_fmts[i].mbus_code)
|
||||
return &in_fmts[i];
|
||||
|
||||
v4l2_err(sd->v4l2_dev, "remote sensor mbus code not supported\n");
|
||||
|
||||
set_default:
|
||||
rect->left = 0;
|
||||
rect->top = 0;
|
||||
rect->width = RKCIF_DEFAULT_WIDTH;
|
||||
rect->height = RKCIF_DEFAULT_HEIGHT;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct
|
||||
cif_output_fmt *find_output_fmt(struct rkcif_stream *stream, u32 pixelfmt)
|
||||
{
|
||||
const struct cif_output_fmt *fmt;
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(out_fmts); i++) {
|
||||
fmt = &out_fmts[i];
|
||||
if (fmt->fourcc == pixelfmt)
|
||||
return fmt;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/***************************** stream operations ******************************/
|
||||
static void rkcif_assign_new_buffer_oneframe(struct rkcif_stream *stream)
|
||||
{
|
||||
struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
void __iomem *base = dev->base_addr;
|
||||
|
||||
/* Set up an empty buffer for the next frame */
|
||||
spin_lock(&stream->vbq_lock);
|
||||
if (!list_empty(&stream->buf_head)) {
|
||||
stream->curr_buf = list_first_entry(&stream->buf_head,
|
||||
struct rkcif_buffer, queue);
|
||||
list_del(&stream->curr_buf->queue);
|
||||
} else {
|
||||
stream->curr_buf = NULL;
|
||||
}
|
||||
spin_unlock(&stream->vbq_lock);
|
||||
|
||||
if (stream->curr_buf) {
|
||||
write_cif_reg(base, CIF_FRM0_ADDR_Y,
|
||||
stream->curr_buf->buff_addr[RKCIF_PLANE_Y]);
|
||||
write_cif_reg(base, CIF_FRM0_ADDR_UV,
|
||||
stream->curr_buf->buff_addr[RKCIF_PLANE_CBCR]);
|
||||
write_cif_reg(base, CIF_FRM1_ADDR_Y,
|
||||
stream->curr_buf->buff_addr[RKCIF_PLANE_Y]);
|
||||
write_cif_reg(base, CIF_FRM1_ADDR_UV,
|
||||
stream->curr_buf->buff_addr[RKCIF_PLANE_CBCR]);
|
||||
} else {
|
||||
v4l2_dbg(1, rkcif_debug, &dev->v4l2_dev, "Buf dropped\n");
|
||||
write_cif_reg(base, CIF_FRM0_ADDR_Y, dummy_buf->dma_addr);
|
||||
write_cif_reg(base, CIF_FRM0_ADDR_UV, dummy_buf->dma_addr);
|
||||
write_cif_reg(base, CIF_FRM1_ADDR_Y, dummy_buf->dma_addr);
|
||||
write_cif_reg(base, CIF_FRM1_ADDR_UV, dummy_buf->dma_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void rkcif_stream_stop(struct rkcif_stream *stream)
|
||||
{
|
||||
struct rkcif_device *cif_dev = stream->cifdev;
|
||||
void __iomem *base = cif_dev->base_addr;
|
||||
u32 val;
|
||||
|
||||
val = read_cif_reg(base, CIF_CTRL);
|
||||
write_cif_reg(base, CIF_CTRL, val & (~ENABLE_CAPTURE));
|
||||
write_cif_reg(base, CIF_INTEN, 0x0);
|
||||
write_cif_reg(base, CIF_INTSTAT, 0x3ff);
|
||||
write_cif_reg(base, CIF_FRAME_STATUS, 0x0);
|
||||
|
||||
stream->state = RKCIF_STATE_READY;
|
||||
}
|
||||
|
||||
static int rkcif_queue_setup(struct vb2_queue *queue,
|
||||
const void *parg,
|
||||
unsigned int *num_buffers,
|
||||
unsigned int *num_planes,
|
||||
unsigned int sizes[],
|
||||
void *alloc_ctxs[])
|
||||
{
|
||||
struct rkcif_stream *stream = queue->drv_priv;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
const struct v4l2_format *pfmt = parg;
|
||||
const struct v4l2_pix_format_mplane *pixm;
|
||||
const struct cif_output_fmt *cif_fmt;
|
||||
u32 i;
|
||||
|
||||
if (pfmt) {
|
||||
pixm = &pfmt->fmt.pix_mp;
|
||||
cif_fmt = find_output_fmt(stream, pixm->pixelformat);
|
||||
} else {
|
||||
pixm = &stream->pixm;
|
||||
cif_fmt = stream->cif_fmt_out;
|
||||
}
|
||||
|
||||
*num_planes = cif_fmt->mplanes;
|
||||
|
||||
for (i = 0; i < cif_fmt->mplanes; i++) {
|
||||
const struct v4l2_plane_pix_format *plane_fmt;
|
||||
|
||||
plane_fmt = &pixm->plane_fmt[i];
|
||||
sizes[i] = plane_fmt->sizeimage;
|
||||
alloc_ctxs[i] = dev->alloc_ctx;
|
||||
}
|
||||
|
||||
v4l2_dbg(1, rkcif_debug, &dev->v4l2_dev, "%s count %d, size %d\n",
|
||||
v4l2_type_names[queue->type], *num_buffers, sizes[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The vb2_buffer are stored in rkcif_buffer, in order to unify
|
||||
* mplane buffer and none-mplane buffer.
|
||||
*/
|
||||
static void rkcif_buf_queue(struct vb2_buffer *vb)
|
||||
{
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||
struct rkcif_buffer *cifbuf = to_rkcif_buffer(vbuf);
|
||||
struct vb2_queue *queue = vb->vb2_queue;
|
||||
struct rkcif_stream *stream = queue->drv_priv;
|
||||
struct v4l2_pix_format_mplane *pixm = &stream->pixm;
|
||||
const struct cif_output_fmt *fmt = stream->cif_fmt_out;
|
||||
unsigned long lock_flags = 0;
|
||||
int i;
|
||||
|
||||
memset(cifbuf->buff_addr, 0, sizeof(cifbuf->buff_addr));
|
||||
/* If mplanes > 1, every c-plane has its own m-plane,
|
||||
* otherwise, multiple c-planes are in the same m-plane
|
||||
*/
|
||||
for (i = 0; i < fmt->mplanes; i++) {
|
||||
void *addr = vb2_plane_vaddr(vb, i);
|
||||
|
||||
cifbuf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
|
||||
if (rkcif_debug && addr && !stream->cifdev->iommu_en) {
|
||||
memset(addr, 0, pixm->plane_fmt[i].sizeimage);
|
||||
v4l2_info(&stream->cifdev->v4l2_dev,
|
||||
"Clear buffer, size: 0x%08x\n",
|
||||
pixm->plane_fmt[i].sizeimage);
|
||||
}
|
||||
}
|
||||
if (fmt->mplanes == 1) {
|
||||
for (i = 0; i < fmt->cplanes - 1; i++)
|
||||
cifbuf->buff_addr[i + 1] = cifbuf->buff_addr[i] +
|
||||
pixm->plane_fmt[i].bytesperline * pixm->height;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&stream->vbq_lock, lock_flags);
|
||||
list_add_tail(&cifbuf->queue, &stream->buf_head);
|
||||
spin_unlock_irqrestore(&stream->vbq_lock, lock_flags);
|
||||
}
|
||||
|
||||
static int rkcif_create_dummy_buf(struct rkcif_stream *stream)
|
||||
{
|
||||
struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
|
||||
/* get a maximum plane size */
|
||||
dummy_buf->size = max3(stream->pixm.plane_fmt[0].bytesperline *
|
||||
stream->pixm.height,
|
||||
stream->pixm.plane_fmt[1].sizeimage,
|
||||
stream->pixm.plane_fmt[2].sizeimage);
|
||||
|
||||
dummy_buf->vaddr = dma_alloc_coherent(dev->dev, dummy_buf->size,
|
||||
&dummy_buf->dma_addr,
|
||||
GFP_KERNEL);
|
||||
if (!dummy_buf->vaddr) {
|
||||
v4l2_err(&dev->v4l2_dev,
|
||||
"Failed to allocate the memory for dummy buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
v4l2_info(&dev->v4l2_dev, "Allocate dummy buffer, size: 0x%08x\n",
|
||||
dummy_buf->size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkcif_destroy_dummy_buf(struct rkcif_stream *stream)
|
||||
{
|
||||
struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
|
||||
dma_free_coherent(dev->dev, dummy_buf->size,
|
||||
dummy_buf->vaddr, dummy_buf->dma_addr);
|
||||
}
|
||||
|
||||
static void rkcif_stop_streaming(struct vb2_queue *queue)
|
||||
{
|
||||
struct rkcif_stream *stream = queue->drv_priv;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
struct rkcif_buffer *buf;
|
||||
struct v4l2_subdev *sd;
|
||||
int ret;
|
||||
|
||||
stream->stopping = true;
|
||||
ret = wait_event_timeout(stream->wq_stopped,
|
||||
stream->state != RKCIF_STATE_STREAMING,
|
||||
msecs_to_jiffies(1000));
|
||||
if (!ret)
|
||||
rkcif_stream_stop(stream);
|
||||
pm_runtime_put(dev->dev);
|
||||
|
||||
/* stop the sub device*/
|
||||
sd = dev->active_sensor->sd;
|
||||
v4l2_subdev_call(sd, video, s_stream, 0);
|
||||
v4l2_subdev_call(sd, core, s_power, 0);
|
||||
|
||||
/* release buffers */
|
||||
if (stream->curr_buf) {
|
||||
list_add_tail(&stream->curr_buf->queue, &stream->buf_head);
|
||||
stream->curr_buf = NULL;
|
||||
}
|
||||
while (!list_empty(&stream->buf_head)) {
|
||||
buf = list_first_entry(&stream->buf_head,
|
||||
struct rkcif_buffer, queue);
|
||||
list_del(&buf->queue);
|
||||
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
|
||||
}
|
||||
|
||||
rkcif_destroy_dummy_buf(stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* CIF supports the following input modes,
|
||||
* YUV, the default mode
|
||||
* PAL,
|
||||
* NTSC,
|
||||
* RAW, if the input format is raw bayer
|
||||
* JPEG, TODO
|
||||
* MIPI, TODO
|
||||
*/
|
||||
static u32 rkcif_determine_input_mode(struct rkcif_device *dev)
|
||||
{
|
||||
struct rkcif_sensor_info *sensor_info = dev->active_sensor;
|
||||
struct rkcif_stream *stream = &dev->stream;
|
||||
v4l2_std_id std;
|
||||
u32 mode = INPUT_MODE_YUV;
|
||||
int ret;
|
||||
|
||||
ret = v4l2_subdev_call(sensor_info->sd, video, querystd, &std);
|
||||
if (ret == 0) {
|
||||
/* retrieve std from sensor if exist */
|
||||
switch (std) {
|
||||
case V4L2_STD_NTSC:
|
||||
mode = INPUT_MODE_NTSC;
|
||||
break;
|
||||
case V4L2_STD_PAL:
|
||||
mode = INPUT_MODE_PAL;
|
||||
break;
|
||||
default:
|
||||
v4l2_err(&dev->v4l2_dev,
|
||||
"std: %lld is not supported", std);
|
||||
}
|
||||
} else {
|
||||
/* determine input mode by mbus_code (fmt_type) */
|
||||
switch (stream->cif_fmt_in->fmt_type) {
|
||||
case CIF_FMT_TYPE_YUV:
|
||||
mode = INPUT_MODE_YUV;
|
||||
break;
|
||||
case CIF_FMT_TYPE_RAW:
|
||||
mode = INPUT_MODE_RAW;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static inline u32 rkcif_scl_ctl(struct rkcif_stream *stream)
|
||||
{
|
||||
u32 fmt_type = stream->cif_fmt_in->fmt_type;
|
||||
|
||||
return (fmt_type == CIF_FMT_TYPE_YUV) ?
|
||||
ENABLE_YUV_16BIT_BYPASS : ENABLE_RAW_16BIT_BYPASS;
|
||||
}
|
||||
|
||||
static int rkcif_stream_start(struct rkcif_stream *stream)
|
||||
{
|
||||
u32 val, mbus_flags, href_pol, vsync_pol, skip_top = 0;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
struct rkcif_sensor_info *sensor_info;
|
||||
void __iomem *base = dev->base_addr;
|
||||
|
||||
sensor_info = dev->active_sensor;
|
||||
stream->frame_idx = 0;
|
||||
|
||||
mbus_flags = sensor_info->mbus.flags;
|
||||
href_pol = (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) ?
|
||||
HSY_HIGH_ACTIVE : HSY_LOW_ACTIVE;
|
||||
vsync_pol = (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) ?
|
||||
VSY_HIGH_ACTIVE : VSY_LOW_ACTIVE;
|
||||
|
||||
val = vsync_pol | href_pol | rkcif_determine_input_mode(dev) |
|
||||
stream->cif_fmt_out->fmt_val | stream->cif_fmt_in->fmt_val;
|
||||
write_cif_reg(base, CIF_FOR, val);
|
||||
write_cif_reg(base, CIF_VIR_LINE_WIDTH, stream->pixm.width);
|
||||
write_cif_reg(base, CIF_SET_SIZE,
|
||||
stream->pixm.width | (stream->pixm.height << 16));
|
||||
|
||||
v4l2_subdev_call(sensor_info->sd, sensor, g_skip_top_lines, &skip_top);
|
||||
|
||||
/* TODO: set crop properly */
|
||||
write_cif_reg(base, CIF_CROP, skip_top << CIF_CROP_Y_SHIFT);
|
||||
write_cif_reg(base, CIF_FRAME_STATUS, FRAME_STAT_CLS);
|
||||
write_cif_reg(base, CIF_INTSTAT, INTSTAT_CLS);
|
||||
write_cif_reg(base, CIF_SCL_CTRL, rkcif_scl_ctl(stream));
|
||||
|
||||
/* Set up an buffer for the next frame */
|
||||
rkcif_assign_new_buffer_oneframe(stream);
|
||||
|
||||
write_cif_reg(base, CIF_INTEN, FRAME_END_EN | PST_INF_FRAME_END);
|
||||
write_cif_reg(base, CIF_CTRL,
|
||||
AXI_BURST_16 | MODE_ONEFRAME | ENABLE_CAPTURE);
|
||||
|
||||
stream->state = RKCIF_STATE_STREAMING;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_sanity_check_fmt(struct rkcif_stream *stream)
|
||||
{
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
|
||||
struct v4l2_rect input, *crop;
|
||||
|
||||
stream->cif_fmt_in = get_input_fmt(dev->active_sensor->sd, &input);
|
||||
if (!stream->cif_fmt_in) {
|
||||
v4l2_err(v4l2_dev, "Input fmt is invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
crop = &stream->crop;
|
||||
if (crop->width + crop->left > input.width ||
|
||||
crop->height + crop->top > input.height) {
|
||||
v4l2_err(v4l2_dev, "crop size is bigger than input\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_start_streaming(struct vb2_queue *queue, unsigned int count)
|
||||
{
|
||||
struct rkcif_stream *stream = queue->drv_priv;
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
|
||||
struct v4l2_subdev *sd;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(stream->state != RKCIF_STATE_READY)) {
|
||||
ret = -EBUSY;
|
||||
v4l2_err(v4l2_dev, "stream in busy state\n");
|
||||
goto destroy_buf;
|
||||
}
|
||||
|
||||
ret = rkcif_sanity_check_fmt(stream);
|
||||
if (ret < 0)
|
||||
goto destroy_buf;
|
||||
|
||||
ret = rkcif_create_dummy_buf(stream);
|
||||
if (ret < 0) {
|
||||
v4l2_err(v4l2_dev, "Failed to create dummy_buf, %d\n", ret);
|
||||
goto destroy_buf;
|
||||
}
|
||||
|
||||
ret = pm_runtime_get_sync(dev->dev);
|
||||
if (ret < 0) {
|
||||
v4l2_err(v4l2_dev, "Failed to get runtime pm, %d\n", ret);
|
||||
goto destroy_dummy_buf;
|
||||
}
|
||||
ret = rkcif_stream_start(stream);
|
||||
if (ret < 0)
|
||||
goto runtime_put;
|
||||
|
||||
/* start sub-devices */
|
||||
sd = dev->active_sensor->sd;
|
||||
ret = v4l2_subdev_call(sd, core, s_power, 1);
|
||||
if (ret < 0 && ret != -ENOIOCTLCMD)
|
||||
goto stop_stream;
|
||||
ret = v4l2_subdev_call(sd, video, s_stream, 1);
|
||||
if (ret < 0)
|
||||
goto subdev_poweroff;
|
||||
|
||||
return 0;
|
||||
|
||||
subdev_poweroff:
|
||||
v4l2_subdev_call(sd, core, s_power, 0);
|
||||
stop_stream:
|
||||
rkcif_stream_stop(stream);
|
||||
runtime_put:
|
||||
pm_runtime_put(dev->dev);
|
||||
destroy_dummy_buf:
|
||||
rkcif_destroy_dummy_buf(stream);
|
||||
destroy_buf:
|
||||
while (!list_empty(&stream->buf_head)) {
|
||||
struct rkcif_buffer *buf;
|
||||
|
||||
buf = list_first_entry(&stream->buf_head,
|
||||
struct rkcif_buffer, queue);
|
||||
list_del(&buf->queue);
|
||||
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct vb2_ops rkcif_vb2_ops = {
|
||||
.queue_setup = rkcif_queue_setup,
|
||||
.buf_queue = rkcif_buf_queue,
|
||||
.wait_prepare = vb2_ops_wait_prepare,
|
||||
.wait_finish = vb2_ops_wait_finish,
|
||||
.stop_streaming = rkcif_stop_streaming,
|
||||
.start_streaming = rkcif_start_streaming,
|
||||
};
|
||||
|
||||
static int rkcif_init_vb2_queue(struct vb2_queue *q,
|
||||
struct rkcif_stream *stream,
|
||||
enum v4l2_buf_type buf_type)
|
||||
{
|
||||
q->type = buf_type;
|
||||
q->io_modes = VB2_MMAP | VB2_DMABUF;
|
||||
q->drv_priv = stream;
|
||||
q->ops = &rkcif_vb2_ops;
|
||||
q->mem_ops = &vb2_dma_contig_memops;
|
||||
q->buf_struct_size = sizeof(struct rkcif_buffer);
|
||||
q->min_buffers_needed = CIF_REQ_BUFS_MIN;
|
||||
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
q->lock = &stream->vlock;
|
||||
|
||||
return vb2_queue_init(q);
|
||||
}
|
||||
|
||||
static void rkcif_set_fmt(struct rkcif_stream *stream,
|
||||
struct v4l2_pix_format_mplane *pixm,
|
||||
bool try)
|
||||
{
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
const struct cif_output_fmt *fmt;
|
||||
struct v4l2_rect input_rect;
|
||||
unsigned int imagesize = 0, planes;
|
||||
u32 xsubs = 1, ysubs = 1, i;
|
||||
|
||||
fmt = find_output_fmt(stream, pixm->pixelformat);
|
||||
if (!fmt)
|
||||
fmt = &out_fmts[0];
|
||||
|
||||
input_rect.width = RKCIF_DEFAULT_WIDTH;
|
||||
input_rect.height = RKCIF_DEFAULT_HEIGHT;
|
||||
|
||||
if (dev->active_sensor && dev->active_sensor->sd)
|
||||
get_input_fmt(dev->active_sensor->sd, &input_rect);
|
||||
|
||||
/* CIF has not scale function,
|
||||
* the size should not be larger than input
|
||||
*/
|
||||
pixm->width = clamp_t(u32, pixm->width,
|
||||
CIF_MIN_WIDTH, input_rect.width);
|
||||
pixm->height = clamp_t(u32, pixm->height,
|
||||
CIF_MIN_HEIGHT, input_rect.height);
|
||||
pixm->num_planes = fmt->mplanes;
|
||||
pixm->field = V4L2_FIELD_NONE;
|
||||
pixm->quantization = V4L2_QUANTIZATION_DEFAULT;
|
||||
|
||||
/* calculate plane size and image size */
|
||||
fcc_xysubs(fmt->fourcc, &xsubs, &ysubs);
|
||||
|
||||
planes = fmt->cplanes ? fmt->cplanes : fmt->mplanes;
|
||||
|
||||
for (i = 0; i < planes; i++) {
|
||||
struct v4l2_plane_pix_format *plane_fmt;
|
||||
int width, height, bpl, size;
|
||||
|
||||
if (i == 0) {
|
||||
width = pixm->width;
|
||||
height = pixm->height;
|
||||
} else {
|
||||
width = pixm->width / xsubs;
|
||||
height = pixm->height / ysubs;
|
||||
}
|
||||
|
||||
bpl = width * fmt->bpp[i] / 8;
|
||||
size = bpl * height;
|
||||
imagesize += size;
|
||||
|
||||
if (fmt->mplanes > i) {
|
||||
/* Set bpl and size for each mplane */
|
||||
plane_fmt = pixm->plane_fmt + i;
|
||||
plane_fmt->bytesperline = bpl;
|
||||
plane_fmt->sizeimage = size;
|
||||
}
|
||||
v4l2_dbg(1, rkcif_debug, &stream->cifdev->v4l2_dev,
|
||||
"C-Plane %i size: %d, Total imagesize: %d\n",
|
||||
i, size, imagesize);
|
||||
}
|
||||
|
||||
/* convert to non-MPLANE format.
|
||||
* It's important since we want to unify non-MPLANE
|
||||
* and MPLANE.
|
||||
*/
|
||||
if (fmt->mplanes == 1)
|
||||
pixm->plane_fmt[0].sizeimage = imagesize;
|
||||
|
||||
if (!try) {
|
||||
stream->cif_fmt_out = fmt;
|
||||
stream->pixm = *pixm;
|
||||
|
||||
v4l2_dbg(1, rkcif_debug, &stream->cifdev->v4l2_dev,
|
||||
"%s: req(%d, %d) out(%d, %d)\n", __func__,
|
||||
pixm->width, pixm->height,
|
||||
stream->pixm.width, stream->pixm.height);
|
||||
}
|
||||
}
|
||||
|
||||
void rkcif_stream_init(struct rkcif_device *dev)
|
||||
{
|
||||
struct rkcif_stream *stream = &dev->stream;
|
||||
struct v4l2_pix_format_mplane pixm;
|
||||
|
||||
memset(stream, 0, sizeof(*stream));
|
||||
memset(&pixm, 0, sizeof(pixm));
|
||||
stream->cifdev = dev;
|
||||
|
||||
INIT_LIST_HEAD(&stream->buf_head);
|
||||
spin_lock_init(&stream->vbq_lock);
|
||||
stream->state = RKCIF_STATE_READY;
|
||||
init_waitqueue_head(&stream->wq_stopped);
|
||||
|
||||
/* Set default format */
|
||||
pixm.pixelformat = V4L2_PIX_FMT_NV12;
|
||||
pixm.width = RKCIF_DEFAULT_WIDTH;
|
||||
pixm.height = RKCIF_DEFAULT_HEIGHT;
|
||||
rkcif_set_fmt(stream, &pixm, false);
|
||||
|
||||
stream->crop.left = 0;
|
||||
stream->crop.top = 0;
|
||||
stream->crop.width = RKCIF_DEFAULT_WIDTH;
|
||||
stream->crop.height = RKCIF_DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
static int rkcif_fh_open(struct file *filp)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(filp);
|
||||
struct rkcif_stream *stream = to_rkcif_stream(vdev);
|
||||
struct rkcif_device *cifdev = stream->cifdev;
|
||||
|
||||
/* Make sure active sensor is valid before .set_fmt() */
|
||||
cifdev->active_sensor = get_active_sensor(stream);
|
||||
if (!cifdev->active_sensor) {
|
||||
v4l2_err(vdev, "Not sensor linked\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Soft reset via CRU.
|
||||
* Because CRU would reset iommu too, so there's not chance
|
||||
* to reset cif once we hold buffers after buf queued
|
||||
*/
|
||||
rkcif_soft_reset(cifdev);
|
||||
|
||||
return v4l2_fh_open(filp);
|
||||
}
|
||||
|
||||
static const struct v4l2_file_operations rkcif_fops = {
|
||||
.open = rkcif_fh_open,
|
||||
.release = vb2_fop_release,
|
||||
.unlocked_ioctl = video_ioctl2,
|
||||
.poll = vb2_fop_poll,
|
||||
.mmap = vb2_fop_mmap,
|
||||
};
|
||||
|
||||
static int rkcif_enum_input(struct file *file, void *priv,
|
||||
struct v4l2_input *input)
|
||||
{
|
||||
if (input->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
input->type = V4L2_INPUT_TYPE_CAMERA;
|
||||
strlcpy(input->name, "Camera", sizeof(input->name));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_try_fmt_vid_cap_mplane(struct file *file, void *fh,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
struct rkcif_stream *stream = video_drvdata(file);
|
||||
|
||||
rkcif_set_fmt(stream, &f->fmt.pix_mp, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_enum_fmt_vid_cap_mplane(struct file *file, void *priv,
|
||||
struct v4l2_fmtdesc *f)
|
||||
{
|
||||
const struct cif_output_fmt *fmt = NULL;
|
||||
|
||||
if (f->index >= ARRAY_SIZE(out_fmts))
|
||||
return -EINVAL;
|
||||
|
||||
fmt = &out_fmts[f->index];
|
||||
f->pixelformat = fmt->fourcc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_s_fmt_vid_cap_mplane(struct file *file,
|
||||
void *priv, struct v4l2_format *f)
|
||||
{
|
||||
struct rkcif_stream *stream = video_drvdata(file);
|
||||
struct rkcif_device *dev = stream->cifdev;
|
||||
|
||||
if (vb2_is_busy(&stream->buf_queue)) {
|
||||
v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
rkcif_set_fmt(stream, &f->fmt.pix_mp, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_g_fmt_vid_cap_mplane(struct file *file, void *fh,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
struct rkcif_stream *stream = video_drvdata(file);
|
||||
|
||||
f->fmt.pix_mp = stream->pixm;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_querycap(struct file *file, void *priv,
|
||||
struct v4l2_capability *cap)
|
||||
{
|
||||
struct rkcif_stream *stream = video_drvdata(file);
|
||||
struct device *dev = stream->cifdev->dev;
|
||||
|
||||
strlcpy(cap->driver, dev->driver->name, sizeof(cap->driver));
|
||||
strlcpy(cap->card, dev->driver->name, sizeof(cap->card));
|
||||
snprintf(cap->bus_info, sizeof(cap->bus_info),
|
||||
"platform:%s", dev_name(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ioctl_ops rkcif_v4l2_ioctl_ops = {
|
||||
.vidioc_reqbufs = vb2_ioctl_reqbufs,
|
||||
.vidioc_querybuf = vb2_ioctl_querybuf,
|
||||
.vidioc_create_bufs = vb2_ioctl_create_bufs,
|
||||
.vidioc_qbuf = vb2_ioctl_qbuf,
|
||||
.vidioc_expbuf = vb2_ioctl_expbuf,
|
||||
.vidioc_dqbuf = vb2_ioctl_dqbuf,
|
||||
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
|
||||
.vidioc_streamon = vb2_ioctl_streamon,
|
||||
.vidioc_streamoff = vb2_ioctl_streamoff,
|
||||
.vidioc_enum_input = rkcif_enum_input,
|
||||
.vidioc_try_fmt_vid_cap_mplane = rkcif_try_fmt_vid_cap_mplane,
|
||||
.vidioc_enum_fmt_vid_cap_mplane = rkcif_enum_fmt_vid_cap_mplane,
|
||||
.vidioc_s_fmt_vid_cap_mplane = rkcif_s_fmt_vid_cap_mplane,
|
||||
.vidioc_g_fmt_vid_cap_mplane = rkcif_g_fmt_vid_cap_mplane,
|
||||
.vidioc_querycap = rkcif_querycap,
|
||||
};
|
||||
|
||||
void rkcif_unregister_stream_vdev(struct rkcif_device *dev)
|
||||
{
|
||||
struct rkcif_stream *stream = &dev->stream;
|
||||
|
||||
media_entity_cleanup(&stream->vdev.entity);
|
||||
video_unregister_device(&stream->vdev);
|
||||
}
|
||||
|
||||
int rkcif_register_stream_vdev(struct rkcif_device *dev)
|
||||
{
|
||||
struct rkcif_stream *stream = &dev->stream;
|
||||
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
|
||||
struct video_device *vdev = &stream->vdev;
|
||||
int ret;
|
||||
|
||||
strlcpy(vdev->name, "stream_cif", sizeof(vdev->name));
|
||||
mutex_init(&stream->vlock);
|
||||
|
||||
vdev->ioctl_ops = &rkcif_v4l2_ioctl_ops;
|
||||
vdev->release = video_device_release_empty;
|
||||
vdev->fops = &rkcif_fops;
|
||||
vdev->minor = -1;
|
||||
vdev->v4l2_dev = v4l2_dev;
|
||||
vdev->lock = &stream->vlock;
|
||||
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE |
|
||||
V4L2_CAP_STREAMING;
|
||||
video_set_drvdata(vdev, stream);
|
||||
vdev->vfl_dir = VFL_DIR_RX;
|
||||
stream->pad.flags = MEDIA_PAD_FL_SINK;
|
||||
|
||||
dev->alloc_ctx = vb2_dma_contig_init_ctx(v4l2_dev->dev);
|
||||
if (IS_ERR(dev->alloc_ctx)) {
|
||||
v4l2_err(&dev->v4l2_dev, "Failed to init memory allocator\n");
|
||||
ret = PTR_ERR(dev->alloc_ctx);
|
||||
goto err;
|
||||
}
|
||||
|
||||
rkcif_init_vb2_queue(&stream->buf_queue, stream,
|
||||
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
|
||||
vdev->queue = &stream->buf_queue;
|
||||
|
||||
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
|
||||
if (ret < 0) {
|
||||
v4l2_err(v4l2_dev,
|
||||
"video_register_device failed with error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = media_entity_init(&vdev->entity, 1, &stream->pad, 0);
|
||||
if (ret < 0)
|
||||
goto unreg;
|
||||
|
||||
return 0;
|
||||
unreg:
|
||||
video_unregister_device(vdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rkcif_vb_done_oneframe(struct rkcif_stream *stream,
|
||||
struct vb2_v4l2_buffer *vb_done)
|
||||
{
|
||||
const struct cif_output_fmt *fmt = stream->cif_fmt_out;
|
||||
u32 i;
|
||||
|
||||
/* Dequeue a filled buffer */
|
||||
for (i = 0; i < fmt->mplanes; i++) {
|
||||
vb2_set_plane_payload(&vb_done->vb2_buf, i,
|
||||
stream->pixm.plane_fmt[i].sizeimage);
|
||||
}
|
||||
vb_done->timestamp = ns_to_timeval(ktime_get_ns());
|
||||
vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE);
|
||||
}
|
||||
|
||||
void rkcif_irq_oneframe(struct rkcif_device *cif_dev)
|
||||
{
|
||||
struct rkcif_stream *stream = &cif_dev->stream;
|
||||
u32 lastline, lastpix, ctl, cif_frmst, intstat;
|
||||
void __iomem *base = cif_dev->base_addr;
|
||||
|
||||
intstat = read_cif_reg(base, CIF_INTSTAT);
|
||||
cif_frmst = read_cif_reg(base, CIF_FRAME_STATUS);
|
||||
lastline = read_cif_reg(base, CIF_LAST_LINE);
|
||||
lastpix = read_cif_reg(base, CIF_LAST_PIX);
|
||||
ctl = read_cif_reg(base, CIF_CTRL);
|
||||
|
||||
/* There are two irqs enabled:
|
||||
* - PST_INF_FRAME_END: cif FIFO is ready, this is prior to FRAME_END
|
||||
* - FRAME_END: cif has saved frame to memory, a frame ready
|
||||
*/
|
||||
|
||||
if ((intstat & PST_INF_FRAME_END)) {
|
||||
write_cif_reg(base, CIF_INTSTAT, PST_INF_FRAME_END_CLR);
|
||||
|
||||
if (stream->stopping)
|
||||
/* To stop CIF ASAP, before FRAME_END irq */
|
||||
write_cif_reg(base, CIF_CTRL, ctl & (~ENABLE_CAPTURE));
|
||||
}
|
||||
|
||||
if ((intstat & FRAME_END)) {
|
||||
struct vb2_v4l2_buffer *vb_done = NULL;
|
||||
|
||||
write_cif_reg(base, CIF_INTSTAT, FRAME_END_CLR);
|
||||
|
||||
if (stream->stopping) {
|
||||
rkcif_stream_stop(stream);
|
||||
stream->stopping = false;
|
||||
wake_up(&stream->wq_stopped);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastline != stream->pixm.height ||
|
||||
!(cif_frmst & CIF_F0_READY)) {
|
||||
v4l2_err(&cif_dev->v4l2_dev,
|
||||
"Bad frame, irq:0x%x frmst:0x%x size:%dx%d\n",
|
||||
intstat, cif_frmst, lastline, lastpix);
|
||||
/* Clear status to receive into the same buffer */
|
||||
write_cif_reg(base, CIF_FRAME_STATUS, FRM0_STAT_CLS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream->curr_buf)
|
||||
vb_done = &stream->curr_buf->vb;
|
||||
rkcif_assign_new_buffer_oneframe(stream);
|
||||
|
||||
/* In one-frame mode, must clear status manually to enable
|
||||
* the next frame end irq
|
||||
*/
|
||||
write_cif_reg(base, CIF_FRAME_STATUS, FRM0_STAT_CLS);
|
||||
|
||||
if (vb_done)
|
||||
rkcif_vb_done_oneframe(stream, vb_done);
|
||||
|
||||
stream->frame_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
void rkcif_irq_pingpong(struct rkcif_device *cif_dev)
|
||||
{
|
||||
/* TODO */
|
||||
}
|
||||
501
drivers/media/platform/rockchip/cif/dev.c
Normal file
501
drivers/media/platform/rockchip/cif/dev.c
Normal file
@@ -0,0 +1,501 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Rockchip CIF Driver
|
||||
*
|
||||
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/dma-iommu.h>
|
||||
#include <media/v4l2-fwnode.h>
|
||||
|
||||
#include "dev.h"
|
||||
#include "regs.h"
|
||||
|
||||
struct cif_match_data {
|
||||
const char * const *clks;
|
||||
int size;
|
||||
};
|
||||
|
||||
int rkcif_debug;
|
||||
module_param_named(debug, rkcif_debug, int, 0644);
|
||||
MODULE_PARM_DESC(debug, "Debug level (0-1)");
|
||||
|
||||
int using_pingpong;
|
||||
|
||||
/***************************** media controller *******************************/
|
||||
static int rkcif_create_links(struct rkcif_device *dev)
|
||||
{
|
||||
unsigned int s, pad;
|
||||
int ret;
|
||||
|
||||
/* sensor links(or mipi-phy) */
|
||||
for (s = 0; s < dev->num_sensors; ++s) {
|
||||
struct rkcif_sensor_info *sensor = &dev->sensors[s];
|
||||
|
||||
for (pad = 0; pad < sensor->sd->entity.num_pads; pad++)
|
||||
if (sensor->sd->entity.pads[pad].flags &
|
||||
MEDIA_PAD_FL_SOURCE)
|
||||
break;
|
||||
|
||||
if (pad == sensor->sd->entity.num_pads) {
|
||||
dev_err(dev->dev, "failed to find src pad for %s\n",
|
||||
sensor->sd->name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = media_entity_create_link(&sensor->sd->entity,
|
||||
pad, &dev->stream.vdev.entity, 0,
|
||||
s ? 0 : MEDIA_LNK_FL_ENABLED);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to create link for %s\n",
|
||||
sensor->sd->name);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
|
||||
{
|
||||
struct rkcif_device *dev;
|
||||
int ret;
|
||||
|
||||
dev = container_of(notifier, struct rkcif_device, notifier);
|
||||
|
||||
mutex_lock(&dev->media_dev.graph_mutex);
|
||||
|
||||
ret = rkcif_create_links(dev);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n");
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&dev->media_dev.graph_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct rkcif_async_subdev {
|
||||
struct v4l2_async_subdev asd;
|
||||
struct v4l2_mbus_config mbus;
|
||||
};
|
||||
|
||||
static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
|
||||
struct v4l2_subdev *subdev,
|
||||
struct v4l2_async_subdev *asd)
|
||||
{
|
||||
struct rkcif_device *cif_dev = container_of(notifier,
|
||||
struct rkcif_device, notifier);
|
||||
struct rkcif_async_subdev *s_asd = container_of(asd,
|
||||
struct rkcif_async_subdev, asd);
|
||||
|
||||
if (cif_dev->num_sensors == ARRAY_SIZE(cif_dev->sensors))
|
||||
return -EBUSY;
|
||||
|
||||
cif_dev->sensors[cif_dev->num_sensors].mbus = s_asd->mbus;
|
||||
cif_dev->sensors[cif_dev->num_sensors].sd = subdev;
|
||||
++cif_dev->num_sensors;
|
||||
|
||||
v4l2_dbg(1, rkcif_debug, subdev, "Async registered subdev\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkcif_fwnode_parse(struct device *dev,
|
||||
struct v4l2_fwnode_endpoint *vep,
|
||||
struct v4l2_async_subdev *asd)
|
||||
{
|
||||
struct rkcif_async_subdev *rk_asd =
|
||||
container_of(asd, struct rkcif_async_subdev, asd);
|
||||
struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel;
|
||||
|
||||
/*
|
||||
* MIPI sensor is linked with a mipi dphy and its media bus config can
|
||||
* not be get in here
|
||||
*/
|
||||
if (vep->bus_type != V4L2_MBUS_BT656 &&
|
||||
vep->bus_type != V4L2_MBUS_PARALLEL)
|
||||
return 0;
|
||||
|
||||
rk_asd->mbus.flags = bus->flags;
|
||||
rk_asd->mbus.type = vep->bus_type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_async_notifier_operations subdev_notifier_ops = {
|
||||
.bound = subdev_notifier_bound,
|
||||
.complete = subdev_notifier_complete,
|
||||
};
|
||||
|
||||
static int cif_subdev_notifier(struct rkcif_device *cif_dev)
|
||||
{
|
||||
struct v4l2_async_notifier *ntf = &cif_dev->notifier;
|
||||
struct device *dev = cif_dev->dev;
|
||||
int ret;
|
||||
|
||||
ret = v4l2_async_notifier_parse_fwnode_endpoints(dev, ntf,
|
||||
sizeof(struct rkcif_async_subdev), rkcif_fwnode_parse);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!ntf->num_subdevs)
|
||||
return -ENODEV; /* no endpoint */
|
||||
|
||||
ntf->ops = &subdev_notifier_ops;
|
||||
|
||||
return v4l2_async_notifier_register(&cif_dev->v4l2_dev, ntf);
|
||||
}
|
||||
|
||||
/***************************** platform deive *******************************/
|
||||
|
||||
static int rkcif_register_platform_subdevs(struct rkcif_device *cif_dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = rkcif_register_stream_vdev(cif_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = cif_subdev_notifier(cif_dev);
|
||||
if (ret < 0) {
|
||||
v4l2_err(&cif_dev->v4l2_dev,
|
||||
"Failed to register subdev notifier(%d)\n", ret);
|
||||
rkcif_unregister_stream_vdev(cif_dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const rk3128_cif_clks[] = {
|
||||
"aclk_cif",
|
||||
"hclk_cif",
|
||||
"sclk_cif_out",
|
||||
};
|
||||
|
||||
static const char * const rk3288_cif_clks[] = {
|
||||
"aclk_cif0",
|
||||
"hclk_cif0",
|
||||
"cif0_in",
|
||||
"cif0_out",
|
||||
};
|
||||
|
||||
static const struct cif_match_data rk3128_cif_clk_data = {
|
||||
.clks = rk3128_cif_clks,
|
||||
.size = ARRAY_SIZE(rk3128_cif_clks),
|
||||
};
|
||||
|
||||
static const struct cif_match_data rk3288_cif_clk_data = {
|
||||
.clks = rk3288_cif_clks,
|
||||
.size = ARRAY_SIZE(rk3288_cif_clks),
|
||||
};
|
||||
|
||||
static const struct of_device_id rkcif_plat_of_match[] = {
|
||||
{
|
||||
.compatible = "rockchip,rk3128-cif",
|
||||
.data = &rk3128_cif_clk_data,
|
||||
},
|
||||
{
|
||||
.compatible = "rockchip,rk3288-cif",
|
||||
.data = &rk3288_cif_clk_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static irqreturn_t rkcif_irq_handler(int irq, void *ctx)
|
||||
{
|
||||
struct device *dev = ctx;
|
||||
struct rkcif_device *cif_dev = dev_get_drvdata(dev);
|
||||
|
||||
if (using_pingpong)
|
||||
rkcif_irq_pingpong(cif_dev);
|
||||
else
|
||||
rkcif_irq_oneframe(cif_dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void rkcif_disable_sys_clk(struct rkcif_device *cif_dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = cif_dev->clk_size - 1; i >= 0; i--)
|
||||
clk_disable_unprepare(cif_dev->clks[i]);
|
||||
}
|
||||
|
||||
static int rkcif_enable_sys_clk(struct rkcif_device *cif_dev)
|
||||
{
|
||||
int i, ret = -EINVAL;
|
||||
|
||||
for (i = 0; i < cif_dev->clk_size; i++) {
|
||||
ret = clk_prepare_enable(cif_dev->clks[i]);
|
||||
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
for (--i; i >= 0; --i)
|
||||
clk_disable_unprepare(cif_dev->clks[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rkcif_iommu_init(struct rkcif_device *cif_dev)
|
||||
{
|
||||
struct iommu_group *group;
|
||||
int ret;
|
||||
|
||||
cif_dev->domain = iommu_domain_alloc(&platform_bus_type);
|
||||
if (!cif_dev->domain)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = iommu_get_dma_cookie(cif_dev->domain);
|
||||
if (ret)
|
||||
goto err_free_domain;
|
||||
|
||||
group = iommu_group_get(cif_dev->dev);
|
||||
if (!group) {
|
||||
group = iommu_group_alloc();
|
||||
if (IS_ERR(group)) {
|
||||
ret = PTR_ERR(group);
|
||||
goto err_put_cookie;
|
||||
}
|
||||
ret = iommu_group_add_device(group, cif_dev->dev);
|
||||
iommu_group_put(group);
|
||||
if (ret)
|
||||
goto err_put_cookie;
|
||||
}
|
||||
iommu_group_put(group);
|
||||
|
||||
ret = iommu_attach_device(cif_dev->domain, cif_dev->dev);
|
||||
if (ret)
|
||||
goto err_put_cookie;
|
||||
if (!common_iommu_setup_dma_ops(cif_dev->dev, 0x10000000, SZ_2G,
|
||||
cif_dev->domain->ops)) {
|
||||
ret = -ENODEV;
|
||||
goto err_detach;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_detach:
|
||||
iommu_detach_device(cif_dev->domain, cif_dev->dev);
|
||||
err_put_cookie:
|
||||
iommu_put_dma_cookie(cif_dev->domain);
|
||||
err_free_domain:
|
||||
iommu_domain_free(cif_dev->domain);
|
||||
|
||||
dev_err(cif_dev->dev, "Failed to setup IOMMU, ret(%d)\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rkcif_iommu_cleanup(struct rkcif_device *cif_dev)
|
||||
{
|
||||
iommu_detach_device(cif_dev->domain, cif_dev->dev);
|
||||
iommu_put_dma_cookie(cif_dev->domain);
|
||||
iommu_domain_free(cif_dev->domain);
|
||||
}
|
||||
|
||||
static inline bool is_iommu_enable(struct device *dev)
|
||||
{
|
||||
struct device_node *iommu;
|
||||
|
||||
iommu = of_parse_phandle(dev->of_node, "iommus", 0);
|
||||
if (!iommu) {
|
||||
dev_info(dev, "no iommu attached, using non-iommu buffers\n");
|
||||
return false;
|
||||
} else if (!of_device_is_available(iommu)) {
|
||||
dev_info(dev, "iommu is disabled, using non-iommu buffers\n");
|
||||
of_node_put(iommu);
|
||||
return false;
|
||||
}
|
||||
of_node_put(iommu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void rkcif_soft_reset(struct rkcif_device *cif_dev)
|
||||
{
|
||||
if (cif_dev->iommu_en)
|
||||
rkcif_iommu_cleanup(cif_dev);
|
||||
|
||||
reset_control_assert(cif_dev->cif_rst);
|
||||
udelay(5);
|
||||
reset_control_deassert(cif_dev->cif_rst);
|
||||
|
||||
if (cif_dev->iommu_en)
|
||||
rkcif_iommu_init(cif_dev);
|
||||
}
|
||||
|
||||
static int rkcif_plat_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct v4l2_device *v4l2_dev;
|
||||
struct rkcif_device *cif_dev;
|
||||
const struct cif_match_data *clk_data;
|
||||
struct resource *res;
|
||||
int i, ret, irq;
|
||||
|
||||
match = of_match_node(rkcif_plat_of_match, node);
|
||||
cif_dev = devm_kzalloc(dev, sizeof(*cif_dev), GFP_KERNEL);
|
||||
if (!cif_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, cif_dev);
|
||||
cif_dev->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
cif_dev->base_addr = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(cif_dev->base_addr))
|
||||
return PTR_ERR(cif_dev->base_addr);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ret = devm_request_irq(dev, irq, rkcif_irq_handler, IRQF_SHARED,
|
||||
dev_driver_string(dev), dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "request irq failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cif_dev->irq = irq;
|
||||
clk_data = match->data;
|
||||
for (i = 0; i < clk_data->size; i++) {
|
||||
struct clk *clk = devm_clk_get(dev, clk_data->clks[i]);
|
||||
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "failed to get %s\n", clk_data->clks[i]);
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
cif_dev->clks[i] = clk;
|
||||
}
|
||||
cif_dev->clk_size = clk_data->size;
|
||||
|
||||
cif_dev->cif_rst = devm_reset_control_get(dev, "rst_cif");
|
||||
if (IS_ERR(cif_dev->cif_rst)) {
|
||||
dev_err(dev, "failed to get core cif reset controller\n");
|
||||
return PTR_ERR(cif_dev->cif_rst);
|
||||
}
|
||||
|
||||
rkcif_stream_init(cif_dev);
|
||||
|
||||
strlcpy(cif_dev->media_dev.model, "rkcif",
|
||||
sizeof(cif_dev->media_dev.model));
|
||||
cif_dev->media_dev.dev = &pdev->dev;
|
||||
v4l2_dev = &cif_dev->v4l2_dev;
|
||||
v4l2_dev->mdev = &cif_dev->media_dev;
|
||||
strlcpy(v4l2_dev->name, "rkcif", sizeof(v4l2_dev->name));
|
||||
v4l2_ctrl_handler_init(&cif_dev->ctrl_handler, 8);
|
||||
v4l2_dev->ctrl_handler = &cif_dev->ctrl_handler;
|
||||
|
||||
ret = v4l2_device_register(cif_dev->dev, &cif_dev->v4l2_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = media_device_register(&cif_dev->media_dev);
|
||||
if (ret < 0) {
|
||||
v4l2_err(v4l2_dev, "Failed to register media device: %d\n",
|
||||
ret);
|
||||
goto err_unreg_v4l2_dev;
|
||||
}
|
||||
|
||||
/* create & register platefom subdev (from of_node) */
|
||||
ret = rkcif_register_platform_subdevs(cif_dev);
|
||||
if (ret < 0)
|
||||
goto err_unreg_media_dev;
|
||||
|
||||
cif_dev->iommu_en = is_iommu_enable(dev);
|
||||
if (cif_dev->iommu_en)
|
||||
rkcif_iommu_init(cif_dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unreg_media_dev:
|
||||
media_device_unregister(&cif_dev->media_dev);
|
||||
err_unreg_v4l2_dev:
|
||||
v4l2_device_unregister(&cif_dev->v4l2_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rkcif_plat_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rkcif_device *cif_dev = platform_get_drvdata(pdev);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
if (cif_dev->iommu_en)
|
||||
rkcif_iommu_cleanup(cif_dev);
|
||||
|
||||
media_device_unregister(&cif_dev->media_dev);
|
||||
v4l2_device_unregister(&cif_dev->v4l2_dev);
|
||||
rkcif_unregister_stream_vdev(cif_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused rkcif_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct rkcif_device *cif_dev = dev_get_drvdata(dev);
|
||||
|
||||
rkcif_disable_sys_clk(cif_dev);
|
||||
|
||||
return pinctrl_pm_select_sleep_state(dev);
|
||||
}
|
||||
|
||||
static int __maybe_unused rkcif_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct rkcif_device *cif_dev = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = pinctrl_pm_select_default_state(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
rkcif_enable_sys_clk(cif_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops rkcif_plat_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(rkcif_runtime_suspend, rkcif_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver rkcif_plat_drv = {
|
||||
.driver = {
|
||||
.name = CIF_DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(rkcif_plat_of_match),
|
||||
.pm = &rkcif_plat_pm_ops,
|
||||
},
|
||||
.probe = rkcif_plat_probe,
|
||||
.remove = rkcif_plat_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(rkcif_plat_drv);
|
||||
MODULE_AUTHOR("Rockchip Camera/ISP team");
|
||||
MODULE_DESCRIPTION("Rockchip CIF platform driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
177
drivers/media/platform/rockchip/cif/dev.h
Normal file
177
drivers/media/platform/rockchip/cif/dev.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Rockchip CIF Driver
|
||||
*
|
||||
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#ifndef _RKCIF_DEV_H
|
||||
#define _RKCIF_DEV_H
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <media/media-device.h>
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
|
||||
#define CIF_DRIVER_NAME "rkcif"
|
||||
|
||||
#define RKCIF_MAX_BUS_CLK 8
|
||||
#define RKCIF_MAX_SENSOR 2
|
||||
|
||||
#define RKCIF_DEFAULT_WIDTH 640
|
||||
#define RKCIF_DEFAULT_HEIGHT 480
|
||||
|
||||
#define write_cif_reg(base, addr, val) writel(val, (addr) + (base))
|
||||
#define read_cif_reg(base, addr) readl((addr) + (base))
|
||||
|
||||
enum rkcif_state {
|
||||
RKCIF_STATE_DISABLED,
|
||||
RKCIF_STATE_READY,
|
||||
RKCIF_STATE_STREAMING
|
||||
};
|
||||
|
||||
struct rkcif_buffer {
|
||||
struct vb2_v4l2_buffer vb;
|
||||
struct list_head queue;
|
||||
union {
|
||||
u32 buff_addr[VIDEO_MAX_PLANES];
|
||||
void *vaddr[VIDEO_MAX_PLANES];
|
||||
};
|
||||
};
|
||||
|
||||
struct rkcif_dummy_buffer {
|
||||
void *vaddr;
|
||||
dma_addr_t dma_addr;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
extern int rkcif_debug;
|
||||
|
||||
static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
|
||||
{
|
||||
return container_of(vb, struct rkcif_buffer, vb);
|
||||
}
|
||||
|
||||
/*
|
||||
* struct rkcif_sensor_info - Sensor infomations
|
||||
* @mbus: media bus configuration
|
||||
*/
|
||||
struct rkcif_sensor_info {
|
||||
struct v4l2_subdev *sd;
|
||||
struct v4l2_mbus_config mbus;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct cif_output_fmt - The output format
|
||||
*
|
||||
* @fourcc: pixel format in fourcc
|
||||
* @cplanes: number of colour planes
|
||||
* @fmt_val: the fmt val corresponding to CIF_FOR register
|
||||
* @bpp: bits per pixel for each cplanes
|
||||
*/
|
||||
struct cif_output_fmt {
|
||||
u32 fourcc;
|
||||
u8 cplanes;
|
||||
u8 mplanes;
|
||||
u32 fmt_val;
|
||||
u8 bpp[VIDEO_MAX_PLANES];
|
||||
};
|
||||
|
||||
enum cif_fmt_type {
|
||||
CIF_FMT_TYPE_YUV = 0,
|
||||
CIF_FMT_TYPE_RAW,
|
||||
};
|
||||
|
||||
/*
|
||||
* struct cif_input_fmt - The input mbus format from sensor
|
||||
*
|
||||
* @mbus_code: mbus format
|
||||
* @fmt_val: the fmt val corresponding to CIF_FOR register
|
||||
*/
|
||||
struct cif_input_fmt {
|
||||
u32 mbus_code;
|
||||
u32 fmt_val;
|
||||
enum cif_fmt_type fmt_type;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct rkcif_stream - Stream states TODO
|
||||
*
|
||||
* @vbq_lock: lock to protect buf_queue
|
||||
* @buf_queue: queued buffer list
|
||||
* @dummy_buf: dummy space to store dropped data
|
||||
*
|
||||
* rkcif use shadowsock registers, so it need two buffer at a time
|
||||
* @curr_buf: the buffer used for current frame
|
||||
*/
|
||||
struct rkcif_stream {
|
||||
struct rkcif_device *cifdev;
|
||||
enum rkcif_state state;
|
||||
bool stopping;
|
||||
wait_queue_head_t wq_stopped;
|
||||
int frame_idx;
|
||||
|
||||
/* lock between irq and buf_queue */
|
||||
spinlock_t vbq_lock;
|
||||
struct vb2_queue buf_queue;
|
||||
struct list_head buf_head;
|
||||
struct rkcif_dummy_buffer dummy_buf;
|
||||
struct rkcif_buffer *curr_buf;
|
||||
|
||||
/* vfd lock */
|
||||
struct mutex vlock;
|
||||
struct video_device vdev;
|
||||
/* TODO: pad for dvp and mipi separately? */
|
||||
struct media_pad pad;
|
||||
|
||||
const struct cif_output_fmt *cif_fmt_out;
|
||||
const struct cif_input_fmt *cif_fmt_in;
|
||||
struct v4l2_pix_format_mplane pixm;
|
||||
struct v4l2_rect crop;
|
||||
};
|
||||
|
||||
static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
|
||||
{
|
||||
return container_of(vdev, struct rkcif_stream, vdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* struct rkcif_device - ISP platform device
|
||||
* @base_addr: base register address
|
||||
* @active_sensor: sensor in-use, set when streaming on
|
||||
* @stream: capture video device
|
||||
*/
|
||||
struct rkcif_device {
|
||||
struct device *dev;
|
||||
int irq;
|
||||
void __iomem *base_addr;
|
||||
struct clk *clks[RKCIF_MAX_BUS_CLK];
|
||||
int clk_size;
|
||||
struct vb2_alloc_ctx *alloc_ctx;
|
||||
bool iommu_en;
|
||||
struct iommu_domain *domain;
|
||||
struct reset_control *cif_rst;
|
||||
|
||||
struct v4l2_device v4l2_dev;
|
||||
struct media_device media_dev;
|
||||
struct v4l2_ctrl_handler ctrl_handler;
|
||||
struct v4l2_async_notifier notifier;
|
||||
|
||||
struct rkcif_sensor_info sensors[RKCIF_MAX_SENSOR];
|
||||
u32 num_sensors;
|
||||
struct rkcif_sensor_info *active_sensor;
|
||||
|
||||
struct rkcif_stream stream;
|
||||
};
|
||||
|
||||
void rkcif_unregister_stream_vdev(struct rkcif_device *dev);
|
||||
int rkcif_register_stream_vdev(struct rkcif_device *dev);
|
||||
void rkcif_stream_init(struct rkcif_device *dev);
|
||||
|
||||
void rkcif_irq_oneframe(struct rkcif_device *cif_dev);
|
||||
void rkcif_irq_pingpong(struct rkcif_device *cif_dev);
|
||||
void rkcif_soft_reset(struct rkcif_device *cif_dev);
|
||||
|
||||
#endif
|
||||
122
drivers/media/platform/rockchip/cif/regs.h
Normal file
122
drivers/media/platform/rockchip/cif/regs.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Rockchip CIF Driver
|
||||
*
|
||||
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#ifndef _RKCIF_REGS_H
|
||||
#define _RKCIF_REGS_H
|
||||
|
||||
/* CIF Reg Offset */
|
||||
#define CIF_CTRL 0x00
|
||||
#define CIF_INTEN 0x04
|
||||
#define CIF_INTSTAT 0x08
|
||||
#define CIF_FOR 0x0c
|
||||
#define CIF_LINE_NUM_ADDR 0x10
|
||||
#define CIF_FRM0_ADDR_Y 0x14
|
||||
#define CIF_FRM0_ADDR_UV 0x18
|
||||
#define CIF_FRM1_ADDR_Y 0x1c
|
||||
#define CIF_FRM1_ADDR_UV 0x20
|
||||
#define CIF_VIR_LINE_WIDTH 0x24
|
||||
#define CIF_SET_SIZE 0x28
|
||||
#define CIF_SCM_ADDR_Y 0x2c
|
||||
#define CIF_SCM_ADDR_U 0x30
|
||||
#define CIF_SCM_ADDR_V 0x34
|
||||
#define CIF_WB_UP_FILTER 0x38
|
||||
#define CIF_WB_LOW_FILTER 0x3c
|
||||
#define CIF_WBC_CNT 0x40
|
||||
#define CIF_CROP 0x44
|
||||
#define CIF_SCL_CTRL 0x48
|
||||
#define CIF_SCL_DST 0x4c
|
||||
#define CIF_SCL_FCT 0x50
|
||||
#define CIF_SCL_VALID_NUM 0x54
|
||||
#define CIF_LINE_LOOP_CTR 0x58
|
||||
#define CIF_FRAME_STATUS 0x60
|
||||
#define CIF_CUR_DST 0x64
|
||||
#define CIF_LAST_LINE 0x68
|
||||
#define CIF_LAST_PIX 0x6c
|
||||
|
||||
/* The key register bit description */
|
||||
|
||||
/* CIF_CTRL Reg */
|
||||
#define DISABLE_CAPTURE (0x0 << 0)
|
||||
#define ENABLE_CAPTURE (0x1 << 0)
|
||||
#define MODE_ONEFRAME (0x0 << 1)
|
||||
#define MODE_PINGPONG (0x1 << 1)
|
||||
#define MODE_LINELOOP (0x2 << 1)
|
||||
#define AXI_BURST_16 (0xF << 12)
|
||||
|
||||
/* CIF_INTEN */
|
||||
#define INTEN_DISABLE (0x0 << 0)
|
||||
#define FRAME_END_EN (0x1 << 0)
|
||||
#define BUS_ERR_EN (0x1 << 6)
|
||||
#define SCL_ERR_EN (0x1 << 7)
|
||||
#define PST_INF_FRAME_END_EN (0x1 << 9)
|
||||
|
||||
/* CIF INTSTAT */
|
||||
#define INTSTAT_CLS (0x3FF)
|
||||
#define FRAME_END (0x01 << 0)
|
||||
#define PST_INF_FRAME_END (0x01 << 9)
|
||||
#define FRAME_END_CLR (0x01 << 0)
|
||||
#define PST_INF_FRAME_END_CLR (0x01 << 9)
|
||||
#define INTSTAT_ERR (0xFC)
|
||||
|
||||
/* FRAME STATUS */
|
||||
#define FRAME_STAT_CLS 0x00
|
||||
#define FRM0_STAT_CLS 0x20 /* write 0 to clear frame 0 */
|
||||
|
||||
/* CIF FORMAT */
|
||||
#define VSY_HIGH_ACTIVE (0x01 << 0)
|
||||
#define VSY_LOW_ACTIVE (0x00 << 0)
|
||||
#define HSY_LOW_ACTIVE (0x01 << 1)
|
||||
#define HSY_HIGH_ACTIVE (0x00 << 1)
|
||||
#define INPUT_MODE_YUV (0x00 << 2)
|
||||
#define INPUT_MODE_PAL (0x02 << 2)
|
||||
#define INPUT_MODE_NTSC (0x03 << 2)
|
||||
#define INPUT_MODE_RAW (0x04 << 2)
|
||||
#define INPUT_MODE_JPEG (0x05 << 2)
|
||||
#define INPUT_MODE_MIPI (0x06 << 2)
|
||||
#define YUV_INPUT_ORDER_UYVY (0x00 << 5)
|
||||
#define YUV_INPUT_ORDER_YVYU (0x01 << 5)
|
||||
#define YUV_INPUT_ORDER_VYUY (0x10 << 5)
|
||||
#define YUV_INPUT_ORDER_YUYV (0x03 << 5)
|
||||
#define YUV_INPUT_422 (0x00 << 7)
|
||||
#define YUV_INPUT_420 (0x01 << 7)
|
||||
#define INPUT_420_ORDER_EVEN (0x00 << 8)
|
||||
#define INPUT_420_ORDER_ODD (0x01 << 8)
|
||||
#define CCIR_INPUT_ORDER_ODD (0x00 << 9)
|
||||
#define CCIR_INPUT_ORDER_EVEN (0x01 << 9)
|
||||
#define RAW_DATA_WIDTH_8 (0x00 << 11)
|
||||
#define RAW_DATA_WIDTH_10 (0x01 << 11)
|
||||
#define RAW_DATA_WIDTH_12 (0x02 << 11)
|
||||
#define YUV_OUTPUT_422 (0x00 << 16)
|
||||
#define YUV_OUTPUT_420 (0x01 << 16)
|
||||
#define OUTPUT_420_ORDER_EVEN (0x00 << 17)
|
||||
#define OUTPUT_420_ORDER_ODD (0x01 << 17)
|
||||
#define RAWD_DATA_LITTLE_ENDIAN (0x00 << 18)
|
||||
#define RAWD_DATA_BIG_ENDIAN (0x01 << 18)
|
||||
#define UV_STORAGE_ORDER_UVUV (0x00 << 19)
|
||||
#define UV_STORAGE_ORDER_VUVU (0x01 << 19)
|
||||
|
||||
/* CIF_SCL_CTRL */
|
||||
#define ENABLE_SCL_DOWN (0x01 << 0)
|
||||
#define DISABLE_SCL_DOWN (0x00 << 0)
|
||||
#define ENABLE_SCL_UP (0x01 << 1)
|
||||
#define DISABLE_SCL_UP (0x00 << 1)
|
||||
#define ENABLE_YUV_16BIT_BYPASS (0x01 << 4)
|
||||
#define DISABLE_YUV_16BIT_BYPASS (0x00 << 4)
|
||||
#define ENABLE_RAW_16BIT_BYPASS (0x01 << 5)
|
||||
#define DISABLE_RAW_16BIT_BYPASS (0x00 << 5)
|
||||
#define ENABLE_32BIT_BYPASS (0x01 << 6)
|
||||
#define DISABLE_32BIT_BYPASS (0x00 << 6)
|
||||
|
||||
/* CIF_INTSTAT */
|
||||
#define CIF_F0_READY (0x01 << 0)
|
||||
#define CIF_F1_READY (0x01 << 1)
|
||||
|
||||
/* CIF CROP */
|
||||
#define CIF_CROP_Y_SHIFT 16
|
||||
#define CIF_CROP_X_SHIFT 0
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user