| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * DW100 Hardware dewarper |
| * |
| * Copyright 2022 NXP |
| * Author: Xavier Roumegue (xavier.roumegue@oss.nxp.com) |
| * |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/debugfs.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/minmax.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| |
| #include <media/v4l2-ctrls.h> |
| #include <media/v4l2-device.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-mem2mem.h> |
| #include <media/videobuf2-dma-contig.h> |
| |
| #include <uapi/linux/dw100.h> |
| |
| #include "dw100_regs.h" |
| |
| #define DRV_NAME "dw100" |
| |
| #define DW100_MIN_W 176u |
| #define DW100_MIN_H 144u |
| #define DW100_MAX_W 4096u |
| #define DW100_MAX_H 3072u |
| #define DW100_ALIGN_W 3 |
| #define DW100_ALIGN_H 3 |
| |
| #define DW100_BLOCK_SIZE 16 |
| |
| #define DW100_DEF_W 640u |
| #define DW100_DEF_H 480u |
| #define DW100_DEF_LUT_W (DIV_ROUND_UP(DW100_DEF_W, DW100_BLOCK_SIZE) + 1) |
| #define DW100_DEF_LUT_H (DIV_ROUND_UP(DW100_DEF_H, DW100_BLOCK_SIZE) + 1) |
| |
| /* |
| * 16 controls have been reserved for this driver for future extension, but |
| * let's limit the related driver allocation to the effective number of controls |
| * in use. |
| */ |
| #define DW100_MAX_CTRLS 1 |
| #define DW100_CTRL_DEWARPING_MAP 0 |
| |
| enum { |
| DW100_QUEUE_SRC = 0, |
| DW100_QUEUE_DST = 1, |
| }; |
| |
| enum { |
| DW100_FMT_CAPTURE = BIT(0), |
| DW100_FMT_OUTPUT = BIT(1), |
| }; |
| |
| struct dw100_device { |
| struct platform_device *pdev; |
| struct v4l2_m2m_dev *m2m_dev; |
| struct v4l2_device v4l2_dev; |
| struct video_device vfd; |
| struct media_device mdev; |
| /* Video device lock */ |
| struct mutex vfd_mutex; |
| void __iomem *mmio; |
| struct clk_bulk_data *clks; |
| int num_clks; |
| struct dentry *debugfs_root; |
| }; |
| |
| struct dw100_q_data { |
| struct v4l2_pix_format_mplane pix_fmt; |
| unsigned int sequence; |
| const struct dw100_fmt *fmt; |
| struct v4l2_rect crop; |
| }; |
| |
| struct dw100_ctx { |
| struct v4l2_fh fh; |
| struct dw100_device *dw_dev; |
| struct v4l2_ctrl_handler hdl; |
| struct v4l2_ctrl *ctrls[DW100_MAX_CTRLS]; |
| /* per context m2m queue lock */ |
| struct mutex vq_mutex; |
| |
| /* Look Up Table for pixel remapping */ |
| unsigned int *map; |
| dma_addr_t map_dma; |
| size_t map_size; |
| unsigned int map_width; |
| unsigned int map_height; |
| bool user_map_is_set; |
| |
| /* Source and destination queue data */ |
| struct dw100_q_data q_data[2]; |
| }; |
| |
| static const struct v4l2_frmsize_stepwise dw100_frmsize_stepwise = { |
| .min_width = DW100_MIN_W, |
| .min_height = DW100_MIN_H, |
| .max_width = DW100_MAX_W, |
| .max_height = DW100_MAX_H, |
| .step_width = 1UL << DW100_ALIGN_W, |
| .step_height = 1UL << DW100_ALIGN_H, |
| }; |
| |
| static const struct dw100_fmt { |
| u32 fourcc; |
| u32 types; |
| u32 reg_format; |
| bool reg_swap_uv; |
| } formats[] = { |
| { |
| .fourcc = V4L2_PIX_FMT_NV16, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, |
| .reg_swap_uv = false, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV16M, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, |
| .reg_swap_uv = false, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV61, |
| .types = DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, |
| .reg_swap_uv = true, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV61M, |
| .types = DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, |
| .reg_swap_uv = true, |
| }, { |
| .fourcc = V4L2_PIX_FMT_YUYV, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, |
| .reg_swap_uv = false, |
| }, { |
| .fourcc = V4L2_PIX_FMT_UYVY, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, |
| .reg_swap_uv = true, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV12, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, |
| .reg_swap_uv = false, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV12M, |
| .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, |
| .reg_swap_uv = false, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV21, |
| .types = DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, |
| .reg_swap_uv = true, |
| }, { |
| .fourcc = V4L2_PIX_FMT_NV21M, |
| .types = DW100_FMT_CAPTURE, |
| .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, |
| .reg_swap_uv = true, |
| }, |
| }; |
| |
| static inline int to_dw100_fmt_type(enum v4l2_buf_type type) |
| { |
| if (V4L2_TYPE_IS_OUTPUT(type)) |
| return DW100_FMT_OUTPUT; |
| else |
| return DW100_FMT_CAPTURE; |
| } |
| |
| static const struct dw100_fmt *dw100_find_pixel_format(u32 pixel_format, |
| int fmt_type) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(formats); i++) { |
| const struct dw100_fmt *fmt = &formats[i]; |
| |
| if (fmt->fourcc == pixel_format && fmt->types & fmt_type) |
| return fmt; |
| } |
| |
| return NULL; |
| } |
| |
| static const struct dw100_fmt *dw100_find_format(struct v4l2_format *f) |
| { |
| return dw100_find_pixel_format(f->fmt.pix_mp.pixelformat, |
| to_dw100_fmt_type(f->type)); |
| } |
| |
| static inline u32 dw100_read(struct dw100_device *dw_dev, u32 reg) |
| { |
| return readl(dw_dev->mmio + reg); |
| } |
| |
| static inline void dw100_write(struct dw100_device *dw_dev, u32 reg, u32 val) |
| { |
| writel(val, dw_dev->mmio + reg); |
| } |
| |
| static inline int dw100_dump_regs(struct seq_file *m) |
| { |
| struct dw100_device *dw_dev = m->private; |
| #define __DECLARE_REG(x) { #x, x } |
| unsigned int i; |
| static const struct reg_desc { |
| const char * const name; |
| unsigned int addr; |
| } dw100_regs[] = { |
| __DECLARE_REG(DW100_DEWARP_ID), |
| __DECLARE_REG(DW100_DEWARP_CTRL), |
| __DECLARE_REG(DW100_MAP_LUT_ADDR), |
| __DECLARE_REG(DW100_MAP_LUT_SIZE), |
| __DECLARE_REG(DW100_MAP_LUT_ADDR2), |
| __DECLARE_REG(DW100_MAP_LUT_SIZE2), |
| __DECLARE_REG(DW100_SRC_IMG_Y_BASE), |
| __DECLARE_REG(DW100_SRC_IMG_UV_BASE), |
| __DECLARE_REG(DW100_SRC_IMG_SIZE), |
| __DECLARE_REG(DW100_SRC_IMG_STRIDE), |
| __DECLARE_REG(DW100_DST_IMG_Y_BASE), |
| __DECLARE_REG(DW100_DST_IMG_UV_BASE), |
| __DECLARE_REG(DW100_DST_IMG_SIZE), |
| __DECLARE_REG(DW100_DST_IMG_STRIDE), |
| __DECLARE_REG(DW100_DST_IMG_Y_SIZE1), |
| __DECLARE_REG(DW100_DST_IMG_UV_SIZE1), |
| __DECLARE_REG(DW100_SRC_IMG_Y_BASE2), |
| __DECLARE_REG(DW100_SRC_IMG_UV_BASE2), |
| __DECLARE_REG(DW100_SRC_IMG_SIZE2), |
| __DECLARE_REG(DW100_SRC_IMG_STRIDE2), |
| __DECLARE_REG(DW100_DST_IMG_Y_BASE2), |
| __DECLARE_REG(DW100_DST_IMG_UV_BASE2), |
| __DECLARE_REG(DW100_DST_IMG_SIZE2), |
| __DECLARE_REG(DW100_DST_IMG_STRIDE2), |
| __DECLARE_REG(DW100_DST_IMG_Y_SIZE2), |
| __DECLARE_REG(DW100_DST_IMG_UV_SIZE2), |
| __DECLARE_REG(DW100_SWAP_CONTROL), |
| __DECLARE_REG(DW100_VERTICAL_SPLIT_LINE), |
| __DECLARE_REG(DW100_HORIZON_SPLIT_LINE), |
| __DECLARE_REG(DW100_SCALE_FACTOR), |
| __DECLARE_REG(DW100_ROI_START), |
| __DECLARE_REG(DW100_BOUNDARY_PIXEL), |
| __DECLARE_REG(DW100_INTERRUPT_STATUS), |
| __DECLARE_REG(DW100_BUS_CTRL), |
| __DECLARE_REG(DW100_BUS_CTRL1), |
| __DECLARE_REG(DW100_BUS_TIME_OUT_CYCLE), |
| }; |
| |
| for (i = 0; i < ARRAY_SIZE(dw100_regs); i++) |
| seq_printf(m, "%s: %#x\n", dw100_regs[i].name, |
| dw100_read(dw_dev, dw100_regs[i].addr)); |
| |
| return 0; |
| } |
| |
| static inline struct dw100_ctx *dw100_file2ctx(struct file *file) |
| { |
| return container_of(file->private_data, struct dw100_ctx, fh); |
| } |
| |
| static struct dw100_q_data *dw100_get_q_data(struct dw100_ctx *ctx, |
| enum v4l2_buf_type type) |
| { |
| if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) |
| return &ctx->q_data[DW100_QUEUE_SRC]; |
| else |
| return &ctx->q_data[DW100_QUEUE_DST]; |
| } |
| |
| static u32 dw100_get_n_vertices_from_length(u32 length) |
| { |
| return DIV_ROUND_UP(length, DW100_BLOCK_SIZE) + 1; |
| } |
| |
| static u16 dw100_map_convert_to_uq12_4(u32 a) |
| { |
| return (u16)((a & 0xfff) << 4); |
| } |
| |
| static u32 dw100_map_format_coordinates(u16 xq, u16 yq) |
| { |
| return (u32)((yq << 16) | xq); |
| } |
| |
| static u32 *dw100_get_user_map(struct dw100_ctx *ctx) |
| { |
| struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; |
| |
| return ctrl->p_cur.p_u32; |
| } |
| |
| /* |
| * Create the dewarp map used by the hardware from the V4L2 control values which |
| * have been initialized with an identity map or set by the application. |
| */ |
| static int dw100_create_mapping(struct dw100_ctx *ctx) |
| { |
| u32 *user_map; |
| |
| if (ctx->map) |
| dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, |
| ctx->map, ctx->map_dma); |
| |
| ctx->map = dma_alloc_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, |
| &ctx->map_dma, GFP_KERNEL); |
| |
| if (!ctx->map) |
| return -ENOMEM; |
| |
| user_map = dw100_get_user_map(ctx); |
| memcpy(ctx->map, user_map, ctx->map_size); |
| |
| dev_dbg(&ctx->dw_dev->pdev->dev, |
| "%ux%u %s mapping created (d:%pad-c:%p) for stream %ux%u->%ux%u\n", |
| ctx->map_width, ctx->map_height, |
| ctx->user_map_is_set ? "user" : "identity", |
| &ctx->map_dma, ctx->map, |
| ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, |
| ctx->q_data[DW100_QUEUE_DST].pix_fmt.height, |
| ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, |
| ctx->q_data[DW100_QUEUE_DST].pix_fmt.height); |
| |
| return 0; |
| } |
| |
| static void dw100_destroy_mapping(struct dw100_ctx *ctx) |
| { |
| if (ctx->map) { |
| dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, |
| ctx->map, ctx->map_dma); |
| ctx->map = NULL; |
| } |
| } |
| |
| static int dw100_s_ctrl(struct v4l2_ctrl *ctrl) |
| { |
| struct dw100_ctx *ctx = |
| container_of(ctrl->handler, struct dw100_ctx, hdl); |
| |
| switch (ctrl->id) { |
| case V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP: |
| ctx->user_map_is_set = true; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static const struct v4l2_ctrl_ops dw100_ctrl_ops = { |
| .s_ctrl = dw100_s_ctrl, |
| }; |
| |
| /* |
| * Initialize the dewarping map with an identity mapping. |
| * |
| * A 16 pixels cell size grid is mapped on the destination image. |
| * The last cells width/height might be lesser than 16 if the destination image |
| * width/height is not divisible by 16. This dewarping grid map specifies the |
| * source image pixel location (x, y) on each grid intersection point. |
| * Bilinear interpolation is used to compute inner cell points locations. |
| * |
| * The coordinates are saved in UQ12.4 fixed point format. |
| */ |
| static void dw100_ctrl_dewarping_map_init(const struct v4l2_ctrl *ctrl, |
| u32 from_idx, |
| union v4l2_ctrl_ptr ptr) |
| { |
| struct dw100_ctx *ctx = |
| container_of(ctrl->handler, struct dw100_ctx, hdl); |
| |
| u32 sw, sh, mw, mh, idx; |
| u16 qx, qy, qdx, qdy, qsh, qsw; |
| u32 *map = ctrl->p_cur.p_u32; |
| |
| sw = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width; |
| sh = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.height; |
| |
| mw = ctrl->dims[0]; |
| mh = ctrl->dims[1]; |
| |
| qsw = dw100_map_convert_to_uq12_4(sw); |
| qsh = dw100_map_convert_to_uq12_4(sh); |
| qdx = qsw / (mw - 1); |
| qdy = qsh / (mh - 1); |
| |
| ctx->map_width = mw; |
| ctx->map_height = mh; |
| ctx->map_size = mh * mw * sizeof(u32); |
| |
| for (idx = from_idx; idx < ctrl->elems; idx++) { |
| qy = min_t(u32, (idx / mw) * qdy, qsh); |
| qx = min_t(u32, (idx % mw) * qdx, qsw); |
| map[idx] = dw100_map_format_coordinates(qx, qy); |
| } |
| |
| ctx->user_map_is_set = false; |
| } |
| |
| static const struct v4l2_ctrl_type_ops dw100_ctrl_type_ops = { |
| .init = dw100_ctrl_dewarping_map_init, |
| .validate = v4l2_ctrl_type_op_validate, |
| .log = v4l2_ctrl_type_op_log, |
| .equal = v4l2_ctrl_type_op_equal, |
| }; |
| |
| static const struct v4l2_ctrl_config controls[] = { |
| [DW100_CTRL_DEWARPING_MAP] = { |
| .ops = &dw100_ctrl_ops, |
| .type_ops = &dw100_ctrl_type_ops, |
| .id = V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP, |
| .name = "Dewarping Vertex Map", |
| .type = V4L2_CTRL_TYPE_U32, |
| .min = 0x00000000, |
| .max = 0xffffffff, |
| .step = 1, |
| .def = 0, |
| .dims = { DW100_DEF_LUT_W, DW100_DEF_LUT_H }, |
| }, |
| }; |
| |
| static int dw100_queue_setup(struct vb2_queue *vq, |
| unsigned int *nbuffers, unsigned int *nplanes, |
| unsigned int sizes[], struct device *alloc_devs[]) |
| { |
| struct dw100_ctx *ctx = vb2_get_drv_priv(vq); |
| const struct v4l2_pix_format_mplane *format; |
| unsigned int i; |
| |
| format = &dw100_get_q_data(ctx, vq->type)->pix_fmt; |
| |
| if (*nplanes) { |
| if (*nplanes != format->num_planes) |
| return -EINVAL; |
| |
| for (i = 0; i < *nplanes; ++i) { |
| if (sizes[i] < format->plane_fmt[i].sizeimage) |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| *nplanes = format->num_planes; |
| |
| for (i = 0; i < format->num_planes; ++i) |
| sizes[i] = format->plane_fmt[i].sizeimage; |
| |
| return 0; |
| } |
| |
| static int dw100_buf_prepare(struct vb2_buffer *vb) |
| { |
| unsigned int i; |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| struct dw100_device *dw_dev = ctx->dw_dev; |
| const struct v4l2_pix_format_mplane *pix_fmt = |
| &dw100_get_q_data(ctx, vb->vb2_queue->type)->pix_fmt; |
| |
| if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { |
| if (vbuf->field != V4L2_FIELD_NONE) { |
| dev_dbg(&dw_dev->pdev->dev, "%x field isn't supported\n", |
| vbuf->field); |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < pix_fmt->num_planes; i++) { |
| unsigned long size = pix_fmt->plane_fmt[i].sizeimage; |
| |
| if (vb2_plane_size(vb, i) < size) { |
| dev_dbg(&dw_dev->pdev->dev, |
| "User buffer too small (%lu < %lu)\n", |
| vb2_plane_size(vb, i), size); |
| return -EINVAL; |
| } |
| |
| vb2_set_plane_payload(vb, i, size); |
| } |
| |
| return 0; |
| } |
| |
| static void dw100_buf_queue(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| |
| v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); |
| } |
| |
| static void dw100_return_all_buffers(struct vb2_queue *q, |
| enum vb2_buffer_state state) |
| { |
| struct dw100_ctx *ctx = vb2_get_drv_priv(q); |
| struct vb2_v4l2_buffer *vbuf; |
| |
| for (;;) { |
| if (V4L2_TYPE_IS_OUTPUT(q->type)) |
| vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); |
| else |
| vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); |
| if (!vbuf) |
| return; |
| v4l2_m2m_buf_done(vbuf, state); |
| } |
| } |
| |
| static int dw100_start_streaming(struct vb2_queue *q, unsigned int count) |
| { |
| struct dw100_ctx *ctx = vb2_get_drv_priv(q); |
| struct dw100_q_data *q_data = dw100_get_q_data(ctx, q->type); |
| int ret; |
| |
| q_data->sequence = 0; |
| |
| ret = dw100_create_mapping(ctx); |
| if (ret) |
| goto err; |
| |
| ret = pm_runtime_resume_and_get(&ctx->dw_dev->pdev->dev); |
| if (ret) { |
| dw100_destroy_mapping(ctx); |
| goto err; |
| } |
| |
| return 0; |
| err: |
| dw100_return_all_buffers(q, VB2_BUF_STATE_QUEUED); |
| return ret; |
| } |
| |
| static void dw100_stop_streaming(struct vb2_queue *q) |
| { |
| struct dw100_ctx *ctx = vb2_get_drv_priv(q); |
| |
| dw100_return_all_buffers(q, VB2_BUF_STATE_ERROR); |
| |
| pm_runtime_put_sync(&ctx->dw_dev->pdev->dev); |
| |
| dw100_destroy_mapping(ctx); |
| } |
| |
| static const struct vb2_ops dw100_qops = { |
| .queue_setup = dw100_queue_setup, |
| .buf_prepare = dw100_buf_prepare, |
| .buf_queue = dw100_buf_queue, |
| .start_streaming = dw100_start_streaming, |
| .stop_streaming = dw100_stop_streaming, |
| .wait_prepare = vb2_ops_wait_prepare, |
| .wait_finish = vb2_ops_wait_finish, |
| }; |
| |
| static int dw100_m2m_queue_init(void *priv, struct vb2_queue *src_vq, |
| struct vb2_queue *dst_vq) |
| { |
| struct dw100_ctx *ctx = priv; |
| int ret; |
| |
| src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| src_vq->io_modes = VB2_MMAP | VB2_DMABUF; |
| src_vq->drv_priv = ctx; |
| src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); |
| src_vq->ops = &dw100_qops; |
| src_vq->mem_ops = &vb2_dma_contig_memops; |
| src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| src_vq->lock = &ctx->vq_mutex; |
| src_vq->dev = ctx->dw_dev->v4l2_dev.dev; |
| |
| ret = vb2_queue_init(src_vq); |
| if (ret) |
| return ret; |
| |
| dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; |
| dst_vq->drv_priv = ctx; |
| dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); |
| dst_vq->ops = &dw100_qops; |
| dst_vq->mem_ops = &vb2_dma_contig_memops; |
| dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| dst_vq->lock = &ctx->vq_mutex; |
| dst_vq->dev = ctx->dw_dev->v4l2_dev.dev; |
| |
| return vb2_queue_init(dst_vq); |
| } |
| |
| static int dw100_open(struct file *file) |
| { |
| struct dw100_device *dw_dev = video_drvdata(file); |
| struct dw100_ctx *ctx; |
| struct v4l2_ctrl_handler *hdl; |
| struct v4l2_pix_format_mplane *pix_fmt; |
| int ret, i; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| mutex_init(&ctx->vq_mutex); |
| v4l2_fh_init(&ctx->fh, video_devdata(file)); |
| file->private_data = &ctx->fh; |
| ctx->dw_dev = dw_dev; |
| |
| ctx->q_data[DW100_QUEUE_SRC].fmt = &formats[0]; |
| |
| pix_fmt = &ctx->q_data[DW100_QUEUE_SRC].pix_fmt; |
| pix_fmt->field = V4L2_FIELD_NONE; |
| pix_fmt->colorspace = V4L2_COLORSPACE_REC709; |
| pix_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_fmt->colorspace); |
| pix_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_fmt->colorspace); |
| pix_fmt->quantization = |
| V4L2_MAP_QUANTIZATION_DEFAULT(false, pix_fmt->colorspace, |
| pix_fmt->ycbcr_enc); |
| |
| v4l2_fill_pixfmt_mp(pix_fmt, formats[0].fourcc, DW100_DEF_W, DW100_DEF_H); |
| |
| ctx->q_data[DW100_QUEUE_SRC].crop.top = 0; |
| ctx->q_data[DW100_QUEUE_SRC].crop.left = 0; |
| ctx->q_data[DW100_QUEUE_SRC].crop.width = DW100_DEF_W; |
| ctx->q_data[DW100_QUEUE_SRC].crop.height = DW100_DEF_H; |
| |
| ctx->q_data[DW100_QUEUE_DST] = ctx->q_data[DW100_QUEUE_SRC]; |
| |
| hdl = &ctx->hdl; |
| v4l2_ctrl_handler_init(hdl, ARRAY_SIZE(controls)); |
| for (i = 0; i < ARRAY_SIZE(controls); i++) { |
| ctx->ctrls[i] = v4l2_ctrl_new_custom(hdl, &controls[i], NULL); |
| if (hdl->error) { |
| dev_err(&ctx->dw_dev->pdev->dev, |
| "Adding control (%d) failed\n", i); |
| ret = hdl->error; |
| goto err; |
| } |
| } |
| ctx->fh.ctrl_handler = hdl; |
| |
| ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dw_dev->m2m_dev, |
| ctx, &dw100_m2m_queue_init); |
| |
| if (IS_ERR(ctx->fh.m2m_ctx)) { |
| ret = PTR_ERR(ctx->fh.m2m_ctx); |
| goto err; |
| } |
| |
| v4l2_fh_add(&ctx->fh); |
| |
| return 0; |
| |
| err: |
| v4l2_ctrl_handler_free(hdl); |
| v4l2_fh_exit(&ctx->fh); |
| mutex_destroy(&ctx->vq_mutex); |
| kfree(ctx); |
| |
| return ret; |
| } |
| |
| static int dw100_release(struct file *file) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| |
| v4l2_fh_del(&ctx->fh); |
| v4l2_fh_exit(&ctx->fh); |
| v4l2_ctrl_handler_free(&ctx->hdl); |
| v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); |
| mutex_destroy(&ctx->vq_mutex); |
| kfree(ctx); |
| |
| return 0; |
| } |
| |
| static const struct v4l2_file_operations dw100_fops = { |
| .owner = THIS_MODULE, |
| .open = dw100_open, |
| .release = dw100_release, |
| .poll = v4l2_m2m_fop_poll, |
| .unlocked_ioctl = video_ioctl2, |
| .mmap = v4l2_m2m_fop_mmap, |
| }; |
| |
| static int dw100_querycap(struct file *file, void *priv, |
| struct v4l2_capability *cap) |
| { |
| strscpy(cap->driver, DRV_NAME, sizeof(cap->driver)); |
| strscpy(cap->card, "DW100 dewarper", sizeof(cap->card)); |
| |
| return 0; |
| } |
| |
| static int dw100_enum_fmt_vid(struct file *file, void *priv, |
| struct v4l2_fmtdesc *f) |
| { |
| int i, num = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(formats); i++) { |
| if (formats[i].types & to_dw100_fmt_type(f->type)) { |
| if (num == f->index) { |
| f->pixelformat = formats[i].fourcc; |
| return 0; |
| } |
| ++num; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int dw100_enum_framesizes(struct file *file, void *priv, |
| struct v4l2_frmsizeenum *fsize) |
| { |
| const struct dw100_fmt *fmt; |
| |
| if (fsize->index) |
| return -EINVAL; |
| |
| fmt = dw100_find_pixel_format(fsize->pixel_format, |
| DW100_FMT_OUTPUT | DW100_FMT_CAPTURE); |
| if (!fmt) |
| return -EINVAL; |
| |
| fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; |
| fsize->stepwise = dw100_frmsize_stepwise; |
| |
| return 0; |
| } |
| |
| static int dw100_g_fmt_vid(struct file *file, void *priv, struct v4l2_format *f) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| struct vb2_queue *vq; |
| struct dw100_q_data *q_data; |
| |
| vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); |
| if (!vq) |
| return -EINVAL; |
| |
| q_data = dw100_get_q_data(ctx, f->type); |
| |
| f->fmt.pix_mp = q_data->pix_fmt; |
| |
| return 0; |
| } |
| |
| static int dw100_try_fmt(struct file *file, struct v4l2_format *f) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; |
| const struct dw100_fmt *fmt; |
| |
| fmt = dw100_find_format(f); |
| if (!fmt) { |
| fmt = &formats[0]; |
| pix->pixelformat = fmt->fourcc; |
| } |
| |
| v4l2_apply_frmsize_constraints(&pix->width, &pix->height, |
| &dw100_frmsize_stepwise); |
| |
| v4l2_fill_pixfmt_mp(pix, fmt->fourcc, pix->width, pix->height); |
| |
| pix->field = V4L2_FIELD_NONE; |
| |
| if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) |
| pix->colorspace = V4L2_COLORSPACE_REC709; |
| if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT) |
| pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); |
| if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) |
| pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); |
| if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) |
| pix->quantization = |
| V4L2_MAP_QUANTIZATION_DEFAULT(false, |
| pix->colorspace, |
| pix->ycbcr_enc); |
| } else { |
| /* |
| * The DW100 can't perform colorspace conversion, the colorspace |
| * on the capture queue must be identical to the output queue. |
| */ |
| const struct dw100_q_data *q_data = |
| dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| pix->colorspace = q_data->pix_fmt.colorspace; |
| pix->xfer_func = q_data->pix_fmt.xfer_func; |
| pix->ycbcr_enc = q_data->pix_fmt.ycbcr_enc; |
| pix->quantization = q_data->pix_fmt.quantization; |
| } |
| |
| return 0; |
| } |
| |
| static int dw100_s_fmt(struct dw100_ctx *ctx, struct v4l2_format *f) |
| { |
| struct dw100_q_data *q_data; |
| struct vb2_queue *vq; |
| |
| vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); |
| if (!vq) |
| return -EINVAL; |
| |
| q_data = dw100_get_q_data(ctx, f->type); |
| if (!q_data) |
| return -EINVAL; |
| |
| if (vb2_is_busy(vq)) { |
| dev_dbg(&ctx->dw_dev->pdev->dev, "%s queue busy\n", __func__); |
| return -EBUSY; |
| } |
| |
| q_data->fmt = dw100_find_format(f); |
| q_data->pix_fmt = f->fmt.pix_mp; |
| q_data->crop.top = 0; |
| q_data->crop.left = 0; |
| q_data->crop.width = f->fmt.pix_mp.width; |
| q_data->crop.height = f->fmt.pix_mp.height; |
| |
| /* Propagate buffers encoding */ |
| |
| if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| struct dw100_q_data *dst_q_data = |
| dw100_get_q_data(ctx, |
| V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); |
| |
| dst_q_data->pix_fmt.colorspace = q_data->pix_fmt.colorspace; |
| dst_q_data->pix_fmt.ycbcr_enc = q_data->pix_fmt.ycbcr_enc; |
| dst_q_data->pix_fmt.quantization = q_data->pix_fmt.quantization; |
| dst_q_data->pix_fmt.xfer_func = q_data->pix_fmt.xfer_func; |
| } |
| |
| dev_dbg(&ctx->dw_dev->pdev->dev, |
| "Setting format for type %u, wxh: %ux%u, fmt: %p4cc\n", |
| f->type, q_data->pix_fmt.width, q_data->pix_fmt.height, |
| &q_data->pix_fmt.pixelformat); |
| |
| if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { |
| int ret; |
| u32 dims[V4L2_CTRL_MAX_DIMS] = {}; |
| struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; |
| |
| dims[0] = dw100_get_n_vertices_from_length(q_data->pix_fmt.width); |
| dims[1] = dw100_get_n_vertices_from_length(q_data->pix_fmt.height); |
| |
| ret = v4l2_ctrl_modify_dimensions(ctrl, dims); |
| |
| if (ret) { |
| dev_err(&ctx->dw_dev->pdev->dev, |
| "Modifying LUT dimensions failed with error %d\n", |
| ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int dw100_try_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) |
| return -EINVAL; |
| |
| return dw100_try_fmt(file, f); |
| } |
| |
| static int dw100_s_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| int ret; |
| |
| ret = dw100_try_fmt_vid_cap(file, priv, f); |
| if (ret) |
| return ret; |
| |
| ret = dw100_s_fmt(ctx, f); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int dw100_try_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) |
| return -EINVAL; |
| |
| return dw100_try_fmt(file, f); |
| } |
| |
| static int dw100_s_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| int ret; |
| |
| ret = dw100_try_fmt_vid_out(file, priv, f); |
| if (ret) |
| return ret; |
| |
| ret = dw100_s_fmt(ctx, f); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int dw100_g_selection(struct file *file, void *fh, |
| struct v4l2_selection *sel) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| struct dw100_q_data *src_q_data; |
| |
| if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) |
| return -EINVAL; |
| |
| src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| switch (sel->target) { |
| case V4L2_SEL_TGT_CROP_DEFAULT: |
| case V4L2_SEL_TGT_CROP_BOUNDS: |
| sel->r.top = 0; |
| sel->r.left = 0; |
| sel->r.width = src_q_data->pix_fmt.width; |
| sel->r.height = src_q_data->pix_fmt.height; |
| break; |
| case V4L2_SEL_TGT_CROP: |
| sel->r.top = src_q_data->crop.top; |
| sel->r.left = src_q_data->crop.left; |
| sel->r.width = src_q_data->crop.width; |
| sel->r.height = src_q_data->crop.height; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int dw100_s_selection(struct file *file, void *fh, |
| struct v4l2_selection *sel) |
| { |
| struct dw100_ctx *ctx = dw100_file2ctx(file); |
| struct dw100_q_data *src_q_data; |
| u32 qscalex, qscaley, qscale; |
| int x, y, w, h; |
| unsigned int wframe, hframe; |
| |
| if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) |
| return -EINVAL; |
| |
| src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| dev_dbg(&ctx->dw_dev->pdev->dev, |
| ">>> Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", |
| sel->type, sel->target, |
| sel->r.width, sel->r.height, sel->r.left, sel->r.top); |
| |
| switch (sel->target) { |
| case V4L2_SEL_TGT_CROP: |
| wframe = src_q_data->pix_fmt.width; |
| hframe = src_q_data->pix_fmt.height; |
| |
| sel->r.top = clamp_t(int, sel->r.top, 0, hframe - DW100_MIN_H); |
| sel->r.left = clamp_t(int, sel->r.left, 0, wframe - DW100_MIN_W); |
| sel->r.height = |
| clamp(sel->r.height, DW100_MIN_H, hframe - sel->r.top); |
| sel->r.width = |
| clamp(sel->r.width, DW100_MIN_W, wframe - sel->r.left); |
| |
| /* UQ16.16 for float operations */ |
| qscalex = (sel->r.width << 16) / wframe; |
| qscaley = (sel->r.height << 16) / hframe; |
| y = sel->r.top; |
| x = sel->r.left; |
| if (qscalex == qscaley) { |
| qscale = qscalex; |
| } else { |
| switch (sel->flags) { |
| case 0: |
| qscale = (qscalex + qscaley) / 2; |
| break; |
| case V4L2_SEL_FLAG_GE: |
| qscale = max(qscaley, qscalex); |
| break; |
| case V4L2_SEL_FLAG_LE: |
| qscale = min(qscaley, qscalex); |
| break; |
| case V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE: |
| return -ERANGE; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| w = (u32)((((u64)wframe << 16) * qscale) >> 32); |
| h = (u32)((((u64)hframe << 16) * qscale) >> 32); |
| x = x + (sel->r.width - w) / 2; |
| y = y + (sel->r.height - h) / 2; |
| x = min(wframe - w, (unsigned int)max(0, x)); |
| y = min(hframe - h, (unsigned int)max(0, y)); |
| |
| sel->r.top = y; |
| sel->r.left = x; |
| sel->r.width = w; |
| sel->r.height = h; |
| |
| src_q_data->crop.top = sel->r.top; |
| src_q_data->crop.left = sel->r.left; |
| src_q_data->crop.width = sel->r.width; |
| src_q_data->crop.height = sel->r.height; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| dev_dbg(&ctx->dw_dev->pdev->dev, |
| "<<< Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", |
| sel->type, sel->target, |
| sel->r.width, sel->r.height, sel->r.left, sel->r.top); |
| |
| return 0; |
| } |
| |
| static const struct v4l2_ioctl_ops dw100_ioctl_ops = { |
| .vidioc_querycap = dw100_querycap, |
| |
| .vidioc_enum_fmt_vid_cap = dw100_enum_fmt_vid, |
| .vidioc_enum_framesizes = dw100_enum_framesizes, |
| .vidioc_g_fmt_vid_cap_mplane = dw100_g_fmt_vid, |
| .vidioc_try_fmt_vid_cap_mplane = dw100_try_fmt_vid_cap, |
| .vidioc_s_fmt_vid_cap_mplane = dw100_s_fmt_vid_cap, |
| |
| .vidioc_enum_fmt_vid_out = dw100_enum_fmt_vid, |
| .vidioc_g_fmt_vid_out_mplane = dw100_g_fmt_vid, |
| .vidioc_try_fmt_vid_out_mplane = dw100_try_fmt_vid_out, |
| .vidioc_s_fmt_vid_out_mplane = dw100_s_fmt_vid_out, |
| |
| .vidioc_g_selection = dw100_g_selection, |
| .vidioc_s_selection = dw100_s_selection, |
| .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, |
| .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, |
| .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, |
| .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, |
| .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, |
| .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, |
| .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, |
| |
| .vidioc_streamon = v4l2_m2m_ioctl_streamon, |
| .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, |
| |
| .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| }; |
| |
| static void dw100_job_finish(struct dw100_device *dw_dev, bool with_error) |
| { |
| struct dw100_ctx *curr_ctx; |
| struct vb2_v4l2_buffer *src_vb, *dst_vb; |
| enum vb2_buffer_state buf_state; |
| |
| curr_ctx = v4l2_m2m_get_curr_priv(dw_dev->m2m_dev); |
| |
| if (!curr_ctx) { |
| dev_err(&dw_dev->pdev->dev, |
| "Instance released before the end of transaction\n"); |
| return; |
| } |
| |
| src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); |
| dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); |
| |
| if (likely(!with_error)) |
| buf_state = VB2_BUF_STATE_DONE; |
| else |
| buf_state = VB2_BUF_STATE_ERROR; |
| |
| v4l2_m2m_buf_done(src_vb, buf_state); |
| v4l2_m2m_buf_done(dst_vb, buf_state); |
| |
| dev_dbg(&dw_dev->pdev->dev, "Finishing transaction with%s error(s)\n", |
| with_error ? "" : "out"); |
| |
| v4l2_m2m_job_finish(dw_dev->m2m_dev, curr_ctx->fh.m2m_ctx); |
| } |
| |
| static void dw100_hw_reset(struct dw100_device *dw_dev) |
| { |
| u32 val; |
| |
| val = dw100_read(dw_dev, DW100_DEWARP_CTRL); |
| val |= DW100_DEWARP_CTRL_ENABLE; |
| val |= DW100_DEWARP_CTRL_SOFT_RESET; |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| val &= ~DW100_DEWARP_CTRL_SOFT_RESET; |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| } |
| |
| static void _dw100_hw_set_master_bus_enable(struct dw100_device *dw_dev, |
| unsigned int enable) |
| { |
| u32 val; |
| |
| dev_dbg(&dw_dev->pdev->dev, "%sable master bus\n", |
| enable ? "En" : "Dis"); |
| |
| val = dw100_read(dw_dev, DW100_BUS_CTRL); |
| |
| if (enable) |
| val |= DW100_BUS_CTRL_AXI_MASTER_ENABLE; |
| else |
| val &= ~DW100_BUS_CTRL_AXI_MASTER_ENABLE; |
| |
| dw100_write(dw_dev, DW100_BUS_CTRL, val); |
| } |
| |
| static void dw100_hw_master_bus_enable(struct dw100_device *dw_dev) |
| { |
| _dw100_hw_set_master_bus_enable(dw_dev, 1); |
| } |
| |
| static void dw100_hw_master_bus_disable(struct dw100_device *dw_dev) |
| { |
| _dw100_hw_set_master_bus_enable(dw_dev, 0); |
| } |
| |
| static void dw100_hw_dewarp_start(struct dw100_device *dw_dev) |
| { |
| u32 val; |
| |
| val = dw100_read(dw_dev, DW100_DEWARP_CTRL); |
| |
| dev_dbg(&dw_dev->pdev->dev, "Starting Hardware CTRL:0x%08x\n", val); |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val | DW100_DEWARP_CTRL_START); |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| } |
| |
| static void dw100_hw_init_ctrl(struct dw100_device *dw_dev) |
| { |
| u32 val; |
| /* |
| * Input format YUV422_SP |
| * Output format YUV422_SP |
| * No hardware handshake (SW) |
| * No automatic double src buffering (Single) |
| * No automatic double dst buffering (Single) |
| * No Black Line |
| * Prefetch image pixel traversal |
| */ |
| |
| val = DW100_DEWARP_CTRL_ENABLE |
| /* Valid only for auto prefetch mode*/ |
| | DW100_DEWARP_CTRL_PREFETCH_THRESHOLD(32); |
| |
| /* |
| * Calculation mode required to support any scaling factor, |
| * but x4 slower than traversal mode. |
| * |
| * DW100_DEWARP_CTRL_PREFETCH_MODE_TRAVERSAL |
| * DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION |
| * DW100_DEWARP_CTRL_PREFETCH_MODE_AUTO |
| * |
| * TODO: Find heuristics requiring calculation mode |
| */ |
| val |= DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION; |
| |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| } |
| |
| static void dw100_hw_set_pixel_boundary(struct dw100_device *dw_dev) |
| { |
| u32 val; |
| |
| val = DW100_BOUNDARY_PIXEL_V(128) |
| | DW100_BOUNDARY_PIXEL_U(128) |
| | DW100_BOUNDARY_PIXEL_Y(0); |
| |
| dw100_write(dw_dev, DW100_BOUNDARY_PIXEL, val); |
| } |
| |
| static void dw100_hw_set_scale(struct dw100_device *dw_dev, u8 scale) |
| { |
| dev_dbg(&dw_dev->pdev->dev, "Setting scale factor to %u\n", scale); |
| |
| dw100_write(dw_dev, DW100_SCALE_FACTOR, scale); |
| } |
| |
| static void dw100_hw_set_roi(struct dw100_device *dw_dev, u32 x, u32 y) |
| { |
| u32 val; |
| |
| dev_dbg(&dw_dev->pdev->dev, "Setting ROI region to %u.%u\n", x, y); |
| |
| val = DW100_ROI_START_X(x) | DW100_ROI_START_Y(y); |
| |
| dw100_write(dw_dev, DW100_ROI_START, val); |
| } |
| |
| static void dw100_hw_set_src_crop(struct dw100_device *dw_dev, |
| const struct dw100_q_data *src_q_data, |
| const struct dw100_q_data *dst_q_data) |
| { |
| const struct v4l2_rect *rect = &src_q_data->crop; |
| u32 src_scale, qscale, left_scale, top_scale; |
| |
| /* HW Scale is UQ1.7 encoded */ |
| src_scale = (rect->width << 7) / src_q_data->pix_fmt.width; |
| dw100_hw_set_scale(dw_dev, src_scale); |
| |
| qscale = (dst_q_data->pix_fmt.width << 7) / src_q_data->pix_fmt.width; |
| |
| left_scale = ((rect->left << 7) * qscale) >> 14; |
| top_scale = ((rect->top << 7) * qscale) >> 14; |
| |
| dw100_hw_set_roi(dw_dev, left_scale, top_scale); |
| } |
| |
| static void dw100_hw_set_source(struct dw100_device *dw_dev, |
| const struct dw100_q_data *q_data, |
| struct vb2_buffer *buffer) |
| { |
| u32 width, height, stride, fourcc, val; |
| const struct dw100_fmt *fmt = q_data->fmt; |
| dma_addr_t addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); |
| dma_addr_t addr_uv; |
| |
| width = q_data->pix_fmt.width; |
| height = q_data->pix_fmt.height; |
| stride = q_data->pix_fmt.plane_fmt[0].bytesperline; |
| fourcc = q_data->fmt->fourcc; |
| |
| if (q_data->pix_fmt.num_planes == 2) |
| addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); |
| else |
| addr_uv = addr_y + (stride * height); |
| |
| dev_dbg(&dw_dev->pdev->dev, |
| "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", |
| width, height, stride, &fourcc, &addr_y); |
| |
| /* Pixel Format */ |
| val = dw100_read(dw_dev, DW100_DEWARP_CTRL); |
| |
| val &= ~DW100_DEWARP_CTRL_INPUT_FORMAT_MASK; |
| val |= DW100_DEWARP_CTRL_INPUT_FORMAT(fmt->reg_format); |
| |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| |
| /* Swap */ |
| val = dw100_read(dw_dev, DW100_SWAP_CONTROL); |
| |
| val &= ~DW100_SWAP_CONTROL_SRC_MASK; |
| /* |
| * Data swapping is performed only on Y plane for source image. |
| */ |
| if (fmt->reg_swap_uv && |
| fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) |
| val |= DW100_SWAP_CONTROL_SRC(DW100_SWAP_CONTROL_Y |
| (DW100_SWAP_CONTROL_BYTE)); |
| |
| dw100_write(dw_dev, DW100_SWAP_CONTROL, val); |
| |
| /* Image resolution */ |
| dw100_write(dw_dev, DW100_SRC_IMG_SIZE, |
| DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); |
| |
| dw100_write(dw_dev, DW100_SRC_IMG_STRIDE, stride); |
| |
| /* Buffers */ |
| dw100_write(dw_dev, DW100_SRC_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); |
| dw100_write(dw_dev, DW100_SRC_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); |
| } |
| |
| static void dw100_hw_set_destination(struct dw100_device *dw_dev, |
| const struct dw100_q_data *q_data, |
| const struct dw100_fmt *ifmt, |
| struct vb2_buffer *buffer) |
| { |
| u32 width, height, stride, fourcc, val, size_y, size_uv; |
| const struct dw100_fmt *fmt = q_data->fmt; |
| dma_addr_t addr_y, addr_uv; |
| |
| width = q_data->pix_fmt.width; |
| height = q_data->pix_fmt.height; |
| stride = q_data->pix_fmt.plane_fmt[0].bytesperline; |
| fourcc = fmt->fourcc; |
| |
| addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); |
| size_y = q_data->pix_fmt.plane_fmt[0].sizeimage; |
| |
| if (q_data->pix_fmt.num_planes == 2) { |
| addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); |
| size_uv = q_data->pix_fmt.plane_fmt[1].sizeimage; |
| } else { |
| addr_uv = addr_y + ALIGN(stride * height, 16); |
| size_uv = size_y; |
| if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV420_SP) |
| size_uv /= 2; |
| } |
| |
| dev_dbg(&dw_dev->pdev->dev, |
| "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", |
| width, height, stride, &fourcc, &addr_y); |
| |
| /* Pixel Format */ |
| val = dw100_read(dw_dev, DW100_DEWARP_CTRL); |
| |
| val &= ~DW100_DEWARP_CTRL_OUTPUT_FORMAT_MASK; |
| val |= DW100_DEWARP_CTRL_OUTPUT_FORMAT(fmt->reg_format); |
| |
| dw100_write(dw_dev, DW100_DEWARP_CTRL, val); |
| |
| /* Swap */ |
| val = dw100_read(dw_dev, DW100_SWAP_CONTROL); |
| |
| val &= ~DW100_SWAP_CONTROL_DST_MASK; |
| |
| /* |
| * Avoid to swap twice |
| */ |
| if (fmt->reg_swap_uv ^ |
| (ifmt->reg_swap_uv && ifmt->reg_format != |
| DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED)) { |
| if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) |
| val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_Y |
| (DW100_SWAP_CONTROL_BYTE)); |
| else |
| val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_UV |
| (DW100_SWAP_CONTROL_BYTE)); |
| } |
| |
| dw100_write(dw_dev, DW100_SWAP_CONTROL, val); |
| |
| /* Image resolution */ |
| dw100_write(dw_dev, DW100_DST_IMG_SIZE, |
| DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); |
| dw100_write(dw_dev, DW100_DST_IMG_STRIDE, stride); |
| dw100_write(dw_dev, DW100_DST_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); |
| dw100_write(dw_dev, DW100_DST_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); |
| dw100_write(dw_dev, DW100_DST_IMG_Y_SIZE1, DW100_DST_IMG_Y_SIZE(size_y)); |
| dw100_write(dw_dev, DW100_DST_IMG_UV_SIZE1, |
| DW100_DST_IMG_UV_SIZE(size_uv)); |
| } |
| |
| static void dw100_hw_set_mapping(struct dw100_device *dw_dev, dma_addr_t addr, |
| u32 width, u32 height) |
| { |
| dev_dbg(&dw_dev->pdev->dev, |
| "Set HW mapping registers for %ux%u addr:%pad", |
| width, height, &addr); |
| |
| dw100_write(dw_dev, DW100_MAP_LUT_ADDR, DW100_MAP_LUT_ADDR_ADDR(addr)); |
| dw100_write(dw_dev, DW100_MAP_LUT_SIZE, DW100_MAP_LUT_SIZE_WIDTH(width) |
| | DW100_MAP_LUT_SIZE_HEIGHT(height)); |
| } |
| |
| static void dw100_hw_clear_irq(struct dw100_device *dw_dev, unsigned int irq) |
| { |
| dw100_write(dw_dev, DW100_INTERRUPT_STATUS, |
| DW100_INTERRUPT_STATUS_INT_CLEAR(irq)); |
| } |
| |
| static void dw100_hw_enable_irq(struct dw100_device *dw_dev) |
| { |
| dw100_write(dw_dev, DW100_INTERRUPT_STATUS, |
| DW100_INTERRUPT_STATUS_INT_ENABLE_MASK); |
| } |
| |
| static void dw100_hw_disable_irq(struct dw100_device *dw_dev) |
| { |
| dw100_write(dw_dev, DW100_INTERRUPT_STATUS, 0); |
| } |
| |
| static u32 dw_hw_get_pending_irqs(struct dw100_device *dw_dev) |
| { |
| u32 val; |
| |
| val = dw100_read(dw_dev, DW100_INTERRUPT_STATUS); |
| |
| return DW100_INTERRUPT_STATUS_INT_STATUS(val); |
| } |
| |
| static irqreturn_t dw100_irq_handler(int irq, void *dev_id) |
| { |
| struct dw100_device *dw_dev = dev_id; |
| u32 pending_irqs, err_irqs, frame_done_irq; |
| bool with_error = true; |
| |
| pending_irqs = dw_hw_get_pending_irqs(dw_dev); |
| frame_done_irq = pending_irqs & DW100_INTERRUPT_STATUS_INT_FRAME_DONE; |
| err_irqs = DW100_INTERRUPT_STATUS_INT_ERR_STATUS(pending_irqs); |
| |
| if (frame_done_irq) { |
| dev_dbg(&dw_dev->pdev->dev, "Frame done interrupt\n"); |
| with_error = false; |
| err_irqs &= ~DW100_INTERRUPT_STATUS_INT_ERR_STATUS |
| (DW100_INTERRUPT_STATUS_INT_ERR_FRAME_DONE); |
| } |
| |
| if (err_irqs) |
| dev_err(&dw_dev->pdev->dev, "Interrupt error: %#x\n", err_irqs); |
| |
| dw100_hw_disable_irq(dw_dev); |
| dw100_hw_master_bus_disable(dw_dev); |
| dw100_hw_clear_irq(dw_dev, pending_irqs | |
| DW100_INTERRUPT_STATUS_INT_ERR_TIME_OUT); |
| |
| dw100_job_finish(dw_dev, with_error); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void dw100_start(struct dw100_ctx *ctx, struct vb2_v4l2_buffer *in_vb, |
| struct vb2_v4l2_buffer *out_vb) |
| { |
| struct dw100_device *dw_dev = ctx->dw_dev; |
| |
| out_vb->sequence = |
| dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)->sequence++; |
| in_vb->sequence = |
| dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)->sequence++; |
| |
| dev_dbg(&ctx->dw_dev->pdev->dev, |
| "Starting queues %p->%p, sequence %u->%u\n", |
| v4l2_m2m_get_vq(ctx->fh.m2m_ctx, |
| V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE), |
| v4l2_m2m_get_vq(ctx->fh.m2m_ctx, |
| V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE), |
| in_vb->sequence, out_vb->sequence); |
| |
| v4l2_m2m_buf_copy_metadata(in_vb, out_vb, true); |
| |
| /* Now, let's deal with hardware ... */ |
| dw100_hw_master_bus_disable(dw_dev); |
| dw100_hw_init_ctrl(dw_dev); |
| dw100_hw_set_pixel_boundary(dw_dev); |
| dw100_hw_set_src_crop(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], |
| &ctx->q_data[DW100_QUEUE_DST]); |
| dw100_hw_set_source(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], |
| &in_vb->vb2_buf); |
| dw100_hw_set_destination(dw_dev, &ctx->q_data[DW100_QUEUE_DST], |
| ctx->q_data[DW100_QUEUE_SRC].fmt, |
| &out_vb->vb2_buf); |
| dw100_hw_set_mapping(dw_dev, ctx->map_dma, |
| ctx->map_width, ctx->map_height); |
| dw100_hw_enable_irq(dw_dev); |
| dw100_hw_dewarp_start(dw_dev); |
| |
| /* Enable Bus */ |
| dw100_hw_master_bus_enable(dw_dev); |
| } |
| |
| static void dw100_device_run(void *priv) |
| { |
| struct dw100_ctx *ctx = priv; |
| struct vb2_v4l2_buffer *src_buf, *dst_buf; |
| |
| src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); |
| dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); |
| |
| dw100_start(ctx, src_buf, dst_buf); |
| } |
| |
| static const struct v4l2_m2m_ops dw100_m2m_ops = { |
| .device_run = dw100_device_run, |
| }; |
| |
| static struct video_device *dw100_init_video_device(struct dw100_device *dw_dev) |
| { |
| struct video_device *vfd = &dw_dev->vfd; |
| |
| vfd->vfl_dir = VFL_DIR_M2M; |
| vfd->fops = &dw100_fops; |
| vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; |
| vfd->ioctl_ops = &dw100_ioctl_ops; |
| vfd->minor = -1; |
| vfd->release = video_device_release_empty; |
| vfd->v4l2_dev = &dw_dev->v4l2_dev; |
| vfd->lock = &dw_dev->vfd_mutex; |
| |
| strscpy(vfd->name, DRV_NAME, sizeof(vfd->name)); |
| mutex_init(vfd->lock); |
| video_set_drvdata(vfd, dw_dev); |
| |
| return vfd; |
| } |
| |
| static int dw100_dump_regs_show(struct seq_file *m, void *private) |
| { |
| struct dw100_device *dw_dev = m->private; |
| int ret; |
| |
| ret = pm_runtime_resume_and_get(&dw_dev->pdev->dev); |
| if (ret < 0) |
| return ret; |
| |
| ret = dw100_dump_regs(m); |
| |
| pm_runtime_put_sync(&dw_dev->pdev->dev); |
| |
| return ret; |
| } |
| DEFINE_SHOW_ATTRIBUTE(dw100_dump_regs); |
| |
| static void dw100_debugfs_init(struct dw100_device *dw_dev) |
| { |
| dw_dev->debugfs_root = |
| debugfs_create_dir(dev_name(&dw_dev->pdev->dev), NULL); |
| |
| debugfs_create_file("dump_regs", 0600, dw_dev->debugfs_root, dw_dev, |
| &dw100_dump_regs_fops); |
| } |
| |
| static void dw100_debugfs_exit(struct dw100_device *dw_dev) |
| { |
| debugfs_remove_recursive(dw_dev->debugfs_root); |
| } |
| |
| static int dw100_probe(struct platform_device *pdev) |
| { |
| struct dw100_device *dw_dev; |
| struct video_device *vfd; |
| int ret, irq; |
| |
| dw_dev = devm_kzalloc(&pdev->dev, sizeof(*dw_dev), GFP_KERNEL); |
| if (!dw_dev) |
| return -ENOMEM; |
| dw_dev->pdev = pdev; |
| |
| ret = devm_clk_bulk_get_all(&pdev->dev, &dw_dev->clks); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Unable to get clocks: %d\n", ret); |
| return ret; |
| } |
| dw_dev->num_clks = ret; |
| |
| dw_dev->mmio = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); |
| if (IS_ERR(dw_dev->mmio)) |
| return PTR_ERR(dw_dev->mmio); |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| platform_set_drvdata(pdev, dw_dev); |
| |
| pm_runtime_enable(&pdev->dev); |
| ret = pm_runtime_resume_and_get(&pdev->dev); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Unable to resume the device: %d\n", ret); |
| goto err_pm; |
| } |
| |
| pm_runtime_put_sync(&pdev->dev); |
| |
| ret = devm_request_irq(&pdev->dev, irq, dw100_irq_handler, IRQF_ONESHOT, |
| dev_name(&pdev->dev), dw_dev); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); |
| goto err_pm; |
| } |
| |
| ret = v4l2_device_register(&pdev->dev, &dw_dev->v4l2_dev); |
| if (ret) |
| goto err_pm; |
| |
| vfd = dw100_init_video_device(dw_dev); |
| |
| dw_dev->m2m_dev = v4l2_m2m_init(&dw100_m2m_ops); |
| if (IS_ERR(dw_dev->m2m_dev)) { |
| dev_err(&pdev->dev, "Failed to init mem2mem device\n"); |
| ret = PTR_ERR(dw_dev->m2m_dev); |
| goto err_v4l2; |
| } |
| |
| dw_dev->mdev.dev = &pdev->dev; |
| strscpy(dw_dev->mdev.model, "dw100", sizeof(dw_dev->mdev.model)); |
| media_device_init(&dw_dev->mdev); |
| dw_dev->v4l2_dev.mdev = &dw_dev->mdev; |
| |
| ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to register video device\n"); |
| goto err_m2m; |
| } |
| |
| ret = v4l2_m2m_register_media_controller(dw_dev->m2m_dev, vfd, |
| MEDIA_ENT_F_PROC_VIDEO_SCALER); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to init mem2mem media controller\n"); |
| goto error_v4l2; |
| } |
| |
| ret = media_device_register(&dw_dev->mdev); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to register mem2mem media device\n"); |
| goto error_m2m_mc; |
| } |
| |
| dw100_debugfs_init(dw_dev); |
| |
| dev_info(&pdev->dev, |
| "dw100 v4l2 m2m registered as /dev/video%u\n", vfd->num); |
| |
| return 0; |
| |
| error_m2m_mc: |
| v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); |
| error_v4l2: |
| video_unregister_device(vfd); |
| err_m2m: |
| media_device_cleanup(&dw_dev->mdev); |
| v4l2_m2m_release(dw_dev->m2m_dev); |
| err_v4l2: |
| v4l2_device_unregister(&dw_dev->v4l2_dev); |
| err_pm: |
| pm_runtime_disable(&pdev->dev); |
| |
| return ret; |
| } |
| |
| static void dw100_remove(struct platform_device *pdev) |
| { |
| struct dw100_device *dw_dev = platform_get_drvdata(pdev); |
| |
| dw100_debugfs_exit(dw_dev); |
| |
| pm_runtime_disable(&pdev->dev); |
| |
| media_device_unregister(&dw_dev->mdev); |
| v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); |
| media_device_cleanup(&dw_dev->mdev); |
| |
| video_unregister_device(&dw_dev->vfd); |
| mutex_destroy(dw_dev->vfd.lock); |
| v4l2_m2m_release(dw_dev->m2m_dev); |
| v4l2_device_unregister(&dw_dev->v4l2_dev); |
| } |
| |
| static int __maybe_unused dw100_runtime_suspend(struct device *dev) |
| { |
| struct dw100_device *dw_dev = dev_get_drvdata(dev); |
| |
| clk_bulk_disable_unprepare(dw_dev->num_clks, dw_dev->clks); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused dw100_runtime_resume(struct device *dev) |
| { |
| int ret; |
| struct dw100_device *dw_dev = dev_get_drvdata(dev); |
| |
| ret = clk_bulk_prepare_enable(dw_dev->num_clks, dw_dev->clks); |
| |
| if (ret) |
| return ret; |
| |
| dw100_hw_reset(dw_dev); |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops dw100_pm = { |
| SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
| pm_runtime_force_resume) |
| SET_RUNTIME_PM_OPS(dw100_runtime_suspend, |
| dw100_runtime_resume, NULL) |
| }; |
| |
| static const struct of_device_id dw100_dt_ids[] = { |
| { .compatible = "nxp,imx8mp-dw100", .data = NULL }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, dw100_dt_ids); |
| |
| static struct platform_driver dw100_driver = { |
| .probe = dw100_probe, |
| .remove_new = dw100_remove, |
| .driver = { |
| .name = DRV_NAME, |
| .pm = &dw100_pm, |
| .of_match_table = dw100_dt_ids, |
| }, |
| }; |
| |
| module_platform_driver(dw100_driver); |
| |
| MODULE_DESCRIPTION("DW100 Hardware dewarper"); |
| MODULE_AUTHOR("Xavier Roumegue <Xavier.Roumegue@oss.nxp.com>"); |
| MODULE_LICENSE("GPL"); |