diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig index 89bb3fa75c1d..903fd2e36136 100644 --- a/drivers/media/platform/rockchip/Kconfig +++ b/drivers/media/platform/rockchip/Kconfig @@ -3,6 +3,7 @@ comment "Rockchip media platform drivers" source "drivers/media/platform/rockchip/cif/Kconfig" +source "drivers/media/platform/rockchip/flexbus_cif/Kconfig" source "drivers/media/platform/rockchip/isp1/Kconfig" source "drivers/media/platform/rockchip/isp/Kconfig" source "drivers/media/platform/rockchip/ispp/Kconfig" diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile index c46a8959ce8a..0bdd933719ef 100644 --- a/drivers/media/platform/rockchip/Makefile +++ b/drivers/media/platform/rockchip/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += cif/ +obj-y += flexbus_cif/ obj-y += isp1/ obj-y += isp/ obj-y += ispp/ diff --git a/drivers/media/platform/rockchip/flexbus_cif/Kconfig b/drivers/media/platform/rockchip/flexbus_cif/Kconfig new file mode 100644 index 000000000000..9c32fd94c959 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/Kconfig @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 Rockchip Electronics Co., Ltd. + +config ROCKCHIP_FLEXBUS_CIF + tristate "Rockchip Flexbus CIF Camera Interface driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on MEDIA_CAMERA_SUPPORT + select MFD_ROCKCHIP_FLEXBUS + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_CMA_SG + select VIDEOBUF2_VMALLOC + select V4L2_FWNODE + default n + help + Support for flexbus cif on the rockchip SoCs + +choice + prompt "rockchip flexbuf-cif device dummy buffer choice" + depends on ROCKCHIP_FLEXBUS_CIF + default ROCKCHIP_FLEXBUS_CIF_USE_DUMMY_BUF + help + choice to use dummy buffer or not + +config ROCKCHIP_FLEXBUS_CIF_USE_DUMMY_BUF + bool "flexbuf-cif use dummy buffer" + help + select to use dummy buffer + +config ROCKCHIP_FLEXBUS_CIF_USE_NONE_DUMMY_BUF + bool "flexbuf-cif not use dummy buffer" + help + select not to use dummy buffer + +endchoice + diff --git a/drivers/media/platform/rockchip/flexbus_cif/Makefile b/drivers/media/platform/rockchip/flexbus_cif/Makefile new file mode 100644 index 000000000000..70ebcb95de1a --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 Rockchip Electronics Co., Ltd. + +obj-$(CONFIG_ROCKCHIP_FLEXBUS_CIF) += flexbus_cif.o +flexbus_cif-objs += dev.o \ + capture.o \ + procfs.o \ + common.o diff --git a/drivers/media/platform/rockchip/flexbus_cif/capture.c b/drivers/media/platform/rockchip/flexbus_cif/capture.c new file mode 100644 index 000000000000..142198cf0962 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/capture.c @@ -0,0 +1,2333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dev.h" +#include "common.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 OUTPUT_STEP_WISE 8 + +#define FLEXBUS_CIF_PLANE_Y 0 +#define FLEXBUS_CIF_PLANE_CBCR 1 +#define FLEXBUS_CIF_MAX_PLANE 3 + +#define STREAM_PAD_SINK 0 +#define STREAM_PAD_SOURCE 1 + +#define CIF_TIMEOUT_FRAME_NUM (2) + +#define CIF_PCLK_DUAL_EDGE (V4L2_MBUS_PCLK_SAMPLE_RISING |\ + V4L2_MBUS_PCLK_SAMPLE_FALLING) + +/* + * Round up height when allocate memory so that Rockchip encoder can + * use DMA buffer directly, though this may waste a bit of memory. + */ +#define MEMORY_ALIGN_ROUND_UP_HEIGHT 16 + +/* 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: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + *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_YUYV, + .cplanes = 2, + .mplanes = 1, + .bpp = { 8, 16 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_YUYV, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .cplanes = 2, + .mplanes = 1, + .bpp = { 8, 16 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_YVYU, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .cplanes = 2, + .mplanes = 1, + .bpp = { 8, 16 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_UYVY, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .cplanes = 2, + .mplanes = 1, + .bpp = { 8, 16 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_VYUY, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .cplanes = 1, + .mplanes = 1, + .bpp = { 24 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_YVYU, + }, + { + .fourcc = V4L2_PIX_FMT_BGR24, + .cplanes = 1, + .mplanes = 1, + .bpp = { 24 }, + .cif_yuv_order = CIF_OUTPUT_ORDER_YVYU, + } +}; + +static const struct cif_input_fmt in_fmts[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_YUYV, + .field = V4L2_FIELD_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_YUYV, + .field = V4L2_FIELD_INTERLACED, + }, { + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_YVYU, + .field = V4L2_FIELD_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_YVYU, + .field = V4L2_FIELD_INTERLACED, + }, { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_UYVY, + .field = V4L2_FIELD_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_UYVY, + .field = V4L2_FIELD_INTERLACED, + }, { + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_VYUY, + .field = V4L2_FIELD_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .cif_yuv_order = CIF_INPUT_ORDER_VYUY, + .field = V4L2_FIELD_INTERLACED, + } +}; + +static int flexbus_cif_output_fmt_check(struct flexbus_cif_stream *stream, + const struct cif_output_fmt *output_fmt) +{ + const struct cif_input_fmt *input_fmt = stream->cif_fmt_in; + int ret = -EINVAL; + + switch (input_fmt->mbus_code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + if (output_fmt->fourcc == V4L2_PIX_FMT_YUYV || + output_fmt->fourcc == V4L2_PIX_FMT_YVYU || + output_fmt->fourcc == V4L2_PIX_FMT_UYVY || + output_fmt->fourcc == V4L2_PIX_FMT_VYUY || + output_fmt->fourcc == V4L2_PIX_FMT_RGB24 || + output_fmt->fourcc == V4L2_PIX_FMT_BGR24) + ret = 0; + break; + default: + break; + } + if (ret) + v4l2_err(&stream->cif_dev->v4l2_dev, + "input mbus_code 0x%x, can't transform to %c%c%c%c\n", + input_fmt->mbus_code, + output_fmt->fourcc & 0xff, + (output_fmt->fourcc >> 8) & 0xff, + (output_fmt->fourcc >> 16) & 0xff, + (output_fmt->fourcc >> 24) & 0xff); + return ret; +} + +static struct v4l2_subdev *get_remote_sensor(struct flexbus_cif_stream *stream, u16 *index) +{ + struct media_pad *local, *remote; + struct media_entity *sensor_me; + struct v4l2_subdev *sub = NULL; + + local = &stream->vnode.vdev.entity.pads[0]; + if (!local) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: video pad[0] is null\n", __func__); + return NULL; + } + + remote = media_pad_remote_pad_first(local); + if (!remote) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: remote pad is null\n", __func__); + return NULL; + } + + if (index) + *index = remote->index; + + sensor_me = remote->entity; + + sub = media_entity_to_v4l2_subdev(sensor_me); + + return sub; + +} + +static void get_remote_terminal_sensor(struct flexbus_cif_stream *stream, + struct v4l2_subdev **sensor_sd) +{ + struct media_graph graph; + struct media_entity *entity = &stream->vnode.vdev.entity; + struct media_device *mdev = entity->graph_obj.mdev; + int ret; + + /* Walk the graph to locate sensor nodes. */ + mutex_lock(&mdev->graph_mutex); + ret = media_graph_walk_init(&graph, mdev); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + *sensor_sd = NULL; + return; + } + + media_graph_walk_start(&graph, entity); + while ((entity = media_graph_walk_next(&graph))) { + if (entity->function == MEDIA_ENT_F_CAM_SENSOR) + break; + } + mutex_unlock(&mdev->graph_mutex); + media_graph_walk_cleanup(&graph); + + if (entity) + *sensor_sd = media_entity_to_v4l2_subdev(entity); + else + *sensor_sd = NULL; +} + +static struct flexbus_cif_sensor_info *sd_to_sensor(struct flexbus_cif_device *dev, + struct v4l2_subdev *sd) +{ + u32 i; + + for (i = 0; i < dev->num_sensors; ++i) + if (dev->sensors[i].sd == sd) + return &dev->sensors[i]; + + return NULL; +} + +static const struct +cif_input_fmt *get_input_fmt(struct v4l2_subdev *sd, struct v4l2_rect *rect, + u32 pad_id) +{ + struct v4l2_subdev_format fmt; + int ret; + u32 i; + + fmt.pad = 0; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.reserved[0] = 0; + fmt.format.field = V4L2_FIELD_NONE; + 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, flexbus_cif_debug, sd->v4l2_dev, + "remote fmt: mbus code:0x%x, size:%dx%d, field: %d\n", + fmt.format.code, fmt.format.width, + fmt.format.height, fmt.format.field); + 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 && + fmt.format.field == in_fmts[i].field) + 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 = FLEXBUS_CIF_DEFAULT_WIDTH; + rect->height = FLEXBUS_CIF_DEFAULT_HEIGHT; + + return NULL; +} + +static const struct +cif_output_fmt *flexbus_cif_find_output_fmt(struct flexbus_cif_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; +} + +static void flexbus_cif_assign_new_buffer_init(struct flexbus_cif_stream *stream, + int channel_id) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + u32 frm0_addr; + u32 frm1_addr; + unsigned long flags; + struct flexbus_cif_dummy_buffer *dummy_buf = &dev->dummy_buf; + + frm0_addr = FLEXBUS_DMA_DST_ADDR0; + frm1_addr = FLEXBUS_DMA_DST_ADDR1; + + spin_lock_irqsave(&stream->vbq_lock, flags); + + if (!stream->curr_buf) { + if (!list_empty(&stream->buf_head)) { + stream->curr_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, + queue); + list_del(&stream->curr_buf->queue); + } + } + + if (stream->curr_buf) { + flexbus_cif_write_register(dev, frm0_addr, + stream->curr_buf->buff_addr[FLEXBUS_CIF_PLANE_Y] >> 2); + } else { + if (dummy_buf->vaddr) { + flexbus_cif_write_register(dev, frm0_addr, dummy_buf->dma_addr); + } else { + if (stream->lack_buf_cnt < 2) + stream->lack_buf_cnt++; + } + } + + + if (!stream->next_buf) { + if (!list_empty(&stream->buf_head)) { + stream->next_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + list_del(&stream->next_buf->queue); + } + } + + if (stream->next_buf) { + flexbus_cif_write_register(dev, frm1_addr, + stream->next_buf->buff_addr[FLEXBUS_CIF_PLANE_Y] >> 2); + } else { + if (dummy_buf->vaddr) { + flexbus_cif_write_register(dev, frm1_addr, dummy_buf->dma_addr); + } else { + if (stream->curr_buf) { + stream->next_buf = stream->curr_buf; + flexbus_cif_write_register(dev, frm1_addr, + stream->next_buf->buff_addr[FLEXBUS_CIF_PLANE_Y] >> 2); + } + if (stream->lack_buf_cnt < 2) + stream->lack_buf_cnt++; + } + } + spin_unlock_irqrestore(&stream->vbq_lock, flags); + +} + +static int flexbus_cif_assign_new_buffer_update(struct flexbus_cif_stream *stream, + int channel_id) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_dummy_buffer *dummy_buf = &dev->dummy_buf; + struct flexbus_cif_buffer *buffer = NULL; + u32 frm_addr; + int ret = 0; + unsigned long flags; + + frm_addr = stream->frame_phase & CIF_CSI_FRAME0_READY ? + FLEXBUS_DMA_DST_ADDR0 : FLEXBUS_DMA_DST_ADDR1; + + spin_lock_irqsave(&stream->vbq_lock, flags); + if (!list_empty(&stream->buf_head)) { + if (stream->frame_phase == CIF_CSI_FRAME0_READY) { + if (!stream->curr_buf) + ret = -EINVAL; + stream->curr_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + if (stream->curr_buf) { + list_del(&stream->curr_buf->queue); + buffer = stream->curr_buf; + v4l2_dbg(3, flexbus_cif_debug, &dev->v4l2_dev, + "stream[%d] update curr_buf 0x%x\n", + stream->id, buffer->buff_addr[0]); + } + } else if (stream->frame_phase == CIF_CSI_FRAME1_READY) { + if (!stream->next_buf) + ret = -EINVAL; + stream->next_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + if (stream->next_buf) { + list_del(&stream->next_buf->queue); + buffer = stream->next_buf; + v4l2_dbg(3, flexbus_cif_debug, &dev->v4l2_dev, + "stream[%d] update next_buf 0x%x\n", + stream->id, buffer->buff_addr[0]); + } + } + } else { + buffer = NULL; + if (dummy_buf->vaddr) { + if (stream->frame_phase == CIF_CSI_FRAME0_READY) { + stream->curr_buf = NULL; + } else if (stream->frame_phase == CIF_CSI_FRAME1_READY) { + if (stream->cif_fmt_in->field == V4L2_FIELD_INTERLACED) { + stream->next_buf = stream->curr_buf; + buffer = stream->next_buf; + } else { + stream->next_buf = NULL; + } + } + } else if (stream->curr_buf && stream->next_buf && + stream->curr_buf != stream->next_buf) { + if (stream->frame_phase == CIF_CSI_FRAME0_READY) { + stream->curr_buf = stream->next_buf; + buffer = stream->next_buf; + } else if (stream->frame_phase == CIF_CSI_FRAME1_READY) { + stream->next_buf = stream->curr_buf; + buffer = stream->curr_buf; + } + if (stream->lack_buf_cnt < 2) + stream->lack_buf_cnt++; + } else { + stream->curr_buf = NULL; + stream->next_buf = NULL; + if (stream->lack_buf_cnt < 2) + stream->lack_buf_cnt++; + } + } + stream->frame_phase_cache = stream->frame_phase; + + if (buffer) { + flexbus_cif_write_register(dev, frm_addr, + buffer->buff_addr[FLEXBUS_CIF_PLANE_Y] >> 2); + } else { + if (dummy_buf->vaddr) { + flexbus_cif_write_register(dev, frm_addr, dummy_buf->dma_addr >> 2); + dev->err_state |= (FLEXBUS_CIF_ERR_ID0_NOT_BUF << stream->id); + dev->irq_stats.not_active_buf_cnt[stream->id]++; + } else { + ret = -EINVAL; + stream->curr_buf = NULL; + stream->next_buf = NULL; + dev->err_state |= (FLEXBUS_CIF_ERR_ID0_NOT_BUF << stream->id); + dev->irq_stats.not_active_buf_cnt[stream->id]++; + } + } + spin_unlock_irqrestore(&stream->vbq_lock, flags); + return ret; +} + +static int flexbus_cif_assign_new_buffer_pingpong(struct flexbus_cif_stream *stream, + int init, int channel_id) +{ + int ret = 0; + + if (init) + flexbus_cif_assign_new_buffer_init(stream, channel_id); + else + ret = flexbus_cif_assign_new_buffer_update(stream, channel_id); + return ret; +} + +static void flexbus_cif_stream_stop(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_device *cif_dev = stream->cif_dev; + + if (atomic_read(&cif_dev->pipe.stream_cnt) == 1) + flexbus_cif_write_register(cif_dev, FLEXBUS_ENR, (BIT(1) << 16)); + + stream->state = FLEXBUS_CIF_STATE_READY; + +} + +static int flexbus_cif_queue_setup(struct vb2_queue *queue, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct flexbus_cif_stream *stream = queue->drv_priv; + struct flexbus_cif_device *dev = stream->cif_dev; + const struct v4l2_pix_format_mplane *pixm = NULL; + const struct cif_output_fmt *cif_fmt; + u32 i, height; + + pixm = &stream->pixm; + cif_fmt = stream->cif_fmt_out; + + *num_planes = cif_fmt->mplanes; + + if (stream->crop_enable) + height = stream->crop[CROP_SRC_ACT].height; + else + height = pixm->height; + + for (i = 0; i < cif_fmt->mplanes; i++) { + const struct v4l2_plane_pix_format *plane_fmt; + int h = round_up(height, MEMORY_ALIGN_ROUND_UP_HEIGHT); + + plane_fmt = &pixm->plane_fmt[i]; + sizes[i] = plane_fmt->sizeimage / height * h; + } + + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "%s count %d, size %d\n", + v4l2_type_names[queue->type], *num_buffers, sizes[0]); + + return 0; +} + +static void flexbus_cif_check_buffer_update_pingpong(struct flexbus_cif_stream *stream, + int channel_id) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_buffer *buffer = NULL; + u32 frm_addr = 0; + u32 frm0_addr = 0; + u32 frm1_addr = 0; + unsigned long flags; + int frame_phase = 0; + bool is_dual_update_buf = false; + + spin_lock_irqsave(&stream->vbq_lock, flags); + if (stream->state == FLEXBUS_CIF_STATE_STREAMING && + (stream->curr_buf == NULL || + stream->next_buf == NULL)) { + frame_phase = stream->frame_phase_cache; + frm0_addr = FLEXBUS_DMA_DST_ADDR0; + frm1_addr = FLEXBUS_DMA_DST_ADDR1; + if (frame_phase & CIF_CSI_FRAME0_READY) + frm_addr = frm0_addr; + else + frm_addr = frm1_addr; + if (stream->curr_buf == NULL && stream->next_buf == NULL) + is_dual_update_buf = true; + if (!list_empty(&stream->buf_head)) { + if (frame_phase == CIF_CSI_FRAME0_READY) { + stream->curr_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + if (stream->curr_buf) { + list_del(&stream->curr_buf->queue); + buffer = stream->curr_buf; + } + if (buffer && is_dual_update_buf) + stream->next_buf = buffer; + } else if (frame_phase == CIF_CSI_FRAME1_READY) { + stream->next_buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + if (stream->next_buf) { + list_del(&stream->next_buf->queue); + buffer = stream->next_buf; + } + if (buffer && is_dual_update_buf) + stream->curr_buf = buffer; + } + } else { + v4l2_info(&dev->v4l2_dev, "%s %d\n", __func__, __LINE__); + } + if (buffer) { + if (is_dual_update_buf) { + flexbus_cif_write_register(dev, frm0_addr, + buffer->buff_addr[FLEXBUS_CIF_PLANE_Y]); + flexbus_cif_write_register(dev, frm1_addr, + buffer->buff_addr[FLEXBUS_CIF_PLANE_Y]); + } else { + flexbus_cif_write_register(dev, frm_addr, + buffer->buff_addr[FLEXBUS_CIF_PLANE_Y]); + } + } + v4l2_dbg(3, flexbus_cif_debug, &stream->cif_dev->v4l2_dev, + "%s, stream[%d] update buffer, frame_phase %d, lack_buf_cnt %d\n", + __func__, stream->id, frame_phase, + stream->lack_buf_cnt); + if (stream->lack_buf_cnt) + stream->lack_buf_cnt--; + } else { + v4l2_info(&dev->v4l2_dev, "%s %d, state %d, curr_buf %p, next_buf %p\n", + __func__, __LINE__, stream->state, stream->curr_buf, stream->next_buf); + } + spin_unlock_irqrestore(&stream->vbq_lock, flags); +} + +/* + * The vb2_buffer are stored in flexbus_cif_buffer, in order to unify + * mplane buffer and none-mplane buffer. + */ +static void flexbus_cif_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct flexbus_cif_buffer *cifbuf = to_flexbus_cif_buffer(vbuf); + struct vb2_queue *queue = vb->vb2_queue; + struct flexbus_cif_stream *stream = queue->drv_priv; + const struct cif_output_fmt *fmt = stream->cif_fmt_out; + unsigned long flags; + 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++) { + if (stream->cif_dev->is_dma_sg_ops) { + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, i); + + cifbuf->buff_addr[i] = sg_dma_address(sgt->sgl); + } else { + cifbuf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + } + } + + spin_lock_irqsave(&stream->vbq_lock, flags); + list_add_tail(&cifbuf->queue, &stream->buf_head); + spin_unlock_irqrestore(&stream->vbq_lock, flags); + if (stream->lack_buf_cnt) + flexbus_cif_check_buffer_update_pingpong(stream, stream->id); + v4l2_dbg(3, flexbus_cif_debug, &stream->cif_dev->v4l2_dev, + "stream[%d] buf queue, index: %d, dma_addr 0x%x\n", + stream->id, vb->index, cifbuf->buff_addr[0]); + atomic_inc(&stream->buf_cnt); +} + +static int flexbus_cif_create_dummy_buf(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_dummy_buffer *dummy_buf = &dev->dummy_buf; + int ret = 0; + + if (stream->cif_fmt_out->fourcc == V4L2_PIX_FMT_RGB24 || + stream->cif_fmt_out->fourcc == V4L2_PIX_FMT_BGR24) + dummy_buf->size = stream->pixm.width * stream->pixm.height * 3; + else + dummy_buf->size = stream->pixm.width * stream->pixm.height * 2; + + dummy_buf->is_need_vaddr = true; + dummy_buf->is_need_dbuf = true; + ret = flexbus_cif_alloc_buffer(dev, dummy_buf); + if (ret) { + 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 ret; +} + +static void flexbus_cif_destroy_dummy_buf(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_dummy_buffer *dummy_buf = &dev->dummy_buf; + + if (dummy_buf->vaddr) + flexbus_cif_free_buffer(dev, dummy_buf); + dummy_buf->dma_addr = 0; + dummy_buf->vaddr = NULL; +} + +static void flexbus_cif_stop_streaming(struct vb2_queue *queue) +{ + struct flexbus_cif_stream *stream = queue->drv_priv; + struct flexbus_cif_vdev_node *node = &stream->vnode; + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct flexbus_cif_buffer *buf = NULL; + int ret; + unsigned long flags; + + mutex_lock(&dev->stream_lock); + + v4l2_info(&dev->v4l2_dev, "stream[%d] start stopping\n", + stream->id); + + + stream->stopping = true; + ret = wait_event_timeout(stream->wq_stopped, + stream->state != FLEXBUS_CIF_STATE_STREAMING, + msecs_to_jiffies(500)); + if (!ret) { + flexbus_cif_stream_stop(stream); + stream->stopping = false; + } + + video_device_pipeline_stop(&node->vdev); + ret = dev->pipe.set_stream(&dev->pipe, false); + if (ret < 0) + v4l2_err(v4l2_dev, "pipeline stream-off failed error:%d\n", + ret); + /* release buffers */ + spin_lock_irqsave(&stream->vbq_lock, flags); + if (stream->curr_buf) + list_add_tail(&stream->curr_buf->queue, &stream->buf_head); + if (stream->next_buf && + stream->next_buf != stream->curr_buf) + list_add_tail(&stream->next_buf->queue, &stream->buf_head); + spin_unlock_irqrestore(&stream->vbq_lock, flags); + + stream->curr_buf = NULL; + stream->next_buf = NULL; + + list_for_each_entry(buf, &stream->buf_head, queue) { + v4l2_dbg(3, flexbus_cif_debug, &dev->v4l2_dev, + "stream[%d] buf return addr 0x%x\n", + stream->id, buf->buff_addr[0]); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + INIT_LIST_HEAD(&stream->buf_head); + while (!list_empty(&stream->vb_done_list)) { + buf = list_first_entry(&stream->vb_done_list, + struct flexbus_cif_buffer, queue); + if (buf) { + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + } + + atomic_set(&stream->buf_cnt, 0); + stream->lack_buf_cnt = 0; + + ret = dev->pipe.close(&dev->pipe); + if (ret < 0) + v4l2_err(v4l2_dev, "pipeline close failed error:%d\n", ret); + tasklet_disable(&stream->vb_done_tasklet); + if (dev->dummy_buf.vaddr) + flexbus_cif_destroy_dummy_buf(stream); + INIT_LIST_HEAD(&stream->vb_done_list); + v4l2_info(&dev->v4l2_dev, "stream[%d] stopping finished, dma_en 0x%x\n", stream->id, stream->dma_en); + mutex_unlock(&dev->stream_lock); + +} + +static u32 flexbus_cif_align_bits_per_pixel(struct flexbus_cif_stream *stream, + const struct cif_output_fmt *fmt, + int plane_index) +{ + u32 bpp = 0, i, cal = 0; + + if (fmt) { + switch (fmt->fourcc) { + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y16: + bpp = fmt->bpp[plane_index]; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + bpp = fmt->bpp[plane_index]; + break; + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_BGR666: + case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SRGGB10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SRGGB12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SBGGR16: + case V4L2_PIX_FMT_SGBRG16: + case V4L2_PIX_FMT_SGRBG16: + case V4L2_PIX_FMT_SRGGB16: + case V4l2_PIX_FMT_SPD16: + case V4l2_PIX_FMT_EBD8: + case V4L2_PIX_FMT_Y10: + case V4L2_PIX_FMT_Y12: + bpp = max(fmt->bpp[plane_index], (u8)CIF_RAW_STORED_BIT_WIDTH); + cal = CIF_RAW_STORED_BIT_WIDTH; + for (i = 1; i < 5; i++) { + if (i * cal >= bpp) { + bpp = i * cal; + break; + } + } + break; + default: + v4l2_err(&stream->cif_dev->v4l2_dev, "fourcc: %d is not supported!\n", + fmt->fourcc); + break; + } + } + + return bpp; +} + +static void flexbus_cif_sync_crop_info(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_subdev_selection input_sel; + int ret; + + if (dev->terminal_sensor.sd) { + input_sel.target = V4L2_SEL_TGT_CROP_BOUNDS; + input_sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; + input_sel.pad = 0; + ret = v4l2_subdev_call(dev->terminal_sensor.sd, + pad, get_selection, NULL, + &input_sel); + if (!ret) { + stream->crop[CROP_SRC_SENSOR] = input_sel.r; + stream->crop_enable = true; + stream->crop_mask |= CROP_SRC_SENSOR_MASK; + dev->terminal_sensor.selection = input_sel; + } else { + dev->terminal_sensor.selection.r = dev->terminal_sensor.raw_rect; + } + } + + if ((stream->crop_mask & 0x3) == (CROP_SRC_USR_MASK | CROP_SRC_SENSOR_MASK)) { + if (stream->crop[CROP_SRC_USR].left + stream->crop[CROP_SRC_USR].width > + stream->crop[CROP_SRC_SENSOR].width || + stream->crop[CROP_SRC_USR].top + stream->crop[CROP_SRC_USR].height > + stream->crop[CROP_SRC_SENSOR].height) + stream->crop[CROP_SRC_USR] = stream->crop[CROP_SRC_SENSOR]; + } + + if (stream->crop_mask & CROP_SRC_USR_MASK) { + stream->crop[CROP_SRC_ACT] = stream->crop[CROP_SRC_USR]; + if (stream->crop_mask & CROP_SRC_SENSOR_MASK) { + stream->crop[CROP_SRC_ACT].left = stream->crop[CROP_SRC_USR].left + + stream->crop[CROP_SRC_SENSOR].left; + stream->crop[CROP_SRC_ACT].top = stream->crop[CROP_SRC_USR].top + + stream->crop[CROP_SRC_SENSOR].top; + } + } else { + stream->crop[CROP_SRC_ACT] = stream->crop[CROP_SRC_SENSOR]; + } +} + +/**flexbus_cif_sanity_check_fmt - check fmt for setting + * @stream - the stream for setting + * @s_crop - the crop information + */ +static int flexbus_cif_sanity_check_fmt(struct flexbus_cif_stream *stream, + const struct v4l2_rect *s_crop) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct v4l2_rect input, *crop; + + if (dev->terminal_sensor.sd) { + stream->cif_fmt_in = get_input_fmt(dev->terminal_sensor.sd, + &input, stream->id); + if (!stream->cif_fmt_in) { + v4l2_err(v4l2_dev, "Input fmt is invalid\n"); + return -EINVAL; + } + } else { + v4l2_err(v4l2_dev, "terminal_sensor is invalid\n"); + return -EINVAL; + } + + if (s_crop) + crop = (struct v4l2_rect *)s_crop; + else + crop = &stream->crop[CROP_SRC_ACT]; + + 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 flexbus_cif_update_sensor_info(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_sensor_info *sensor, *terminal_sensor; + struct v4l2_subdev *sensor_sd; + int ret = 0; + + sensor_sd = get_remote_sensor(stream, NULL); + if (!sensor_sd) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: stream[%d] get remote sensor_sd failed!\n", + __func__, stream->id); + return -ENODEV; + } + + sensor = sd_to_sensor(stream->cif_dev, sensor_sd); + if (!sensor) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: stream[%d] get remote sensor failed!\n", + __func__, stream->id); + return -ENODEV; + } + ret = v4l2_subdev_call(sensor->sd, pad, get_mbus_config, + 0, &sensor->mbus); + if (ret && ret != -ENOIOCTLCMD) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: get remote %s mbus failed!\n", __func__, sensor->sd->name); + return ret; + } + + stream->cif_dev->active_sensor = sensor; + + terminal_sensor = &stream->cif_dev->terminal_sensor; + get_remote_terminal_sensor(stream, &terminal_sensor->sd); + if (terminal_sensor->sd) { + ret = v4l2_subdev_call(terminal_sensor->sd, pad, get_mbus_config, + 0, &terminal_sensor->mbus); + if (ret && ret != -ENOIOCTLCMD) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: get terminal %s mbus failed!\n", + __func__, terminal_sensor->sd->name); + return ret; + } + ret = v4l2_subdev_call(terminal_sensor->sd, video, + g_frame_interval, &terminal_sensor->fi); + if (ret) { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: get terminal %s g_frame_interval failed!\n", + __func__, terminal_sensor->sd->name); + return ret; + } + } else { + v4l2_err(&stream->cif_dev->v4l2_dev, + "%s: stream[%d] get remote terminal sensor failed!\n", + __func__, stream->id); + return -ENODEV; + } + + return ret; +} + +static void flexbus_cif_config_cif_clk_sampling_edge(struct flexbus_cif_device *dev, + enum flexbus_cif_clk_edge edge) +{ + u32 val = 0; + + if (edge == FLEXBUS_CIF_CLK_RISING) + dev->fb_dev->config->grf_config(dev->fb_dev, 1, false, false); + else + dev->fb_dev->config->grf_config(dev->fb_dev, 1, false, true); + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, + "%s %d, val %x\n", __func__, __LINE__, val); +} + +static int flexbus_cif_stream_start(struct flexbus_cif_stream *stream) +{ + u32 val, mbus_flags, href_pol, vsync_pol; + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_sensor_info *sensor_info; + struct v4l2_mbus_config *mbus; + struct flexbus_cif_cif_sof_subdev *sof_sd = &dev->cif_sof_subdev; + struct v4l2_rect rect = {0}; + + if (stream->state < FLEXBUS_CIF_STATE_STREAMING) { + stream->frame_idx = 0; + stream->buf_wake_up_cnt = 0; + stream->lack_buf_cnt = 0; + stream->frame_phase = 0; + stream->is_in_vblank = false; + stream->is_change_toisp = false; + } + sensor_info = dev->active_sensor; + mbus = &sensor_info->mbus; + + mbus_flags = mbus->bus.parallel.flags; + if (mbus_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + flexbus_cif_config_cif_clk_sampling_edge(dev, FLEXBUS_CIF_CLK_RISING); + else + flexbus_cif_config_cif_clk_sampling_edge(dev, FLEXBUS_CIF_CLK_FALLING); + 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; + flexbus_cif_write_register(dev, FLEXBUS_DVP_POL, href_pol | vsync_pol); + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "href_pol %d vsync_pol %d\n", + href_pol, vsync_pol); + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, + "mbus_flags %x V4L2_MBUS_PCLK_SAMPLE_RISING %lx\n", + mbus_flags, V4L2_MBUS_PCLK_SAMPLE_RISING); + + if (stream->crop_enable) { + rect.left = stream->crop[CROP_SRC_ACT].left * 2; + rect.top = stream->crop[CROP_SRC_ACT].top; + rect.width = stream->crop[CROP_SRC_ACT].width * 2; + rect.height = stream->crop[CROP_SRC_ACT].height; + } else { + rect.left = 0; + rect.top = 0; + rect.width = stream->pixm.width * 2; + rect.height = stream->pixm.height; + } + flexbus_cif_write_register(dev, FLEXBUS_DVP_CROP_SIZE, + rect.width | + (rect.height << 16)); + flexbus_cif_write_register(dev, FLEXBUS_DVP_CROP_START, + rect.top << CIF_CROP_Y_SHIFT | + rect.left); + flexbus_cif_assign_new_buffer_pingpong(stream, + FLEXBUS_CIF_YUV_ADDR_STATE_INIT, + stream->id); + flexbus_cif_write_register(dev, FLEXBUS_SLAVE_MODE, BIT(1) | BIT(0)); + val = flexbus_cif_read_register(dev, FLEXBUS_RX_CTL); + val &= ~FLEXBUS_DST_WAT_LVL_MASK; + val |= FLEXBUS_DFS_8BIT; + val |= FLEXBUS_CONTINUE_MODE; + val |= FLEXBUS_AUTOPAD; + flexbus_cif_write_register(dev, FLEXBUS_RX_CTL, val); + + flexbus_cif_write_register(dev, FLEXBUS_COM_CTL, 0x2); + flexbus_cif_write_register(dev, FLEXBUS_DMA_WR_OUTSTD, 0x1f); + + flexbus_cif_write_register(dev, FLEXBUS_DVP_ORDER, stream->cif_fmt_in->cif_yuv_order | + stream->cif_fmt_out->cif_yuv_order); + + flexbus_cif_write_register_or(dev, FLEXBUS_IMR, CIF_FIFO_OVERFLOW | + CIF_BANDWIDTH_LACK | + CIF_DMA_END | + CIF_SIZE_ERR); + flexbus_cif_write_register(dev, FLEXBUS_ENR, BIT(1) | ((BIT(1) | BIT(0)) << 16)); + atomic_set(&sof_sd->frm_sync_seq, 0); + stream->state = FLEXBUS_CIF_STATE_STREAMING; + + return 0; +} + +static int flexbus_cif_start_streaming(struct vb2_queue *queue, unsigned int count) +{ + struct flexbus_cif_stream *stream = queue->drv_priv; + struct flexbus_cif_vdev_node *node = &stream->vnode; + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct flexbus_cif_sensor_info *terminal_sensor = &dev->terminal_sensor; + int ret; + + v4l2_info(&dev->v4l2_dev, "stream[%d] start streaming\n", stream->id); + + mutex_lock(&dev->stream_lock); + if (stream->state == FLEXBUS_CIF_STATE_STREAMING) { + ret = -EBUSY; + v4l2_err(v4l2_dev, "stream in busy state\n"); + goto destroy_buf; + } + + if (dev->active_sensor) { + ret = flexbus_cif_update_sensor_info(stream); + if (ret < 0) { + v4l2_err(v4l2_dev, + "update sensor info failed %d\n", + ret); + goto out; + } + } + + if (terminal_sensor->sd) { + + ret = v4l2_subdev_call(terminal_sensor->sd, + video, g_frame_interval, &terminal_sensor->fi); + if (ret) + terminal_sensor->fi.interval = (struct v4l2_fract) {1, 30}; + + flexbus_cif_sync_crop_info(stream); + } + + ret = flexbus_cif_sanity_check_fmt(stream, NULL); + if (ret < 0) + goto destroy_buf; + + if (dev->is_use_dummybuf && (!dev->dummy_buf.vaddr)) { + ret = flexbus_cif_create_dummy_buf(stream); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to create dummy_buf, %d\n", ret); + goto destroy_buf; + } + } + tasklet_enable(&stream->vb_done_tasklet); + ret = dev->pipe.open(&dev->pipe, &node->vdev.entity, true); + if (ret < 0) { + v4l2_err(v4l2_dev, "open cif pipeline failed %d\n", + ret); + goto destroy_buf; + } + ret = dev->pipe.set_stream(&dev->pipe, true); + if (ret < 0) + goto stop_stream; + + msleep(50); + ret = flexbus_cif_stream_start(stream); + if (ret < 0) + goto destroy_buf; + ret = video_device_pipeline_start(&node->vdev, &dev->pipe.pipe); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "start pipeline failed %d\n", + ret); + goto pipe_stream_off; + } + + goto out; + +stop_stream: + flexbus_cif_stream_stop(stream); +pipe_stream_off: + dev->pipe.set_stream(&dev->pipe, false); +destroy_buf: + if (stream->next_buf) + vb2_buffer_done(&stream->next_buf->vb.vb2_buf, + VB2_BUF_STATE_QUEUED); + if (stream->curr_buf) + vb2_buffer_done(&stream->curr_buf->vb.vb2_buf, + VB2_BUF_STATE_QUEUED); + while (!list_empty(&stream->buf_head)) { + struct flexbus_cif_buffer *buf; + + buf = list_first_entry(&stream->buf_head, + struct flexbus_cif_buffer, queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + list_del(&buf->queue); + } + +out: + mutex_unlock(&dev->stream_lock); + return ret; +} + +static const struct vb2_ops flexbus_cif_vb2_ops = { + .queue_setup = flexbus_cif_queue_setup, + .buf_queue = flexbus_cif_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = flexbus_cif_stop_streaming, + .start_streaming = flexbus_cif_start_streaming, +}; + +static int flexbus_cif_init_vb2_queue(struct vb2_queue *q, + struct flexbus_cif_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 = &flexbus_cif_vb2_ops; + if (stream->cif_dev->is_dma_sg_ops) + q->mem_ops = &vb2_cma_sg_memops; + else + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct flexbus_cif_buffer); + q->min_buffers_needed = CIF_REQ_BUFS_MIN; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &stream->vnode.vlock; + q->dev = stream->cif_dev->dev; + q->allow_cache_hints = 1; + q->bidirectional = 1; + q->gfp_flags = GFP_DMA32; + q->dma_attrs = DMA_ATTR_FORCE_CONTIGUOUS; + return vb2_queue_init(q); +} + +static int flexbus_cif_set_fmt(struct flexbus_cif_stream *stream, + struct v4l2_pix_format_mplane *pixm, + bool try) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + const struct cif_output_fmt *fmt; + const struct cif_input_fmt *cif_fmt_in = NULL; + struct v4l2_rect input_rect; + unsigned int imagesize = 0, planes; + u32 xsubs = 1, ysubs = 1, i; + int ret; + + for (i = 0; i < FLEXBUS_CIF_MAX_PLANE; i++) + memset(&pixm->plane_fmt[i], 0, sizeof(struct v4l2_plane_pix_format)); + + fmt = flexbus_cif_find_output_fmt(stream, pixm->pixelformat); + if (!fmt) + fmt = &out_fmts[0]; + + input_rect.width = FLEXBUS_CIF_DEFAULT_WIDTH; + input_rect.height = FLEXBUS_CIF_DEFAULT_HEIGHT; + + if (dev->terminal_sensor.sd) { + cif_fmt_in = get_input_fmt(dev->terminal_sensor.sd, + &input_rect, stream->id); + stream->cif_fmt_in = cif_fmt_in; + } else { + v4l2_err(&stream->cif_dev->v4l2_dev, + "terminal subdev does not exist\n"); + return -EINVAL; + } + + ret = flexbus_cif_output_fmt_check(stream, fmt); + if (ret) + return -EINVAL; + + dev->terminal_sensor.raw_rect = 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; + + flexbus_cif_sync_crop_info(stream); + /* 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, bpp; + + if (i == 0) { + if (stream->crop_enable) { + width = stream->crop[CROP_SRC_ACT].width; + height = stream->crop[CROP_SRC_ACT].height; + } else { + width = pixm->width; + height = pixm->height; + } + } else { + if (stream->crop_enable) { + width = stream->crop[CROP_SRC_ACT].width / xsubs; + height = stream->crop[CROP_SRC_ACT].height / ysubs; + } else { + width = pixm->width / xsubs; + height = pixm->height / ysubs; + } + } + + bpp = flexbus_cif_align_bits_per_pixel(stream, fmt, i); + bpl = width * bpp / CIF_YUV_STORED_BIT_WIDTH; + 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, flexbus_cif_debug, &stream->cif_dev->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, flexbus_cif_debug, &stream->cif_dev->v4l2_dev, + "%s: req(%d, %d) out(%d, %d)\n", __func__, + pixm->width, pixm->height, + stream->pixm.width, stream->pixm.height); + } + return 0; +} + +void flexbus_cif_stream_init(struct flexbus_cif_device *dev, u32 id) +{ + struct flexbus_cif_stream *stream = &dev->stream[id]; + struct v4l2_pix_format_mplane pixm; + int i; + + memset(stream, 0, sizeof(*stream)); + memset(&pixm, 0, sizeof(pixm)); + stream->id = id; + stream->cif_dev = dev; + + INIT_LIST_HEAD(&stream->buf_head); + spin_lock_init(&stream->vbq_lock); + spin_lock_init(&stream->fps_lock); + stream->state = FLEXBUS_CIF_STATE_READY; + init_waitqueue_head(&stream->wq_stopped); + + /* Set default format */ + pixm.pixelformat = V4L2_PIX_FMT_NV12; + pixm.width = FLEXBUS_CIF_DEFAULT_WIDTH; + pixm.height = FLEXBUS_CIF_DEFAULT_HEIGHT; + flexbus_cif_set_fmt(stream, &pixm, false); + + for (i = 0; i < CROP_SRC_MAX; i++) { + stream->crop[i].left = 0; + stream->crop[i].top = 0; + stream->crop[i].width = FLEXBUS_CIF_DEFAULT_WIDTH; + stream->crop[i].height = FLEXBUS_CIF_DEFAULT_HEIGHT; + } + + stream->crop_enable = false; + stream->crop_dyn_en = false; + stream->crop_mask = 0x0; + + stream->is_compact = false; + + atomic_set(&stream->buf_cnt, 0); +} + +static int flexbus_cif_fh_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct flexbus_cif_vdev_node *vnode = vdev_to_node(vdev); + struct flexbus_cif_stream *stream = to_flexbus_cif_stream(vnode); + struct flexbus_cif_device *cif_dev = stream->cif_dev; + int ret; + + /* Make sure active sensor is valid before .set_fmt() */ + ret = flexbus_cif_update_sensor_info(stream); + if (ret < 0) { + v4l2_err(vdev, + "update sensor info failed %d\n", + ret); + + return ret; + } + + ret = pm_runtime_resume_and_get(cif_dev->dev); + if (ret < 0) { + v4l2_err(vdev, "Failed to get runtime pm, %d\n", + ret); + return ret; + } + + ret = v4l2_fh_open(file); + if (!ret) { + mutex_lock(&cif_dev->stream_lock); + ret = v4l2_pipeline_pm_get(&vnode->vdev.entity); + v4l2_info(vdev, "open video, entity use_count %d\n", + vnode->vdev.entity.use_count); + mutex_unlock(&cif_dev->stream_lock); + if (ret < 0) + vb2_fop_release(file); + } + return ret; +} + +static int flexbus_cif_fh_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct flexbus_cif_vdev_node *vnode = vdev_to_node(vdev); + struct flexbus_cif_stream *stream = to_flexbus_cif_stream(vnode); + struct flexbus_cif_device *cif_dev = stream->cif_dev; + int ret = 0; + + ret = vb2_fop_release(file); + if (!ret) { + mutex_lock(&cif_dev->stream_lock); + v4l2_pipeline_pm_put(&vnode->vdev.entity); + v4l2_info(vdev, "close video, entity use_count %d\n", + vnode->vdev.entity.use_count); + mutex_unlock(&cif_dev->stream_lock); + } + + pm_runtime_put_sync(cif_dev->dev); + return ret; +} + +static const struct v4l2_file_operations flexbus_cif_fops = { + .open = flexbus_cif_fh_open, + .release = flexbus_cif_fh_release, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = video_ioctl2, +#endif +}; + +static int flexbus_cif_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int flexbus_cif_try_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + int ret = 0; + + ret = flexbus_cif_set_fmt(stream, &f->fmt.pix_mp, true); + + return ret; +} + +static int flexbus_cif_enum_framesizes(struct file *file, void *prov, + struct v4l2_frmsizeenum *fsize) +{ + struct v4l2_frmsize_stepwise *s = &fsize->stepwise; + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_rect input_rect; + + if (fsize->index != 0) + return -EINVAL; + + if (!flexbus_cif_find_output_fmt(stream, fsize->pixel_format)) + return -EINVAL; + + input_rect.width = FLEXBUS_CIF_DEFAULT_WIDTH; + input_rect.height = FLEXBUS_CIF_DEFAULT_HEIGHT; + + if (dev->terminal_sensor.sd) + get_input_fmt(dev->terminal_sensor.sd, + &input_rect, stream->id); + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + s->min_width = CIF_MIN_WIDTH; + s->min_height = CIF_MIN_HEIGHT; + s->max_width = input_rect.width; + s->max_height = input_rect.height; + s->step_width = OUTPUT_STEP_WISE; + s->step_height = OUTPUT_STEP_WISE; + + return 0; +} + +static int flexbus_cif_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + struct flexbus_cif_sensor_info *sensor = dev->active_sensor; + struct v4l2_subdev_frame_interval fi; + int ret; + + if (fival->index != 0) + return -EINVAL; + + if (!sensor || !sensor->sd) { + /* TODO: active_sensor is NULL if using DMARX path */ + v4l2_err(&dev->v4l2_dev, "%s Not active sensor\n", __func__); + return -ENODEV; + } + + ret = v4l2_subdev_call(sensor->sd, video, g_frame_interval, &fi); + if (ret && ret != -ENOIOCTLCMD) { + return ret; + } else if (ret == -ENOIOCTLCMD) { + /* Set a default value for sensors not implements ioctl */ + fi.interval.numerator = 1; + fi.interval.denominator = 30; + } + + fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + fival->stepwise.step.numerator = 1; + fival->stepwise.step.denominator = 1; + fival->stepwise.max.numerator = 1; + fival->stepwise.max.denominator = 1; + fival->stepwise.min.numerator = fi.interval.numerator; + fival->stepwise.min.denominator = fi.interval.denominator; + + return 0; +} + +static int flexbus_cif_enum_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct cif_output_fmt *fmt = NULL; + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + const struct cif_input_fmt *cif_fmt_in = NULL; + struct v4l2_rect input_rect; + int i = 0; + int ret = 0; + int fource_idx = 0; + + if (f->index >= ARRAY_SIZE(out_fmts)) + return -EINVAL; + + if (dev->terminal_sensor.sd) { + cif_fmt_in = get_input_fmt(dev->terminal_sensor.sd, + &input_rect, stream->id); + stream->cif_fmt_in = cif_fmt_in; + } else { + v4l2_err(&stream->cif_dev->v4l2_dev, + "terminal subdev does not exist\n"); + return -EINVAL; + } + + if (f->index != 0) + fource_idx = stream->new_fource_idx; + + for (i = fource_idx; i < ARRAY_SIZE(out_fmts); i++) { + fmt = &out_fmts[i]; + ret = flexbus_cif_output_fmt_check(stream, fmt); + if (!ret) { + f->pixelformat = fmt->fourcc; + stream->new_fource_idx = i + 1; + break; + } + } + if (i == ARRAY_SIZE(out_fmts)) + return -EINVAL; + + switch (f->pixelformat) { + case V4l2_PIX_FMT_EBD8: + strscpy(f->description, + "Embedded data 8-bit", + sizeof(f->description)); + break; + case V4l2_PIX_FMT_SPD16: + strscpy(f->description, + "Shield pix data 16-bit", + sizeof(f->description)); + break; + default: + break; + } + return 0; +} + +static int flexbus_cif_s_fmt_vid_cap_mplane(struct file *file, + void *priv, struct v4l2_format *f) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + int ret = 0; + + if (vb2_is_busy(&stream->vnode.buf_queue)) { + v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = flexbus_cif_set_fmt(stream, &f->fmt.pix_mp, false); + + return ret; +} + +static int flexbus_cif_g_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + + f->fmt.pix_mp = stream->pixm; + + return 0; +} + +static int flexbus_cif_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct device *dev = stream->cif_dev->dev; + + strscpy(cap->driver, dev->driver->name, sizeof(cap->driver)); + strscpy(cap->card, dev->driver->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", dev_name(dev)); + + return 0; +} + +static __maybe_unused int flexbus_cif_cropcap(struct file *file, void *fh, + struct v4l2_cropcap *cap) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_rect *raw_rect = &dev->terminal_sensor.raw_rect; + int ret = 0; + + if (stream->crop_mask & CROP_SRC_SENSOR) { + cap->bounds.left = stream->crop[CROP_SRC_SENSOR].left; + cap->bounds.top = stream->crop[CROP_SRC_SENSOR].top; + cap->bounds.width = stream->crop[CROP_SRC_SENSOR].width; + cap->bounds.height = stream->crop[CROP_SRC_SENSOR].height; + } else { + cap->bounds.left = raw_rect->left; + cap->bounds.top = raw_rect->top; + cap->bounds.width = raw_rect->width; + cap->bounds.height = raw_rect->height; + } + + cap->defrect = cap->bounds; + cap->pixelaspect.numerator = 1; + cap->pixelaspect.denominator = 1; + + return ret; +} + +static int flexbus_cif_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_subdev *sensor_sd; + struct v4l2_subdev_selection sd_sel; + const struct v4l2_rect *rect = &s->r; + struct v4l2_rect sensor_crop; + struct v4l2_rect *raw_rect = &dev->terminal_sensor.raw_rect; + u16 pad = 0; + int ret = 0; + + if (!s) { + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "sel is null\n"); + goto err; + } + + if (s->target == V4L2_SEL_TGT_CROP_BOUNDS) { + sensor_sd = get_remote_sensor(stream, &pad); + + sd_sel.r = s->r; + sd_sel.pad = pad; + sd_sel.target = s->target; + sd_sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + ret = v4l2_subdev_call(sensor_sd, pad, set_selection, NULL, &sd_sel); + if (!ret) { + s->r = sd_sel.r; + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "%s: pad:%d, which:%d, target:%d\n", + __func__, pad, sd_sel.which, sd_sel.target); + } + } else if (s->target == V4L2_SEL_TGT_CROP) { + ret = flexbus_cif_sanity_check_fmt(stream, rect); + if (ret) { + v4l2_err(&dev->v4l2_dev, "set crop failed\n"); + return ret; + } + + if (stream->crop_mask & CROP_SRC_SENSOR) { + sensor_crop = stream->crop[CROP_SRC_SENSOR]; + if (rect->left + rect->width > sensor_crop.width || + rect->top + rect->height > sensor_crop.height) { + v4l2_err(&dev->v4l2_dev, + "crop size is bigger than sensor input:left:%d, top:%d, width:%d, height:%d\n", + sensor_crop.left, sensor_crop.top, + sensor_crop.width, sensor_crop.height); + return -EINVAL; + } + } else { + if (rect->left + rect->width > raw_rect->width || + rect->top + rect->height > raw_rect->height) { + v4l2_err(&dev->v4l2_dev, + "crop size is bigger than sensor raw input:left:%d, top:%d, width:%d, height:%d\n", + raw_rect->left, raw_rect->top, + raw_rect->width, raw_rect->height); + return -EINVAL; + } + } + + stream->crop[CROP_SRC_USR] = *rect; + stream->crop_enable = true; + stream->crop_mask |= CROP_SRC_USR_MASK; + stream->crop[CROP_SRC_ACT] = stream->crop[CROP_SRC_USR]; + if (stream->crop_mask & CROP_SRC_SENSOR) { + sensor_crop = stream->crop[CROP_SRC_SENSOR]; + stream->crop[CROP_SRC_ACT].left = sensor_crop.left + stream->crop[CROP_SRC_USR].left; + stream->crop[CROP_SRC_ACT].top = sensor_crop.top + stream->crop[CROP_SRC_USR].top; + } + + if (stream->state == FLEXBUS_CIF_STATE_STREAMING) { + stream->crop_dyn_en = true; + + v4l2_info(&dev->v4l2_dev, "enable dynamic crop, S_SELECTION(%ux%u@%u:%u) target: %d\n", + rect->width, rect->height, rect->left, rect->top, s->target); + } else { + v4l2_info(&dev->v4l2_dev, "static crop, S_SELECTION(%ux%u@%u:%u) target: %d\n", + rect->width, rect->height, rect->left, rect->top, s->target); + } + } else { + goto err; + } + + return ret; + +err: + return -EINVAL; +} + +static int flexbus_cif_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct flexbus_cif_stream *stream = video_drvdata(file); + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_subdev *sensor_sd; + struct v4l2_subdev_selection sd_sel; + u16 pad = 0; + int ret = 0; + + if (!s) { + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "sel is null\n"); + goto err; + } + + if (s->target == V4L2_SEL_TGT_CROP_BOUNDS) { + sensor_sd = get_remote_sensor(stream, &pad); + + sd_sel.pad = pad; + sd_sel.target = s->target; + sd_sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, "%s(line:%d): sd:%s pad:%d, which:%d, target:%d\n", + __func__, __LINE__, sensor_sd->name, pad, sd_sel.which, sd_sel.target); + + ret = v4l2_subdev_call(sensor_sd, pad, get_selection, NULL, &sd_sel); + if (!ret) { + s->r = sd_sel.r; + } else { + s->r.left = 0; + s->r.top = 0; + s->r.width = stream->pixm.width; + s->r.height = stream->pixm.height; + } + } else if (s->target == V4L2_SEL_TGT_CROP) { + if (stream->crop_mask & (CROP_SRC_USR_MASK | CROP_SRC_SENSOR_MASK)) { + s->r = stream->crop[CROP_SRC_ACT]; + } else { + s->r.left = 0; + s->r.top = 0; + s->r.width = stream->pixm.width; + s->r.height = stream->pixm.height; + } + } else { + goto err; + } + + return ret; +err: + return -EINVAL; +} + +static long flexbus_cif_ioctl_default(struct file *file, void *fh, + bool valid_prio, unsigned int cmd, void *arg) +{ + return -EINVAL; +} + +static const struct v4l2_ioctl_ops flexbus_cif_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 = flexbus_cif_enum_input, + .vidioc_try_fmt_vid_cap_mplane = flexbus_cif_try_fmt_vid_cap_mplane, + .vidioc_enum_fmt_vid_cap = flexbus_cif_enum_fmt_vid_cap_mplane, + .vidioc_s_fmt_vid_cap_mplane = flexbus_cif_s_fmt_vid_cap_mplane, + .vidioc_g_fmt_vid_cap_mplane = flexbus_cif_g_fmt_vid_cap_mplane, + .vidioc_querycap = flexbus_cif_querycap, + .vidioc_s_selection = flexbus_cif_s_selection, + .vidioc_g_selection = flexbus_cif_g_selection, + .vidioc_enum_frameintervals = flexbus_cif_enum_frameintervals, + .vidioc_enum_framesizes = flexbus_cif_enum_framesizes, + .vidioc_default = flexbus_cif_ioctl_default, +}; + +static void flexbus_cif_vb_done_oneframe(struct flexbus_cif_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); + } + + vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE); + v4l2_dbg(2, flexbus_cif_debug, &stream->cif_dev->v4l2_dev, + "stream[%d] vb done, index: %d, sequence %d\n", stream->id, + vb_done->vb2_buf.index, vb_done->sequence); + atomic_dec(&stream->buf_cnt); +} + +static void flexbus_cif_tasklet_handle(unsigned long data) +{ + struct flexbus_cif_stream *stream = (struct flexbus_cif_stream *)data; + struct flexbus_cif_buffer *buf = NULL; + unsigned long flags = 0; + LIST_HEAD(local_list); + + spin_lock_irqsave(&stream->vbq_lock, flags); + list_replace_init(&stream->vb_done_list, &local_list); + spin_unlock_irqrestore(&stream->vbq_lock, flags); + while (!list_empty(&local_list)) { + buf = list_first_entry(&local_list, + struct flexbus_cif_buffer, queue); + list_del(&buf->queue); + flexbus_cif_vb_done_oneframe(stream, &buf->vb); + } +} + +static void flexbus_cif_vb_done_tasklet(struct flexbus_cif_stream *stream, + struct flexbus_cif_buffer *buf) +{ + unsigned long flags = 0; + + if (!stream || !buf) + return; + spin_lock_irqsave(&stream->vbq_lock, flags); + list_add_tail(&buf->queue, &stream->vb_done_list); + spin_unlock_irqrestore(&stream->vbq_lock, flags); + tasklet_schedule(&stream->vb_done_tasklet); +} + +static void flexbus_cif_unregister_stream_vdev(struct flexbus_cif_stream *stream) +{ + tasklet_kill(&stream->vb_done_tasklet); + media_entity_cleanup(&stream->vnode.vdev.entity); + video_unregister_device(&stream->vnode.vdev); +} + +static int flexbus_cif_register_stream_vdev(struct flexbus_cif_stream *stream, + bool is_multi_input) +{ + struct flexbus_cif_device *dev = stream->cif_dev; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct video_device *vdev = &stream->vnode.vdev; + struct flexbus_cif_vdev_node *node; + int ret = 0; + char *vdev_name; + + vdev_name = CIF_CIF_VDEV_NAME; + strscpy(vdev->name, vdev_name, sizeof(vdev->name)); + node = vdev_to_node(vdev); + mutex_init(&node->vlock); + + vdev->ioctl_ops = &flexbus_cif_v4l2_ioctl_ops; + vdev->release = video_device_release_empty; + vdev->fops = &flexbus_cif_fops; + vdev->minor = -1; + vdev->v4l2_dev = v4l2_dev; + vdev->lock = &node->vlock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_STREAMING; + video_set_drvdata(vdev, stream); + vdev->vfl_dir = VFL_DIR_RX; + node->pad.flags = MEDIA_PAD_FL_SINK; + + flexbus_cif_init_vb2_queue(&node->buf_queue, stream, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + vdev->queue = &node->buf_queue; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(v4l2_dev, + "video_register_device failed with error %d\n", ret); + return ret; + } + + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); + if (ret < 0) + goto unreg; + + INIT_LIST_HEAD(&stream->vb_done_list); + tasklet_init(&stream->vb_done_tasklet, + flexbus_cif_tasklet_handle, + (unsigned long)stream); + tasklet_disable(&stream->vb_done_tasklet); + return 0; +unreg: + video_unregister_device(vdev); + return ret; +} + +void flexbus_cif_unregister_stream_vdevs(struct flexbus_cif_device *dev, + int stream_num) +{ + struct flexbus_cif_stream *stream; + int i; + + for (i = 0; i < stream_num; i++) { + stream = &dev->stream[i]; + flexbus_cif_unregister_stream_vdev(stream); + } +} + +int flexbus_cif_register_stream_vdevs(struct flexbus_cif_device *dev, + int stream_num, + bool is_multi_input) +{ + struct flexbus_cif_stream *stream; + int i, j, ret; + + for (i = 0; i < stream_num; i++) { + stream = &dev->stream[i]; + stream->cif_dev = dev; + ret = flexbus_cif_register_stream_vdev(stream, is_multi_input); + if (ret < 0) + goto err; + } + return 0; +err: + for (j = 0; j < i; j++) { + stream = &dev->stream[j]; + flexbus_cif_unregister_stream_vdev(stream); + } + + return ret; +} + +static int flexbus_cif_sof_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type == V4L2_EVENT_FRAME_SYNC) + return v4l2_event_subscribe(fh, sub, 4, NULL); + else + return -EINVAL; +} + +static void flexbus_cif_cif_event_inc_sof(struct flexbus_cif_device *dev) +{ + struct flexbus_cif_cif_sof_subdev *subdev = &dev->cif_sof_subdev; + + if (subdev) { + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + .u.frame_sync.frame_sequence = + atomic_inc_return(&subdev->frm_sync_seq) - 1, + }; + v4l2_event_queue(subdev->sd.devnode, &event); + } +} + +static const struct v4l2_subdev_core_ops flexbus_cif_cif_sof_sd_core_ops = { + .subscribe_event = flexbus_cif_sof_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops flexbus_cif_cif_sof_sd_ops = { + .core = &flexbus_cif_cif_sof_sd_core_ops, +}; + +int flexbus_cif_register_cif_sof_subdev(struct flexbus_cif_device *dev) +{ + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct flexbus_cif_cif_sof_subdev *subdev = &dev->cif_sof_subdev; + struct v4l2_subdev *sd; + int ret; + + memset(subdev, 0, sizeof(*subdev)); + subdev->cif_dev = dev; + sd = &subdev->sd; + v4l2_subdev_init(sd, &flexbus_cif_cif_sof_sd_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + snprintf(sd->name, sizeof(sd->name), "flexbus_cif-cif-sof"); + + v4l2_set_subdevdata(sd, subdev); + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) + goto end; + + ret = v4l2_device_register_subdev_nodes(v4l2_dev); + if (ret < 0) + goto free_subdev; + + return ret; + +free_subdev: + v4l2_device_unregister_subdev(sd); + +end: + v4l2_err(sd, "Failed to register subdev, ret:%d\n", ret); + return ret; +} + +void flexbus_cif_unregister_cif_sof_subdev(struct flexbus_cif_device *dev) +{ + struct v4l2_subdev *sd = &dev->cif_sof_subdev.sd; + + v4l2_device_unregister_subdev(sd); +} + +static void flexbus_cif_dynamic_crop(struct flexbus_cif_stream *stream) +{ + struct flexbus_cif_device *cif_dev = stream->cif_dev; + u32 raw_width, crop_width = 64, + crop_height = 64, crop_x = 0, crop_y = 0; + + if (!cif_dev->active_sensor) + return; + + raw_width = stream->crop[CROP_SRC_ACT].width; + crop_width = raw_width; + crop_height = stream->crop[CROP_SRC_ACT].height; + crop_x = stream->crop[CROP_SRC_ACT].left; + crop_y = stream->crop[CROP_SRC_ACT].top; + + flexbus_cif_write_register(cif_dev, FLEXBUS_DVP_CROP_START, + crop_y << CIF_CROP_Y_SHIFT | crop_x); + + flexbus_cif_write_register(cif_dev, FLEXBUS_DVP_CROP_SIZE, + crop_height << CIF_CROP_Y_SHIFT | crop_width); + + stream->crop_dyn_en = false; +} + +static void flexbus_cif_buf_done_prepare(struct flexbus_cif_stream *stream, + struct flexbus_cif_buffer *active_buf, + int mipi_id, + u32 mode) +{ + struct vb2_v4l2_buffer *vb_done = NULL; + + if (active_buf) { + vb_done = &active_buf->vb; + vb_done->vb2_buf.timestamp = ktime_get_ns(); + vb_done->sequence = stream->frame_idx - 1; + active_buf->fe_timestamp = ktime_get_ns(); + } + + flexbus_cif_vb_done_tasklet(stream, active_buf); +} + +static void flexbus_cif_deal_readout_time(struct flexbus_cif_stream *stream) +{ + unsigned long flags; + + spin_lock_irqsave(&stream->fps_lock, flags); + stream->readout.fe_timestamp = ktime_get_ns(); + spin_unlock_irqrestore(&stream->fps_lock, flags); +} + +static void flexbus_cif_update_stream(struct flexbus_cif_device *cif_dev, + struct flexbus_cif_stream *stream, + int mipi_id) +{ + struct flexbus_cif_buffer *active_buf = NULL; + unsigned long flags; + int ret = 0; + + if (stream->frame_phase == (CIF_CSI_FRAME0_READY | CIF_CSI_FRAME1_READY)) { + cif_dev->err_state |= (FLEXBUS_CIF_ERR_ID0_TRIG_SIMULT << stream->id); + cif_dev->irq_stats.trig_simult_cnt[stream->id]++; + return; + } + spin_lock_irqsave(&stream->fps_lock, flags); + if (stream->frame_phase & CIF_CSI_FRAME0_READY) { + if (stream->curr_buf) + active_buf = stream->curr_buf; + stream->fps_stats.frm0_timestamp = ktime_get_ns(); + } else if (stream->frame_phase & CIF_CSI_FRAME1_READY) { + if (stream->next_buf) + active_buf = stream->next_buf; + stream->fps_stats.frm1_timestamp = ktime_get_ns(); + } + spin_unlock_irqrestore(&stream->fps_lock, flags); + + flexbus_cif_deal_readout_time(stream); + + ret = flexbus_cif_assign_new_buffer_pingpong(stream, + FLEXBUS_CIF_YUV_ADDR_STATE_UPDATE, + mipi_id); + if (ret) + return; + + flexbus_cif_buf_done_prepare(stream, active_buf, mipi_id, 0); + +} + +static u32 flexbus_cif_mbus_pixelcode_to_v4l2(u32 pixelcode) +{ + u32 pixelformat; + + switch (pixelcode) { + case MEDIA_BUS_FMT_Y8_1X8: + pixelformat = V4L2_PIX_FMT_GREY; + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + pixelformat = V4L2_PIX_FMT_SBGGR8; + break; + case MEDIA_BUS_FMT_SGBRG8_1X8: + pixelformat = V4L2_PIX_FMT_SGBRG8; + break; + case MEDIA_BUS_FMT_SGRBG8_1X8: + pixelformat = V4L2_PIX_FMT_SGRBG8; + break; + case MEDIA_BUS_FMT_SRGGB8_1X8: + pixelformat = V4L2_PIX_FMT_SRGGB8; + break; + case MEDIA_BUS_FMT_Y10_1X10: + pixelformat = V4L2_PIX_FMT_Y10; + break; + case MEDIA_BUS_FMT_SBGGR10_1X10: + pixelformat = V4L2_PIX_FMT_SBGGR10; + break; + case MEDIA_BUS_FMT_SGBRG10_1X10: + pixelformat = V4L2_PIX_FMT_SGBRG10; + break; + case MEDIA_BUS_FMT_SGRBG10_1X10: + pixelformat = V4L2_PIX_FMT_SGRBG10; + break; + case MEDIA_BUS_FMT_SRGGB10_1X10: + pixelformat = V4L2_PIX_FMT_SRGGB10; + break; + case MEDIA_BUS_FMT_Y12_1X12: + pixelformat = V4L2_PIX_FMT_Y12; + break; + case MEDIA_BUS_FMT_SBGGR12_1X12: + pixelformat = V4L2_PIX_FMT_SBGGR12; + break; + case MEDIA_BUS_FMT_SGBRG12_1X12: + pixelformat = V4L2_PIX_FMT_SGBRG12; + break; + case MEDIA_BUS_FMT_SGRBG12_1X12: + pixelformat = V4L2_PIX_FMT_SGRBG12; + break; + case MEDIA_BUS_FMT_SRGGB12_1X12: + pixelformat = V4L2_PIX_FMT_SRGGB12; + break; + case MEDIA_BUS_FMT_SPD_2X8: + pixelformat = V4l2_PIX_FMT_SPD16; + break; + case MEDIA_BUS_FMT_EBD_1X8: + pixelformat = V4l2_PIX_FMT_EBD8; + break; + default: + pixelformat = V4L2_PIX_FMT_SRGGB10; + } + + return pixelformat; +} + +void flexbus_cif_set_default_fmt(struct flexbus_cif_device *cif_dev) +{ + struct v4l2_subdev_selection input_sel; + struct v4l2_pix_format_mplane pixm; + struct v4l2_subdev_format fmt; + int stream_num = 0; + int ret, i; + + stream_num = 1; + + if (!cif_dev->terminal_sensor.sd) + flexbus_cif_update_sensor_info(&cif_dev->stream[0]); + + if (cif_dev->terminal_sensor.sd) { + for (i = 0; i < stream_num; i++) { + memset(&fmt, 0, sizeof(fmt)); + fmt.pad = i; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + v4l2_subdev_call(cif_dev->terminal_sensor.sd, pad, get_fmt, NULL, &fmt); + + memset(&pixm, 0, sizeof(pixm)); + pixm.pixelformat = flexbus_cif_mbus_pixelcode_to_v4l2(fmt.format.code); + pixm.width = fmt.format.width; + pixm.height = fmt.format.height; + + memset(&input_sel, 0, sizeof(input_sel)); + input_sel.pad = i; + input_sel.target = V4L2_SEL_TGT_CROP_BOUNDS; + input_sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(cif_dev->terminal_sensor.sd, + pad, get_selection, NULL, + &input_sel); + if (!ret) { + pixm.width = input_sel.r.width; + pixm.height = input_sel.r.height; + } + flexbus_cif_set_fmt(&cif_dev->stream[i], &pixm, false); + } + } +} + +static void flexbus_cif_send_sof(struct flexbus_cif_device *cif_dev) +{ + flexbus_cif_cif_event_inc_sof(cif_dev); +} + +static void flexbus_cif_deal_sof(struct flexbus_cif_device *cif_dev) +{ + struct flexbus_cif_stream *detect_stream = &cif_dev->stream[0]; + unsigned long flags; + + spin_lock_irqsave(&detect_stream->fps_lock, flags); + detect_stream->readout.fs_timestamp = ktime_get_ns(); + spin_unlock_irqrestore(&detect_stream->fps_lock, flags); + + flexbus_cif_send_sof(cif_dev); + detect_stream->frame_idx++; + v4l2_dbg(3, flexbus_cif_debug, &cif_dev->v4l2_dev, + "stream[%d] sof %d %lld\n", + detect_stream->id, + detect_stream->frame_idx - 1, + ktime_get_ns()); +} + +void flexbus_cif_err_print_work(struct work_struct *work) +{ + struct flexbus_cif_err_state_work *err_state_work = container_of(work, + struct flexbus_cif_err_state_work, + work); + struct flexbus_cif_device *dev = container_of(err_state_work, + struct flexbus_cif_device, + err_state_work); + u32 err_state = 0; + u64 cur_time = 0; + bool is_print = false; + + cur_time = ktime_get_ns(); + if (err_state_work->last_timestamp == 0) { + is_print = true; + } else { + if (cur_time - err_state_work->last_timestamp > 500000000) + is_print = true; + } + err_state_work->last_timestamp = cur_time; + err_state = err_state_work->err_state; + if (err_state & FLEXBUS_CIF_ERR_ID0_NOT_BUF && is_print) + v4l2_err(&dev->v4l2_dev, + "stream[0] not active buffer, frame num %d, cnt %llu\n", + dev->stream[0].frame_idx, dev->irq_stats.not_active_buf_cnt[0]); + if (err_state & FLEXBUS_CIF_ERR_ID1_NOT_BUF && is_print) + v4l2_err(&dev->v4l2_dev, + "stream[1] not active buffer, frame num %d, cnt %llu\n", + dev->stream[1].frame_idx, dev->irq_stats.not_active_buf_cnt[1]); + if (err_state & FLEXBUS_CIF_ERR_ID2_NOT_BUF && is_print) + v4l2_err(&dev->v4l2_dev, + "stream[2] not active buffer, frame num %d, cnt %llu\n", + dev->stream[2].frame_idx, dev->irq_stats.not_active_buf_cnt[2]); + if (err_state & FLEXBUS_CIF_ERR_ID3_NOT_BUF && is_print) + v4l2_err(&dev->v4l2_dev, + "stream[3] not active buffer, frame num %d, cnt %llu\n", + dev->stream[3].frame_idx, dev->irq_stats.not_active_buf_cnt[3]); + if (err_state & FLEXBUS_CIF_ERR_SIZE) + v4l2_err(&dev->v4l2_dev, + "ERROR: size err, cnt %llu\n", + dev->irq_stats.cif_size_err_cnt); + if (err_state & FLEXBUS_CIF_ERR_OVERFLOW) + v4l2_err(&dev->v4l2_dev, + "ERROR: fifo overflow, cnt %llu, dnum %d\n", + dev->irq_stats.cif_overflow_cnt, err_state_work->fifo_dnum); + if (err_state & FLEXBUS_CIF_ERR_BANDWIDTH_LACK) + v4l2_err(&dev->v4l2_dev, + "ERROR: bandwidth lack, cnt %llu\n", + dev->irq_stats.cif_bwidth_lack_cnt); +} + +void flexbus_cif_irq_pingpong(struct flexbus_cif_device *cif_dev, u32 isr) +{ + struct flexbus_cif_stream *stream; + + if (!cif_dev->active_sensor) + return; + + stream = &cif_dev->stream[FLEXBUS_CIF_STREAM_CIF]; + if (isr & CIF_FIFO_OVERFLOW) { + cif_dev->irq_stats.cif_overflow_cnt++; + cif_dev->err_state |= FLEXBUS_CIF_ERR_OVERFLOW; + cif_dev->err_state_work.fifo_dnum = flexbus_cif_read_register(cif_dev, FLEXBUS_RXFIFO_DNUM); + } + + if (isr & CIF_BANDWIDTH_LACK) { + cif_dev->irq_stats.cif_bwidth_lack_cnt++; + cif_dev->err_state |= FLEXBUS_CIF_ERR_BANDWIDTH_LACK; + } + + if (isr & CIF_SIZE_ERR) { + cif_dev->irq_stats.cif_size_err_cnt++; + //RESET + stream->buf_wake_up_cnt = 0; + cif_dev->err_state |= FLEXBUS_CIF_ERR_SIZE; + } + + if (isr & CIF_DMA_END) { + stream->frame_idx++; + stream->buf_wake_up_cnt++; + if (stream->stopping) { + flexbus_cif_stream_stop(stream); + stream->stopping = false; + wake_up(&stream->wq_stopped); + return; + } + if (stream->state != FLEXBUS_CIF_STATE_STREAMING) + return; + //need to change + stream->frame_phase = (isr >> FLEXBUS_IMR_DMA_DST0_SHIFT) & 0x3; + flexbus_cif_update_stream(cif_dev, stream, 0); + cif_dev->irq_stats.frm_end_cnt[stream->id]++; + if (stream->crop_dyn_en) + flexbus_cif_dynamic_crop(stream); + } + if (isr & CIF_DMA_START) + flexbus_cif_deal_sof(cif_dev); +} + diff --git a/drivers/media/platform/rockchip/flexbus_cif/common.c b/drivers/media/platform/rockchip/flexbus_cif/common.c new file mode 100644 index 000000000000..282c5b49393e --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/common.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include "dev.h" +#include "common.h" + +static void flexbus_cif_init_dummy_vb2(struct flexbus_cif_device *dev, + struct flexbus_cif_dummy_buffer *buf) +{ + unsigned long attrs = buf->is_need_vaddr ? 0 : DMA_ATTR_NO_KERNEL_MAPPING; + + memset(&buf->vb2_queue, 0, sizeof(struct vb2_queue)); + memset(&buf->vb, 0, sizeof(struct vb2_buffer)); + buf->vb2_queue.gfp_flags = GFP_KERNEL | GFP_DMA32; + buf->vb2_queue.dma_dir = DMA_BIDIRECTIONAL; + attrs |= DMA_ATTR_FORCE_CONTIGUOUS; + buf->vb2_queue.dma_attrs = attrs; + buf->vb.vb2_queue = &buf->vb2_queue; +} + +int flexbus_cif_alloc_buffer(struct flexbus_cif_device *dev, + struct flexbus_cif_dummy_buffer *buf) +{ + const struct vb2_mem_ops *g_ops = &vb2_cma_sg_memops; + struct sg_table *sg_tbl; + void *mem_priv; + int ret = 0; + + if (!buf->size) { + ret = -EINVAL; + goto err; + } + + flexbus_cif_init_dummy_vb2(dev, buf); + buf->size = PAGE_ALIGN(buf->size); + mem_priv = g_ops->alloc(&buf->vb, dev->dev, buf->size); + if (IS_ERR_OR_NULL(mem_priv)) { + ret = -ENOMEM; + goto err; + } + + buf->mem_priv = mem_priv; + sg_tbl = (struct sg_table *)g_ops->cookie(&buf->vb, mem_priv); + buf->dma_addr = sg_dma_address(sg_tbl->sgl); + g_ops->prepare(mem_priv); + if (buf->is_need_vaddr) + buf->vaddr = g_ops->vaddr(&buf->vb, mem_priv); + if (buf->is_need_dbuf) { + buf->dbuf = g_ops->get_dmabuf(&buf->vb, mem_priv, O_RDWR); + if (buf->is_need_dmafd) { + buf->dma_fd = dma_buf_fd(buf->dbuf, O_CLOEXEC); + if (buf->dma_fd < 0) { + dma_buf_put(buf->dbuf); + ret = buf->dma_fd; + goto err; + } + get_dma_buf(buf->dbuf); + } + } + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, + "%s buf:0x%x~0x%x size:%d\n", __func__, + (u32)buf->dma_addr, (u32)buf->dma_addr + buf->size, buf->size); + return ret; +err: + dev_err(dev->dev, "%s failed ret:%d\n", __func__, ret); + return ret; +} + +void flexbus_cif_free_buffer(struct flexbus_cif_device *dev, + struct flexbus_cif_dummy_buffer *buf) +{ + const struct vb2_mem_ops *g_ops = &vb2_cma_sg_memops; + + if (buf && buf->mem_priv) { + v4l2_dbg(1, flexbus_cif_debug, &dev->v4l2_dev, + "%s buf:0x%x~0x%x\n", __func__, + (u32)buf->dma_addr, (u32)buf->dma_addr + buf->size); + if (buf->dbuf) + dma_buf_put(buf->dbuf); + g_ops->put(buf->mem_priv); + buf->size = 0; + buf->dbuf = NULL; + buf->vaddr = NULL; + buf->mem_priv = NULL; + buf->is_need_dbuf = false; + buf->is_need_vaddr = false; + buf->is_need_dmafd = false; + buf->is_free = true; + } +} + diff --git a/drivers/media/platform/rockchip/flexbus_cif/common.h b/drivers/media/platform/rockchip/flexbus_cif/common.h new file mode 100644 index 000000000000..8ab77f8d0ed6 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#ifndef _FLEXBUS_CIF_COMMON_H +#define _FLEXBUS_CIF_COMMON_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dev.h" + +int flexbus_cif_alloc_buffer(struct flexbus_cif_device *dev, + struct flexbus_cif_dummy_buffer *buf); +void flexbus_cif_free_buffer(struct flexbus_cif_device *dev, + struct flexbus_cif_dummy_buffer *buf); + +#endif /* _FLEXBUS_CIF_COMMON_H */ + diff --git a/drivers/media/platform/rockchip/flexbus_cif/dev.c b/drivers/media/platform/rockchip/flexbus_cif/dev.c new file mode 100644 index 000000000000..bef3d4953e53 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/dev.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dev.h" +#include "procfs.h" +#include +#include +#include +#include + +int flexbus_cif_debug; +module_param_named(debug, flexbus_cif_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +static DEFINE_MUTEX(flexbus_cif_mutex); +static LIST_HEAD(flexbus_cif_device_list); + +void flexbus_cif_write_register(struct flexbus_cif_device *dev, + u32 offset, u32 val) +{ + rockchip_flexbus_writel(dev->fb_dev, offset, val); + v4l2_dbg(4, flexbus_cif_debug, &dev->v4l2_dev, + "write reg[0x%x]:0x%x!!!\n", + offset, val); + +} + +void flexbus_cif_write_register_or(struct flexbus_cif_device *dev, + u32 offset, u32 val) +{ + unsigned int reg_val = 0x0; + + reg_val = rockchip_flexbus_readl(dev->fb_dev, offset); + reg_val |= val; + rockchip_flexbus_writel(dev->fb_dev, offset, reg_val); + v4l2_dbg(4, flexbus_cif_debug, &dev->v4l2_dev, + "write or reg[0x%x]:0x%x!!!\n", + offset, val); +} + +void flexbus_cif_write_register_and(struct flexbus_cif_device *dev, + u32 offset, u32 val) +{ + unsigned int reg_val = 0x0; + + reg_val = rockchip_flexbus_readl(dev->fb_dev, offset); + reg_val &= val; + rockchip_flexbus_writel(dev->fb_dev, offset, reg_val); + v4l2_dbg(4, flexbus_cif_debug, &dev->v4l2_dev, + "write and reg[0x%x]:0x%x!!!\n", + offset, val); +} + +unsigned int flexbus_cif_read_register(struct flexbus_cif_device *dev, u32 offset) +{ + unsigned int val = 0x0; + + val = rockchip_flexbus_readl(dev->fb_dev, offset); + + return val; +} + +/**************************** pipeline operations *****************************/ +static int __cif_pipeline_prepare(struct flexbus_cif_pipeline *p, + struct media_entity *me) +{ + struct v4l2_subdev *sd; + int i; + + p->num_subdevs = 0; + memset(p->subdevs, 0, sizeof(p->subdevs)); + + while (1) { + struct media_pad *pad = NULL; + + /* Find remote source pad */ + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + pad = media_pad_remote_pad_first(spad); + if (pad) + break; + } + + if (!pad) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + p->subdevs[p->num_subdevs++] = sd; + me = &sd->entity; + if (me->num_pads == 1) + break; + } + + return 0; +} + +static int __cif_pipeline_s_cif_clk(struct flexbus_cif_pipeline *p) +{ + return 0; +} + +static int flexbus_cif_pipeline_open(struct flexbus_cif_pipeline *p, + struct media_entity *me, + bool prepare) +{ + int ret; + + if (WARN_ON(!p || !me)) + return -EINVAL; + if (atomic_inc_return(&p->power_cnt) > 1) + return 0; + + /* go through media graphic and get subdevs */ + if (prepare) + __cif_pipeline_prepare(p, me); + + if (!p->num_subdevs) + return -EINVAL; + + ret = __cif_pipeline_s_cif_clk(p); + if (ret < 0) + return ret; + + return 0; +} + +static int flexbus_cif_pipeline_close(struct flexbus_cif_pipeline *p) +{ + atomic_dec_return(&p->power_cnt); + + return 0; +} + +/* + * stream-on order: isp_subdev, mipi dphy, sensor + * stream-off order: mipi dphy, sensor, isp_subdev + */ +static int flexbus_cif_pipeline_set_stream(struct flexbus_cif_pipeline *p, bool on) +{ + int i, ret = 0; + + if ((on && atomic_inc_return(&p->stream_cnt) > 1) || + (!on && atomic_dec_return(&p->stream_cnt) > 0)) + return 0; + + /* phy -> sensor */ + for (i = 0; i < p->num_subdevs; i++) { + ret = v4l2_subdev_call(p->subdevs[i], video, s_stream, on); + if (on && ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + goto err_stream_off; + } + + return 0; + +err_stream_off: + for (--i; i >= 0; --i) + v4l2_subdev_call(p->subdevs[i], video, s_stream, false); + return ret; +} + +static int flexbus_cif_create_link(struct flexbus_cif_device *dev, + struct flexbus_cif_sensor_info *sensor) +{ + struct flexbus_cif_sensor_info linked_sensor; + struct media_entity *source_entity, *sink_entity; + int ret = 0; + u32 pad; + + linked_sensor.sd = sensor->sd; + + memcpy(&linked_sensor.mbus, &sensor->mbus, + sizeof(struct v4l2_mbus_config)); + + for (pad = 0; pad < linked_sensor.sd->entity.num_pads; pad++) { + if (linked_sensor.sd->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) { + if (pad == linked_sensor.sd->entity.num_pads) { + dev_err(dev->dev, + "failed to find src pad for %s\n", + linked_sensor.sd->name); + + break; + } + + if (linked_sensor.mbus.type == V4L2_MBUS_BT656 || + linked_sensor.mbus.type == V4L2_MBUS_PARALLEL) { + source_entity = &linked_sensor.sd->entity; + sink_entity = &dev->stream[FLEXBUS_CIF_STREAM_CIF].vnode.vdev.entity; + + ret = media_create_pad_link(source_entity, + pad, + sink_entity, + 0, + MEDIA_LNK_FL_ENABLED); + if (ret) + dev_err(dev->dev, "failed to create link for %s\n", + linked_sensor.sd->name); + break; + } + } + } + + return ret; +} + +/***************************** media controller *******************************/ +static int flexbus_cif_create_links(struct flexbus_cif_device *dev) +{ + u32 s = 0; + + /* sensor links(or mipi-phy) */ + for (s = 0; s < dev->num_sensors; ++s) { + struct flexbus_cif_sensor_info *sensor = &dev->sensors[s]; + + flexbus_cif_create_link(dev, sensor); + } + + return 0; +} + +static int _set_pipeline_default_fmt(struct flexbus_cif_device *dev) +{ + flexbus_cif_set_default_fmt(dev); + return 0; +} + + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct flexbus_cif_device *dev; + struct flexbus_cif_sensor_info *sensor; + struct v4l2_subdev *sd; + struct v4l2_device *v4l2_dev = NULL; + int ret, index; + + dev = container_of(notifier, struct flexbus_cif_device, notifier); + + v4l2_dev = &dev->v4l2_dev; + + for (index = 0; index < dev->num_sensors; index++) { + sensor = &dev->sensors[index]; + + list_for_each_entry(sd, &v4l2_dev->subdevs, list) { + if (sd->ops) { + if (sd == sensor->sd) { + ret = v4l2_subdev_call(sd, + pad, + get_mbus_config, + 0, + &sensor->mbus); + if (ret) + v4l2_err(v4l2_dev, + "get mbus config failed for linking\n"); + } + } + } + + if (sensor->mbus.type == V4L2_MBUS_PARALLEL || + sensor->mbus.type == V4L2_MBUS_BT656) { + ret = flexbus_cif_register_cif_sof_subdev(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "Err: register cif sof subdev failed!!!\n"); + goto notifier_end; + } + break; + } + } + + ret = flexbus_cif_create_links(dev); + if (ret < 0) + goto unregister_cif; + + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret < 0) + goto unregister_cif; + + ret = _set_pipeline_default_fmt(dev); + if (ret < 0) + goto unregister_cif; + + v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n"); + + return ret; + +unregister_cif: + flexbus_cif_unregister_cif_sof_subdev(dev); +notifier_end: + return ret; +} + +struct flexbus_cif_async_subdev { + struct v4l2_async_subdev asd; + struct v4l2_mbus_config mbus; + int lanes; +}; + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct flexbus_cif_device *cif_dev = container_of(notifier, + struct flexbus_cif_device, notifier); + struct flexbus_cif_async_subdev *s_asd = container_of(asd, + struct flexbus_cif_async_subdev, asd); + + if (cif_dev->num_sensors == ARRAY_SIZE(cif_dev->sensors)) { + v4l2_err(&cif_dev->v4l2_dev, + "%s: the num of subdev is beyond %d\n", + __func__, cif_dev->num_sensors); + return -EBUSY; + } + + cif_dev->sensors[cif_dev->num_sensors].lanes = s_asd->lanes; + 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_err(subdev, "Async registered subdev\n"); + + return 0; +} + +static int flexbus_cif_fwnode_parse(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct flexbus_cif_async_subdev *rk_asd = + container_of(asd, struct flexbus_cif_async_subdev, asd); + + if (vep->bus_type != V4L2_MBUS_BT656 && + vep->bus_type != V4L2_MBUS_PARALLEL) + return 0; + + 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 flexbus_cif_device *cif_dev) +{ + struct v4l2_async_notifier *ntf = &cif_dev->notifier; + struct device *dev = cif_dev->dev; + int ret; + + v4l2_async_nf_init(ntf); + + ret = v4l2_async_nf_parse_fwnode_endpoints( + dev, ntf, sizeof(struct flexbus_cif_async_subdev), flexbus_cif_fwnode_parse); + + if (ret < 0) { + v4l2_err(&cif_dev->v4l2_dev, + "%s: parse fwnode failed\n", __func__); + return ret; + } + + ntf->ops = &subdev_notifier_ops; + + ret = v4l2_async_nf_register(&cif_dev->v4l2_dev, ntf); + + return ret; +} + +/***************************** platform deive *******************************/ + +static int flexbus_cif_register_platform_subdevs(struct flexbus_cif_device *cif_dev) +{ + int stream_num = 0, ret; + + stream_num = FLEXBUS_CIF_SINGLE_STREAM; + ret = flexbus_cif_register_stream_vdevs(cif_dev, stream_num, + false); + + if (ret < 0) { + dev_err(cif_dev->dev, "cif register stream[%d] failed!\n", stream_num); + return -EINVAL; + } + + ret = cif_subdev_notifier(cif_dev); + if (ret < 0) { + v4l2_err(&cif_dev->v4l2_dev, + "Failed to register subdev notifier(%d)\n", ret); + goto err_unreg_stream_vdev; + } + + return 0; +err_unreg_stream_vdev: + flexbus_cif_unregister_stream_vdevs(cif_dev, stream_num); + return ret; +} + +static void flexbus_cif_irq_handler(struct rockchip_flexbus *fb_dev, u32 isr) +{ + struct flexbus_cif_device *cif_dev = (struct flexbus_cif_device *)fb_dev->fb1_data; + + flexbus_cif_irq_pingpong(cif_dev, isr); + flexbus_cif_write_register(cif_dev, FLEXBUS_ICR, isr); + if (cif_dev->err_state && + (!work_busy(&cif_dev->err_state_work.work))) { + cif_dev->err_state_work.err_state = cif_dev->err_state; + cif_dev->err_state = 0; + schedule_work(&cif_dev->err_state_work.work); + } +} + +static int flexbus_cif_plat_init(struct flexbus_cif_device *cif_dev, struct device_node *node) +{ + struct device *dev = cif_dev->dev; + struct v4l2_device *v4l2_dev; + int ret; + + mutex_init(&cif_dev->stream_lock); + atomic_set(&cif_dev->pipe.power_cnt, 0); + atomic_set(&cif_dev->pipe.stream_cnt, 0); + atomic_set(&cif_dev->power_cnt, 0); + cif_dev->pipe.open = flexbus_cif_pipeline_open; + cif_dev->pipe.close = flexbus_cif_pipeline_close; + cif_dev->pipe.set_stream = flexbus_cif_pipeline_set_stream; + cif_dev->id_use_cnt = 0; + INIT_WORK(&cif_dev->err_state_work.work, flexbus_cif_err_print_work); + + flexbus_cif_stream_init(cif_dev, FLEXBUS_CIF_STREAM_CIF); + cif_dev->is_dma_sg_ops = true; + +#if defined(CONFIG_ROCKCHIP_FLEXBUS_CIF_USE_DUMMY_BUF) + cif_dev->is_use_dummybuf = true; +#else + cif_dev->is_use_dummybuf = false; +#endif + + strscpy(cif_dev->media_dev.model, "flexbus-cif", + sizeof(cif_dev->media_dev.model)); + cif_dev->media_dev.dev = dev; + v4l2_dev = &cif_dev->v4l2_dev; + v4l2_dev->mdev = &cif_dev->media_dev; + strscpy(v4l2_dev->name, "flexbus-cif", sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(cif_dev->dev, &cif_dev->v4l2_dev); + if (ret < 0) + return ret; + + media_device_init(&cif_dev->media_dev); + 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 = flexbus_cif_register_platform_subdevs(cif_dev); + if (ret < 0) + goto err_unreg_media_dev; + mutex_lock(&flexbus_cif_mutex); + list_add_tail(&cif_dev->list, &flexbus_cif_device_list); + mutex_unlock(&flexbus_cif_mutex); + + 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 flexbus_cif_plat_uninit(struct flexbus_cif_device *cif_dev) +{ + int stream_num = 0; + + if (cif_dev->active_sensor->mbus.type == V4L2_MBUS_BT656 || + cif_dev->active_sensor->mbus.type == V4L2_MBUS_PARALLEL) + flexbus_cif_unregister_cif_sof_subdev(cif_dev); + + media_device_unregister(&cif_dev->media_dev); + v4l2_device_unregister(&cif_dev->v4l2_dev); + + stream_num = FLEXBUS_CIF_SINGLE_STREAM; + + flexbus_cif_unregister_stream_vdevs(cif_dev, stream_num); + + return 0; +} + +static const struct flexbus_cif_match_data cif_match_data = { + .chip_id = RK_FLEXBUS_CIF_RK3576, +}; + +static const struct of_device_id flexbus_cif_plat_of_match[] = { + { + .compatible = "rockchip,flexbus-cif-rk3576", + .data = &cif_match_data, + }, + {}, +}; + +static int flexbus_cif_plat_probe(struct platform_device *pdev) +{ + struct rockchip_flexbus *rkfb = dev_get_drvdata(pdev->dev.parent); + const struct of_device_id *match; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct flexbus_cif_device *cif_dev; + const struct flexbus_cif_match_data *data = NULL; + int ret; + + dev_info(dev, "czf flexbus_cif driver probe\n"); + + if (rkfb->opmode1 != ROCKCHIP_FLEXBUS1_OPMODE_CIF) { + dev_err(&pdev->dev, "flexbus1 opmode mismatch!\n"); + return -ENODEV; + } + + match = of_match_node(flexbus_cif_plat_of_match, node); + if (IS_ERR(match)) + return PTR_ERR(match); + + 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; + data = match->data; + cif_dev->chip_id = data->chip_id; + + ret = flexbus_cif_plat_init(cif_dev, node); + if (ret) + return ret; + + cif_dev->fb_dev = rkfb; + rkfb->fb1_data = cif_dev; + rkfb->fb1_isr = flexbus_cif_irq_handler; + + if (flexbus_cif_proc_init(cif_dev)) + dev_warn(dev, "dev:%s create proc failed\n", dev_name(dev)); + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int flexbus_cif_plat_remove(struct platform_device *pdev) +{ + struct flexbus_cif_device *cif_dev = platform_get_drvdata(pdev); + + flexbus_cif_plat_uninit(cif_dev); + flexbus_cif_proc_cleanup(cif_dev); + + return 0; +} + +static int __maybe_unused flexbus_cif_runtime_suspend(struct device *dev) +{ + struct flexbus_cif_device *cif_dev = dev_get_drvdata(dev); + int ret = 0; + + if (atomic_dec_return(&cif_dev->power_cnt)) + return 0; + + return (ret > 0) ? 0 : ret; +} + +static int __maybe_unused flexbus_cif_runtime_resume(struct device *dev) +{ + struct flexbus_cif_device *cif_dev = dev_get_drvdata(dev); + int ret = 0; + + if (atomic_inc_return(&cif_dev->power_cnt) > 1) + return 0; + + return (ret > 0) ? 0 : ret; +} + +static int __maybe_unused __flexbus_cif_clr_unready_dev(void) +{ + struct flexbus_cif_device *cif_dev; + + mutex_lock(&flexbus_cif_mutex); + + list_for_each_entry(cif_dev, &flexbus_cif_device_list, list) { + v4l2_async_notifier_clr_unready_dev(&cif_dev->notifier); + } + + mutex_unlock(&flexbus_cif_mutex); + + return 0; +} + +static int flexbus_cif_clr_unready_dev_param_set(const char *val, const struct kernel_param *kp) +{ +#ifdef MODULE + __flexbus_cif_clr_unready_dev(); +#endif + + return 0; +} + +module_param_call(flexbus_cif_clr_unready_dev, flexbus_cif_clr_unready_dev_param_set, NULL, NULL, 0200); +MODULE_PARM_DESC(flexbus_cif_clr_unready_dev, "clear unready devices"); + +#ifndef MODULE +int flexbus_cif_clr_unready_dev(void) +{ + __flexbus_cif_clr_unready_dev(); + + return 0; +} +late_initcall(flexbus_cif_clr_unready_dev); +#endif + +static const struct dev_pm_ops flexbus_cif_plat_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(flexbus_cif_runtime_suspend, flexbus_cif_runtime_resume, NULL) +}; + +struct platform_driver flexbus_cif_plat_drv = { + .driver = { + .name = CIF_DRIVER_NAME, + .of_match_table = of_match_ptr(flexbus_cif_plat_of_match), + .pm = &flexbus_cif_plat_pm_ops, + }, + .probe = flexbus_cif_plat_probe, + .remove = flexbus_cif_plat_remove, +}; + +static int flexbus_cif_plat_drv_init(void) +{ + return platform_driver_register(&flexbus_cif_plat_drv); +} + +static void __exit flexbus_cif_plat_drv_exit(void) +{ + platform_driver_unregister(&flexbus_cif_plat_drv); +} + +module_init(flexbus_cif_plat_drv_init); +module_exit(flexbus_cif_plat_drv_exit); + +MODULE_AUTHOR("Rockchip Camera/ISP team"); +MODULE_DESCRIPTION("Rockchip flexbus cif platform driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/rockchip/flexbus_cif/dev.h b/drivers/media/platform/rockchip/flexbus_cif/dev.h new file mode 100644 index 000000000000..36cffa3d0441 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/dev.h @@ -0,0 +1,467 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#ifndef _FLEXBUS_CIF_DEV_H +#define _FLEXBUS_CIF_DEV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "regs.h" + +#define CIF_DRIVER_NAME "flexbus" +#define CIF_VIDEODEVICE_NAME "stream" + +#define FLEXBUS_CIF_SINGLE_STREAM 1 +#define FLEXBUS_CIF_STREAM_CIF 0 +#define CIF_CIF_VDEV_NAME CIF_VIDEODEVICE_NAME "_cif" + +#define FLEXBUS_CIF_PLANE_Y 0 +#define FLEXBUS_CIF_PLANE_CBCR 1 + +/* + * RK1808 support 5 channel inputs simultaneously: + * cif + 4 mipi virtual channels; + * RV1126/RK356X support 4 channels of BT.656/BT.1120/MIPI + */ +#define FLEXBUS_CIF_MULTI_STREAMS_NUM 5 +#define FLEXBUS_CIF_MAX_STREAM_CIF 4 + +#define FLEXBUS_CIF_MAX_SENSOR 2 +#define FLEXBUS_CIF_MAX_PIPELINE 4 + +#define FLEXBUS_CIF_DEFAULT_WIDTH 640 +#define FLEXBUS_CIF_DEFAULT_HEIGHT 480 + +#define FLEXBUS_CIF_MAX_INTERVAL_NS 5000000 + +/* + * for distinguishing cropping from senosr or usr + */ +#define CROP_SRC_SENSOR_MASK (0x1 << 0) +#define CROP_SRC_USR_MASK (0x1 << 1) + +enum flexbus_cif_yuvaddr_state { + FLEXBUS_CIF_YUV_ADDR_STATE_UPDATE = 0x0, + FLEXBUS_CIF_YUV_ADDR_STATE_INIT = 0x1 +}; + +enum flexbus_cif_state { + FLEXBUS_CIF_STATE_DISABLED, + FLEXBUS_CIF_STATE_READY, + FLEXBUS_CIF_STATE_STREAMING, + FLEXBUS_CIF_STATE_RESET_IN_STREAMING, +}; + +enum flexbus_cif_clk_edge { + FLEXBUS_CIF_CLK_RISING = 0x0, + FLEXBUS_CIF_CLK_FALLING, +}; + +/* + * for distinguishing cropping from senosr or usr + */ +enum flexbus_cif_crop_src { + CROP_SRC_ACT = 0x0, + CROP_SRC_SENSOR, + CROP_SRC_USR, + CROP_SRC_MAX +}; + +enum flexbus_cif_chip_id { + RK_FLEXBUS_CIF_RK3576, +}; + +struct flexbus_cif_match_data { + int chip_id; +}; + +/* + * struct flexbus_cif_pipeline - An CIF hardware pipeline + * + * Capture device call other devices via pipeline + * + * @num_subdevs: number of linked subdevs + * @power_cnt: pipeline power count + * @stream_cnt: stream power count + */ +struct flexbus_cif_pipeline { + struct media_pipeline pipe; + int num_subdevs; + atomic_t power_cnt; + atomic_t stream_cnt; + struct v4l2_subdev *subdevs[FLEXBUS_CIF_MAX_PIPELINE]; + int (*open)(struct flexbus_cif_pipeline *p, + struct media_entity *me, bool prepare); + int (*close)(struct flexbus_cif_pipeline *p); + int (*set_stream)(struct flexbus_cif_pipeline *p, bool on); +}; + +struct flexbus_cif_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; + union { + u32 buff_addr[VIDEO_MAX_PLANES]; + void *vaddr[VIDEO_MAX_PLANES]; + }; + struct dma_buf *dbuf; + u64 fe_timestamp; +}; + +struct flexbus_cif_dummy_buffer { + struct vb2_buffer vb; + struct vb2_queue vb2_queue; + struct list_head list; + struct dma_buf *dbuf; + dma_addr_t dma_addr; + struct page **pages; + void *mem_priv; + void *vaddr; + u32 size; + int dma_fd; + bool is_need_vaddr; + bool is_need_dbuf; + bool is_need_dmafd; + bool is_free; +}; + + +extern int flexbus_cif_debug; + +/* + * struct flexbus_cif_sensor_info - Sensor infomations + * @sd: v4l2 subdev of sensor + * @mbus: media bus configuration + * @fi: v4l2 subdev frame interval + * @lanes: lane num of sensor + * @raw_rect: raw output rectangle of sensor, not crop or selection + * @selection: selection info of sensor + */ +struct flexbus_cif_sensor_info { + struct v4l2_subdev *sd; + struct v4l2_mbus_config mbus; + struct v4l2_subdev_frame_interval fi; + int lanes; + struct v4l2_rect raw_rect; + struct v4l2_subdev_selection selection; + int dsi_input_en; +}; + +enum cif_fmt_type { + CIF_FMT_TYPE_YUV = 0, + CIF_FMT_TYPE_RAW, +}; + +/* + * struct cif_output_fmt - The output format + * + * @bpp: bits per pixel for each cplanes + * @fourcc: pixel format in fourcc + * @fmt_val: the fmt val corresponding to CIF_FOR register + * @csi_fmt_val: the fmt val corresponding to CIF_CSI_ID_CTRL + * @cplanes: number of colour planes + * @mplanes: number of planes for format + * @raw_bpp: bits per pixel for raw format + * @fmt_type: image format, raw or yuv + */ +struct cif_output_fmt { + u8 bpp[VIDEO_MAX_PLANES]; + u32 fourcc; + u32 fmt_val; + u32 cif_yuv_order; + u8 cplanes; + u8 mplanes; + u8 raw_bpp; +}; + +/* + * struct cif_input_fmt - The input mbus format from sensor + * + * @mbus_code: mbus format + * @cif_fmt_val: the fmt val corresponding to CIF_FOR register + * @csi_fmt_val: the fmt val corresponding to CIF_CSI_ID_CTRL + * @fmt_type: image format, raw or yuv + * @field: the field type of the input from sensor + */ +struct cif_input_fmt { + u32 mbus_code; + u32 cif_yuv_order; + enum v4l2_field field; +}; + +struct flexbus_cif_vdev_node { + struct vb2_queue buf_queue; + /* vfd lock */ + struct mutex vlock; + struct video_device vdev; + struct media_pad pad; +}; + +/* + * the mark that csi frame0/1 interrupts enabled + * in CIF_MIPI_INTEN + */ +enum cif_frame_ready { + CIF_CSI_FRAME0_READY = 0x1, + CIF_CSI_FRAME1_READY +}; + +/* struct flexbus_cif_hdr - hdr configured + * @op_mode: hdr optional mode + */ +struct flexbus_cif_hdr { + u8 mode; +}; + +/* struct flexbus_cif_fps_stats - take notes on timestamp of buf + * @frm0_timestamp: timesstamp of buf in frm0 + * @frm1_timestamp: timesstamp of buf in frm1 + */ +struct flexbus_cif_fps_stats { + u64 frm0_timestamp; + u64 frm1_timestamp; +}; + +/* struct flexbus_cif_fps_stats - take notes on timestamp of buf + * @fs_timestamp: timesstamp of frame start + * @fe_timestamp: timesstamp of frame end + * @wk_timestamp: timesstamp of buf send to user in wake up mode + * @readout_time: one frame of readout time + * @early_time: early time of buf send to user + * @total_time: totaltime of readout time in hdr + */ +struct flexbus_cif_readout_stats { + u64 fs_timestamp; + u64 fe_timestamp; + u64 wk_timestamp; + u64 readout_time; + u64 early_time; + u64 total_time; +}; + +/* + * struct flexbus_cif_stream - Stream states TODO + * + * @vbq_lock: lock to protect buf_queue + * @buf_queue: queued buffer list + * @dummy_buf: dummy space to store dropped data + * @crop_enable: crop status when stream off + * @crop_dyn_en: crop status when streaming + * flexbus_cif use shadowsock registers, so it need two buffer at a time + * @curr_buf: the buffer used for current frame + * @next_buf: the buffer used for next frame + * @fps_lock: to protect parameters about calculating fps + */ +struct flexbus_cif_stream { + unsigned id:3; + struct flexbus_cif_device *cif_dev; + struct flexbus_cif_vdev_node vnode; + enum flexbus_cif_state state; + wait_queue_head_t wq_stopped; + unsigned int frame_idx; + u32 frame_phase; + u32 frame_phase_cache; + unsigned int crop_mask; + /* lock between irq and buf_queue */ + struct list_head buf_head; + struct flexbus_cif_buffer *curr_buf; + struct flexbus_cif_buffer *next_buf; + + spinlock_t vbq_lock; /* vfd lock */ + spinlock_t fps_lock; + /* TODO: pad for cif 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[CROP_SRC_MAX]; + struct flexbus_cif_fps_stats fps_stats; + struct flexbus_cif_readout_stats readout; + unsigned int capture_mode; + int dma_en; + int to_en_dma; + int to_stop_dma; + int buf_owner; + int buf_replace_cnt; + struct list_head rx_buf_head_vicap; + unsigned int cur_stream_mode; + int lack_buf_cnt; + unsigned int buf_wake_up_cnt; + struct tasklet_struct vb_done_tasklet; + struct list_head vb_done_list; + int last_rx_buf_idx; + int last_frame_idx; + int new_fource_idx; + atomic_t buf_cnt; + bool stopping; + bool crop_enable; + bool crop_dyn_en; + bool is_compact; + bool is_buf_active; + bool is_high_align; + bool to_en_scale; + bool is_finish_stop_dma; + bool is_in_vblank; + bool is_change_toisp; + bool is_stop_capture; +}; + +struct flexbus_cif_cif_sof_subdev { + struct flexbus_cif_device *cif_dev; + struct v4l2_subdev sd; + atomic_t frm_sync_seq; +}; + +static inline struct flexbus_cif_buffer *to_flexbus_cif_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct flexbus_cif_buffer, vb); +} + +static inline +struct flexbus_cif_vdev_node *vdev_to_node(struct video_device *vdev) +{ + return container_of(vdev, struct flexbus_cif_vdev_node, vdev); +} + +static inline +struct flexbus_cif_stream *to_flexbus_cif_stream(struct flexbus_cif_vdev_node *vnode) +{ + return container_of(vnode, struct flexbus_cif_stream, vnode); +} + +static inline struct flexbus_cif_vdev_node *queue_to_node(struct vb2_queue *q) +{ + return container_of(q, struct flexbus_cif_vdev_node, buf_queue); +} + +static inline struct vb2_queue *to_vb2_queue(struct file *file) +{ + struct flexbus_cif_vdev_node *vnode = video_drvdata(file); + + return &vnode->buf_queue; +} + +enum flexbus_cif_err_state { + FLEXBUS_CIF_ERR_ID0_NOT_BUF = 0x1, + FLEXBUS_CIF_ERR_ID1_NOT_BUF = 0x2, + FLEXBUS_CIF_ERR_ID2_NOT_BUF = 0x4, + FLEXBUS_CIF_ERR_ID3_NOT_BUF = 0x8, + FLEXBUS_CIF_ERR_ID0_TRIG_SIMULT = 0x10, + FLEXBUS_CIF_ERR_ID1_TRIG_SIMULT = 0x20, + FLEXBUS_CIF_ERR_ID2_TRIG_SIMULT = 0x40, + FLEXBUS_CIF_ERR_ID3_TRIG_SIMULT = 0x80, + FLEXBUS_CIF_ERR_SIZE = 0x100, + FLEXBUS_CIF_ERR_OVERFLOW = 0x200, + FLEXBUS_CIF_ERR_BANDWIDTH_LACK = 0x400, + FLEXBUS_CIF_ERR_BUS = 0X800, + FLEXBUS_CIF_ERR_ID0_MULTI_FS = 0x1000, + FLEXBUS_CIF_ERR_ID1_MULTI_FS = 0x2000, + FLEXBUS_CIF_ERR_ID2_MULTI_FS = 0x4000, + FLEXBUS_CIF_ERR_ID3_MULTI_FS = 0x8000, + FLEXBUS_CIF_ERR_PIXEL = 0x10000, + FLEXBUS_CIF_ERR_LINE = 0x20000, +}; + +struct flexbus_cif_err_state_work { + struct work_struct work; + u64 last_timestamp; + u32 err_state; + u32 intstat; + u32 lastline; + u32 lastpixel; + u32 fifo_dnum; +}; + +struct flexbus_cif_irq_stats { + u64 cif_bus_err_cnt; + u64 cif_overflow_cnt; + u64 cif_line_err_cnt; + u64 cif_pix_err_cnt; + u64 cif_size_err_cnt; + u64 cif_bwidth_lack_cnt; + u64 frm_end_cnt[4]; + u64 not_active_buf_cnt[4]; + u64 trig_simult_cnt[4]; + u64 all_err_cnt; +}; + +/* + * struct flexbus_cif_device - ISP platform device + * @base_addr: base register address + * @active_sensor: sensor in-use, set when streaming on + * @stream: capture video device + */ +struct flexbus_cif_device { + struct list_head list; + struct device *dev; + struct rockchip_flexbus *fb_dev; + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct v4l2_async_notifier notifier; + int chip_id; + + struct flexbus_cif_sensor_info sensors[FLEXBUS_CIF_MAX_SENSOR]; + u32 num_sensors; + struct flexbus_cif_sensor_info *active_sensor; + struct flexbus_cif_sensor_info terminal_sensor; + + struct flexbus_cif_stream stream[FLEXBUS_CIF_MULTI_STREAMS_NUM]; + struct flexbus_cif_pipeline pipe; + + atomic_t stream_cnt; + atomic_t power_cnt; + struct mutex stream_lock; /* lock between streams */ + struct flexbus_cif_cif_sof_subdev cif_sof_subdev; + struct flexbus_cif_dummy_buffer dummy_buf; + struct flexbus_cif_irq_stats irq_stats; + + u32 err_state; + struct flexbus_cif_err_state_work err_state_work; + struct proc_dir_entry *proc_dir; + int id_use_cnt; + bool is_use_dummybuf; + bool is_dma_sg_ops; +}; + +//extern struct platform_driver flexbus_cif_plat_drv; + +void flexbus_cif_write_register(struct flexbus_cif_device *dev, + u32 offset, u32 val); +void flexbus_cif_write_register_or(struct flexbus_cif_device *dev, + u32 offset, u32 val); +void flexbus_cif_write_register_and(struct flexbus_cif_device *dev, + u32 offset, u32 val); +unsigned int flexbus_cif_read_register(struct flexbus_cif_device *dev, + u32 offset); +void flexbus_cif_write_grf_register(struct flexbus_cif_device *dev, + u32 offset, u32 val); + +void flexbus_cif_unregister_stream_vdevs(struct flexbus_cif_device *dev, + int stream_num); +int flexbus_cif_register_stream_vdevs(struct flexbus_cif_device *dev, + int stream_num, + bool is_multi_input); +void flexbus_cif_stream_init(struct flexbus_cif_device *dev, u32 id); +void flexbus_cif_set_default_fmt(struct flexbus_cif_device *cif_dev); +void flexbus_cif_irq_pingpong(struct flexbus_cif_device *cif_dev, u32 isr); +int flexbus_cif_register_cif_sof_subdev(struct flexbus_cif_device *dev); +void flexbus_cif_unregister_cif_sof_subdev(struct flexbus_cif_device *dev); + +int flexbus_cif_clr_unready_dev(void); + +void flexbus_cif_err_print_work(struct work_struct *work); + +#endif diff --git a/drivers/media/platform/rockchip/flexbus_cif/procfs.c b/drivers/media/platform/rockchip/flexbus_cif/procfs.c new file mode 100644 index 000000000000..e8e91d81476d --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/procfs.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "dev.h" +#include "procfs.h" + +#ifdef CONFIG_PROC_FS + +static const struct { + const char *name; + u32 mbus_code; +} mbus_formats[] = { + /* media bus code */ + { "RGB444_1X12", MEDIA_BUS_FMT_RGB444_1X12 }, + { "RGB444_2X8_PADHI_BE", MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE }, + { "RGB444_2X8_PADHI_LE", MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE }, + { "RGB555_2X8_PADHI_BE", MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE }, + { "RGB555_2X8_PADHI_LE", MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE }, + { "RGB565_1X16", MEDIA_BUS_FMT_RGB565_1X16 }, + { "BGR565_2X8_BE", MEDIA_BUS_FMT_BGR565_2X8_BE }, + { "BGR565_2X8_LE", MEDIA_BUS_FMT_BGR565_2X8_LE }, + { "RGB565_2X8_BE", MEDIA_BUS_FMT_RGB565_2X8_BE }, + { "RGB565_2X8_LE", MEDIA_BUS_FMT_RGB565_2X8_LE }, + { "RGB666_1X18", MEDIA_BUS_FMT_RGB666_1X18 }, + { "RBG888_1X24", MEDIA_BUS_FMT_RBG888_1X24 }, + { "RGB666_1X24_CPADHI", MEDIA_BUS_FMT_RGB666_1X24_CPADHI }, + { "RGB666_1X7X3_SPWG", MEDIA_BUS_FMT_RGB666_1X7X3_SPWG }, + { "BGR888_1X24", MEDIA_BUS_FMT_BGR888_1X24 }, + { "GBR888_1X24", MEDIA_BUS_FMT_GBR888_1X24 }, + { "RGB888_1X24", MEDIA_BUS_FMT_RGB888_1X24 }, + { "RGB888_2X12_BE", MEDIA_BUS_FMT_RGB888_2X12_BE }, + { "RGB888_2X12_LE", MEDIA_BUS_FMT_RGB888_2X12_LE }, + { "RGB888_1X7X4_SPWG", MEDIA_BUS_FMT_RGB888_1X7X4_SPWG }, + { "RGB888_1X7X4_JEIDA", MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA }, + { "ARGB8888_1X32", MEDIA_BUS_FMT_ARGB8888_1X32 }, + { "RGB888_1X32_PADHI", MEDIA_BUS_FMT_RGB888_1X32_PADHI }, + { "RGB101010_1X30", MEDIA_BUS_FMT_RGB101010_1X30 }, + { "RGB121212_1X36", MEDIA_BUS_FMT_RGB121212_1X36 }, + { "RGB161616_1X48", MEDIA_BUS_FMT_RGB161616_1X48 }, + { "Y8_1X8", MEDIA_BUS_FMT_Y8_1X8 }, + { "UV8_1X8", MEDIA_BUS_FMT_UV8_1X8 }, + { "UYVY8_1_5X8", MEDIA_BUS_FMT_UYVY8_1_5X8 }, + { "VYUY8_1_5X8", MEDIA_BUS_FMT_VYUY8_1_5X8 }, + { "YUYV8_1_5X8", MEDIA_BUS_FMT_YUYV8_1_5X8 }, + { "YVYU8_1_5X8", MEDIA_BUS_FMT_YVYU8_1_5X8 }, + { "UYVY8_2X8", MEDIA_BUS_FMT_UYVY8_2X8 }, + { "VYUY8_2X8", MEDIA_BUS_FMT_VYUY8_2X8 }, + { "YUYV8_2X8", MEDIA_BUS_FMT_YUYV8_2X8 }, + { "YVYU8_2X8", MEDIA_BUS_FMT_YVYU8_2X8 }, + { "Y10_1X10", MEDIA_BUS_FMT_Y10_1X10 }, + { "Y10_2X8_PADHI_LE", MEDIA_BUS_FMT_Y10_2X8_PADHI_LE }, + { "UYVY10_2X10", MEDIA_BUS_FMT_UYVY10_2X10 }, + { "VYUY10_2X10", MEDIA_BUS_FMT_VYUY10_2X10 }, + { "YUYV10_2X10", MEDIA_BUS_FMT_YUYV10_2X10 }, + { "YVYU10_2X10", MEDIA_BUS_FMT_YVYU10_2X10 }, + { "Y12_1X12", MEDIA_BUS_FMT_Y12_1X12 }, + { "UYVY12_2X12", MEDIA_BUS_FMT_UYVY12_2X12 }, + { "VYUY12_2X12", MEDIA_BUS_FMT_VYUY12_2X12 }, + { "YUYV12_2X12", MEDIA_BUS_FMT_YUYV12_2X12 }, + { "YVYU12_2X12", MEDIA_BUS_FMT_YVYU12_2X12 }, + { "UYVY8_1X16", MEDIA_BUS_FMT_UYVY8_1X16 }, + { "VYUY8_1X16", MEDIA_BUS_FMT_VYUY8_1X16 }, + { "YUYV8_1X16", MEDIA_BUS_FMT_YUYV8_1X16 }, + { "YVYU8_1X16", MEDIA_BUS_FMT_YVYU8_1X16 }, + { "YDYUYDYV8_1X16", MEDIA_BUS_FMT_YDYUYDYV8_1X16 }, + { "UYVY10_1X20", MEDIA_BUS_FMT_UYVY10_1X20 }, + { "VYUY10_1X20", MEDIA_BUS_FMT_VYUY10_1X20 }, + { "YUYV10_1X20", MEDIA_BUS_FMT_YUYV10_1X20 }, + { "YVYU10_1X20", MEDIA_BUS_FMT_YVYU10_1X20 }, + { "VUY8_1X24", MEDIA_BUS_FMT_VUY8_1X24 }, + { "YUV8_1X24", MEDIA_BUS_FMT_YUV8_1X24 }, + { "UYYVYY8_0_5X24", MEDIA_BUS_FMT_UYYVYY8_0_5X24 }, + { "UYVY12_1X24", MEDIA_BUS_FMT_UYVY12_1X24 }, + { "VYUY12_1X24", MEDIA_BUS_FMT_VYUY12_1X24 }, + { "YUYV12_1X24", MEDIA_BUS_FMT_YUYV12_1X24 }, + { "YVYU12_1X24", MEDIA_BUS_FMT_YVYU12_1X24 }, + { "YUV10_1X30", MEDIA_BUS_FMT_YUV10_1X30 }, + { "UYYVYY10_0_5X30", MEDIA_BUS_FMT_UYYVYY10_0_5X30 }, + { "AYUV8_1X32", MEDIA_BUS_FMT_AYUV8_1X32 }, + { "UYYVYY12_0_5X36", MEDIA_BUS_FMT_UYYVYY12_0_5X36 }, + { "YUV12_1X36", MEDIA_BUS_FMT_YUV12_1X36 }, + { "YUV16_1X48", MEDIA_BUS_FMT_YUV16_1X48 }, + { "UYYVYY16_0_5X48", MEDIA_BUS_FMT_UYYVYY16_0_5X48 }, + { "SBGGR8_1X8", MEDIA_BUS_FMT_SBGGR8_1X8 }, + { "SGBRG8_1X8", MEDIA_BUS_FMT_SGBRG8_1X8 }, + { "SGRBG8_1X8", MEDIA_BUS_FMT_SGRBG8_1X8 }, + { "SRGGB8_1X8", MEDIA_BUS_FMT_SRGGB8_1X8 }, + { "SBGGR10_ALAW8_1X8", MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8 }, + { "SGBRG10_ALAW8_1X8", MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8 }, + { "SGRBG10_ALAW8_1X8", MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8 }, + { "SRGGB10_ALAW8_1X8", MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8 }, + { "SBGGR10_DPCM8_1X8", MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8 }, + { "SGBRG10_DPCM8_1X8", MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8 }, + { "SGRBG10_DPCM8_1X8", MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8 }, + { "SRGGB10_DPCM8_1X8", MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8 }, + { "SBGGR10_2X8_PADHI_BE", MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE }, + { "SBGGR10_2X8_PADHI_LE", MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE }, + { "SBGGR10_2X8_PADLO_BE", MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE }, + { "SBGGR10_2X8_PADLO_LE", MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE }, + { "SBGGR10_1X10", MEDIA_BUS_FMT_SBGGR10_1X10 }, + { "SGBRG10_1X10", MEDIA_BUS_FMT_SGBRG10_1X10 }, + { "SGRBG10_1X10", MEDIA_BUS_FMT_SGRBG10_1X10 }, + { "SRGGB10_1X10", MEDIA_BUS_FMT_SRGGB10_1X10 }, + { "SBGGR12_1X12", MEDIA_BUS_FMT_SBGGR12_1X12 }, + { "SGBRG12_1X12", MEDIA_BUS_FMT_SGBRG12_1X12 }, + { "SGRBG12_1X12", MEDIA_BUS_FMT_SGRBG12_1X12 }, + { "SRGGB12_1X12", MEDIA_BUS_FMT_SRGGB12_1X12 }, + { "SBGGR14_1X14", MEDIA_BUS_FMT_SBGGR14_1X14 }, + { "SGBRG14_1X14", MEDIA_BUS_FMT_SGBRG14_1X14 }, + { "SGRBG14_1X14", MEDIA_BUS_FMT_SGRBG14_1X14 }, + { "SRGGB14_1X14", MEDIA_BUS_FMT_SRGGB14_1X14 }, + { "SBGGR16_1X16", MEDIA_BUS_FMT_SBGGR16_1X16 }, + { "SGBRG16_1X16", MEDIA_BUS_FMT_SGBRG16_1X16 }, + { "SGRBG16_1X16", MEDIA_BUS_FMT_SGRBG16_1X16 }, + { "SRGGB16_1X16", MEDIA_BUS_FMT_SRGGB16_1X16 }, + { "JPEG_1X8", MEDIA_BUS_FMT_JPEG_1X8 }, + { "S5C_UYVY_JPEG_1X8", MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8 }, + { "AHSV8888_1X32", MEDIA_BUS_FMT_AHSV8888_1X32 }, + { "FIXED", MEDIA_BUS_FMT_FIXED }, + { "Y8", MEDIA_BUS_FMT_Y8_1X8 }, + { "Y10", MEDIA_BUS_FMT_Y10_1X10 }, + { "Y12", MEDIA_BUS_FMT_Y12_1X12 }, + { "YUYV", MEDIA_BUS_FMT_YUYV8_1X16 }, + { "YUYV1_5X8", MEDIA_BUS_FMT_YUYV8_1_5X8 }, + { "YUYV2X8", MEDIA_BUS_FMT_YUYV8_2X8 }, + { "UYVY", MEDIA_BUS_FMT_UYVY8_1X16 }, + { "UYVY1_5X8", MEDIA_BUS_FMT_UYVY8_1_5X8 }, + { "UYVY2X8", MEDIA_BUS_FMT_UYVY8_2X8 }, + { "VUY24", MEDIA_BUS_FMT_VUY8_1X24 }, + { "SBGGR8", MEDIA_BUS_FMT_SBGGR8_1X8 }, + { "SGBRG8", MEDIA_BUS_FMT_SGBRG8_1X8 }, + { "SGRBG8", MEDIA_BUS_FMT_SGRBG8_1X8 }, + { "SRGGB8", MEDIA_BUS_FMT_SRGGB8_1X8 }, + { "SBGGR10", MEDIA_BUS_FMT_SBGGR10_1X10 }, + { "SGBRG10", MEDIA_BUS_FMT_SGBRG10_1X10 }, + { "SGRBG10", MEDIA_BUS_FMT_SGRBG10_1X10 }, + { "SRGGB10", MEDIA_BUS_FMT_SRGGB10_1X10 }, + { "SBGGR10_DPCM8", MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8 }, + { "SGBRG10_DPCM8", MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8 }, + { "SGRBG10_DPCM8", MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8 }, + { "SRGGB10_DPCM8", MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8 }, + { "SBGGR12", MEDIA_BUS_FMT_SBGGR12_1X12 }, + { "SGBRG12", MEDIA_BUS_FMT_SGBRG12_1X12 }, + { "SGRBG12", MEDIA_BUS_FMT_SGRBG12_1X12 }, + { "SRGGB12", MEDIA_BUS_FMT_SRGGB12_1X12 }, + { "AYUV32", MEDIA_BUS_FMT_AYUV8_1X32 }, + { "RBG24", MEDIA_BUS_FMT_RBG888_1X24 }, + { "RGB32", MEDIA_BUS_FMT_RGB888_1X32_PADHI }, + { "ARGB32", MEDIA_BUS_FMT_ARGB8888_1X32 }, + + /* v4l2 fourcc code */ + { "NV16", V4L2_PIX_FMT_NV16}, + { "NV61", V4L2_PIX_FMT_NV61}, + { "NV12", V4L2_PIX_FMT_NV12}, + { "NV21", V4L2_PIX_FMT_NV21}, + { "YUYV", V4L2_PIX_FMT_YUYV}, + { "YVYU", V4L2_PIX_FMT_YVYU}, + { "UYVY", V4L2_PIX_FMT_UYVY}, + { "VYUY", V4L2_PIX_FMT_VYUY}, + { "RGB3", V4L2_PIX_FMT_RGB24}, + { "RGBP", V4L2_PIX_FMT_RGB565}, + { "BGRH", V4L2_PIX_FMT_BGR666}, + { "RGGB", V4L2_PIX_FMT_SRGGB8}, + { "GRBG", V4L2_PIX_FMT_SGRBG8}, + { "GBRG", V4L2_PIX_FMT_SGBRG8}, + { "BA81", V4L2_PIX_FMT_SBGGR8}, + { "RG10", V4L2_PIX_FMT_SRGGB10}, + { "BA10", V4L2_PIX_FMT_SGRBG10}, + { "GB10", V4L2_PIX_FMT_SGBRG10}, + { "BG10", V4L2_PIX_FMT_SBGGR10}, + { "RG12", V4L2_PIX_FMT_SRGGB12}, + { "BA12", V4L2_PIX_FMT_SGRBG12}, + { "GB12", V4L2_PIX_FMT_SGBRG12}, + { "BG12", V4L2_PIX_FMT_SBGGR12}, + { "BYR2", V4L2_PIX_FMT_SBGGR16}, + { "Y16 ", V4L2_PIX_FMT_Y16}, +}; + +static const char *flexbus_cif_pixelcode_to_string(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mbus_formats); ++i) { + if (mbus_formats[i].mbus_code == mbus_code) + return mbus_formats[i].name; + } + + return "unknown"; +} + +static void flexbus_cif_show_format(struct flexbus_cif_device *dev, struct seq_file *f) +{ + struct flexbus_cif_stream *stream = &dev->stream[0]; + struct flexbus_cif_pipeline *pipe = &dev->pipe; + struct flexbus_cif_sensor_info *sensor = &dev->terminal_sensor; + struct v4l2_rect *rect = &sensor->raw_rect; + struct v4l2_subdev_frame_interval *interval = &sensor->fi; + struct v4l2_subdev_selection *sel = &sensor->selection; + u32 mbus_flags; + u64 fps, timestamp0, timestamp1; + unsigned long flags; + + if (atomic_read(&pipe->stream_cnt) < 1) + return; + + if (sensor) { + seq_puts(f, "Input Info:\n"); + + seq_printf(f, "\tsrc subdev:%s\n", sensor->sd->name); + mbus_flags = sensor->mbus.bus.parallel.flags; + if (sensor->mbus.type == V4L2_MBUS_PARALLEL || + sensor->mbus.type == V4L2_MBUS_BT656) { + seq_printf(f, "\tinterface:%s\n", + sensor->mbus.type == V4L2_MBUS_PARALLEL ? "BT601" : "BT656/BT1120"); + seq_printf(f, "\thref_pol:%s\n", + mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH ? "high active" : "low active"); + seq_printf(f, "\tvsync_pol:%s\n", + mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH ? "high active" : "low active"); + } + + seq_printf(f, "\tformat:%s/%ux%u@%d\n", + flexbus_cif_pixelcode_to_string(stream->cif_fmt_in->mbus_code), + rect->width, rect->height, + interval->interval.denominator / interval->interval.numerator); + + seq_printf(f, "\tcrop.bounds:(%u, %u)/%ux%u\n", + sel->r.left, sel->r.top, + sel->r.width, sel->r.height); + + spin_lock_irqsave(&stream->fps_lock, flags); + timestamp0 = stream->fps_stats.frm0_timestamp; + timestamp1 = stream->fps_stats.frm1_timestamp; + spin_unlock_irqrestore(&stream->fps_lock, flags); + fps = timestamp0 > timestamp1 ? + timestamp0 - timestamp1 : timestamp1 - timestamp0; + fps = div_u64(fps, 1000000); + + seq_puts(f, "Output Info:\n"); + seq_printf(f, "\tformat:%s/%ux%u(%u,%u)\n", + flexbus_cif_pixelcode_to_string(stream->cif_fmt_out->fourcc), + sel->r.width, sel->r.height, + sel->r.left, sel->r.top); + seq_printf(f, "\tframe amount:%d\n", stream->frame_idx - 1); + seq_printf(f, "\trate:%llu ms\n", fps); + fps = div_u64(1000, fps); + seq_printf(f, "\tfps:%llu\n", fps); + seq_puts(f, "\tirq statistics:\n"); + seq_printf(f, "\t\t\ttotal:%llu\n", + dev->irq_stats.frm_end_cnt[0] + + dev->irq_stats.frm_end_cnt[1] + + dev->irq_stats.frm_end_cnt[2] + + dev->irq_stats.frm_end_cnt[3] + + dev->irq_stats.all_err_cnt); + if (sensor->mbus.type == V4L2_MBUS_PARALLEL || + sensor->mbus.type == V4L2_MBUS_BT656) { + seq_printf(f, "\t\t\tcif bus err:%llu\n", dev->irq_stats.cif_bus_err_cnt); + seq_printf(f, "\t\t\tcif pix err:%llu\n", dev->irq_stats.cif_pix_err_cnt); + seq_printf(f, "\t\t\tcif line err:%llu\n", dev->irq_stats.cif_line_err_cnt); + seq_printf(f, "\t\t\tcif over flow:%llu\n", dev->irq_stats.cif_overflow_cnt); + seq_printf(f, "\t\t\tcif bandwidth lack:%llu\n", + dev->irq_stats.cif_bwidth_lack_cnt); + seq_printf(f, "\t\t\tcif size err:%llu\n", dev->irq_stats.cif_size_err_cnt); + } + seq_printf(f, "\t\t\tnot active buf cnt:%llu %llu %llu %llu\n", + dev->irq_stats.not_active_buf_cnt[0], + dev->irq_stats.not_active_buf_cnt[1], + dev->irq_stats.not_active_buf_cnt[2], + dev->irq_stats.not_active_buf_cnt[3]); + seq_printf(f, "\t\t\tframe dma end:%llu %llu %llu %llu\n", + dev->irq_stats.frm_end_cnt[0], + dev->irq_stats.frm_end_cnt[1], + dev->irq_stats.frm_end_cnt[2], + dev->irq_stats.frm_end_cnt[3]); + seq_printf(f, "buf_cnt in drv: %d %d %d %d\n", + atomic_read(&dev->stream[0].buf_cnt), + atomic_read(&dev->stream[1].buf_cnt), + atomic_read(&dev->stream[2].buf_cnt), + atomic_read(&dev->stream[3].buf_cnt)); + } +} + +static int flexbus_cif_proc_show(struct seq_file *f, void *v) +{ + struct flexbus_cif_device *dev = f->private; + + if (dev) + flexbus_cif_show_format(dev, f); + else + seq_puts(f, "dev null\n"); + + return 0; +} + +static int flexbus_cif_proc_open(struct inode *inode, struct file *file) +{ + struct flexbus_cif_device *data = pde_data(inode); + + return single_open(file, flexbus_cif_proc_show, data); +} + +static const struct proc_ops flexbus_cif_proc_fops = { + .proc_open = flexbus_cif_proc_open, + .proc_release = single_release, + .proc_read = seq_read, + .proc_lseek = seq_lseek, +}; + +int flexbus_cif_proc_init(struct flexbus_cif_device *dev) +{ + + dev->proc_dir = proc_create_data(dev_name(dev->dev), 0444, + NULL, &flexbus_cif_proc_fops, + dev); + if (!dev->proc_dir) { + dev_err(dev->dev, "create proc/%s failed!\n", + dev_name(dev->dev)); + return -ENODEV; + } + + return 0; +} + +void flexbus_cif_proc_cleanup(struct flexbus_cif_device *dev) +{ + remove_proc_entry(dev_name(dev->dev), NULL); +} + +#endif diff --git a/drivers/media/platform/rockchip/flexbus_cif/procfs.h b/drivers/media/platform/rockchip/flexbus_cif/procfs.h new file mode 100644 index 000000000000..aa0d28de8546 --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/procfs.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ +#ifndef _FLEXBUS_CIF_PROCFS_H +#define _FLEXBUS_CIF_PROCFS_H + +#ifdef CONFIG_PROC_FS + +int flexbus_cif_proc_init(struct flexbus_cif_device *dev); +void flexbus_cif_proc_cleanup(struct flexbus_cif_device *dev); + +#else + +static inline int flexbus_cif_proc_init(struct rkisp_device *dev) +{ + return 0; +} +static inline void flexbus_cif_proc_cleanup(struct rkisp_device *dev) +{ + +} + +#endif + +#endif diff --git a/drivers/media/platform/rockchip/flexbus_cif/regs.h b/drivers/media/platform/rockchip/flexbus_cif/regs.h new file mode 100644 index 000000000000..fd82155ca54c --- /dev/null +++ b/drivers/media/platform/rockchip/flexbus_cif/regs.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Flexbus CIF Driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#ifndef _FLEXBUS_CIF_REGS_H +#define _FLEXBUS_CIF_REGS_H + +#define GRF_FLEXBUS1_CON (0x6400) + +#define CIF_INPUT_ORDER_YVYU (0x0 << 0) +#define CIF_INPUT_ORDER_VYUY (0x1 << 0) +#define CIF_INPUT_ORDER_YUYV (0x2 << 0) +#define CIF_INPUT_ORDER_UYVY (0x3 << 0) + +#define CIF_OUTPUT_ORDER_YVYU (0x0 << 2) +#define CIF_OUTPUT_ORDER_VYUY (0x1 << 2) +#define CIF_OUTPUT_ORDER_YUYV (0x2 << 2) +#define CIF_OUTPUT_ORDER_UYVY (0x3 << 2) + + +#define YUV_INPUT_422 (0x00 << 7) +#define CIF_YUV_STORED_BIT_WIDTH (8U) +#define CIF_CROP_Y_SHIFT 16 + +#define CIF_FIFO_OVERFLOW FLEXBUS_RX_OVF_ISR +#define CIF_BANDWIDTH_LACK FLEXBUS_RX_UDF_ISR +#define CIF_SIZE_ERR FLEXBUS_DVP_FRAME_AB_ISR +#define CIF_DMA_START FLEXBUS_DVP_FRAME_START_ISR +#define CIF_DMA_END (FLEXBUS_DMA_DST0_ISR | FLEXBUS_DMA_DST1_ISR) + +#define HSY_HIGH_ACTIVE (BIT(1)) +#define HSY_LOW_ACTIVE (0) +#define VSY_HIGH_ACTIVE (BIT(0)) +#define VSY_LOW_ACTIVE (0) +#define CIF_RAW_STORED_BIT_WIDTH (8U) +#define FLEXBUS_IMR_DMA_DST0_SHIFT (10U) + +#define CIF_YUV2RGB_ENABLE (0x1 << 0) +#define CIF_YUV2RGB_B_LSB (0x1 << 3) + +#define CIF_YUV2RGB_BT601_LIMIT (0 << 1) +#define CIF_YUV2RGB_BT601_FULL (2 << 1) +#define CIF_YUV2RGB_BT709_LIMIT (3 << 1) + +#endif +