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:
Shunqian Zheng
2018-06-28 15:05:17 +08:00
committed by Tao Huang
parent 9794d0994a
commit 1052901f87
8 changed files with 1785 additions and 0 deletions

View File

@@ -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

View File

@@ -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/

View 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.

View 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

View 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 */
}

View 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");

View 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

View 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