// SPDX-License-Identifier: GPL-2.0 /* * Imagination E5010 JPEG Encoder driver. * * TODO: Add MMU and memory tiling support * * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ * * Author: David Huang * Author: Devarsh Thakkar */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "e5010-jpeg-enc.h" #include "e5010-jpeg-enc-hw.h" /* forward declarations */ static const struct of_device_id e5010_of_match[]; static const struct v4l2_file_operations e5010_fops; static const struct v4l2_ioctl_ops e5010_ioctl_ops; static const struct vb2_ops e5010_video_ops; static const struct v4l2_m2m_ops e5010_m2m_ops; static struct e5010_fmt e5010_formats[] = { { .fourcc = V4L2_PIX_FMT_NV12, .num_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .chroma_order = CHROMA_ORDER_CB_CR, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV12M, .num_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .chroma_order = CHROMA_ORDER_CB_CR, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV21, .num_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .chroma_order = CHROMA_ORDER_CR_CB, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV21M, .num_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .chroma_order = CHROMA_ORDER_CR_CB, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV16, .num_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .chroma_order = CHROMA_ORDER_CB_CR, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV16M, .num_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .chroma_order = CHROMA_ORDER_CB_CR, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV61, .num_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .chroma_order = CHROMA_ORDER_CR_CB, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_NV61M, .num_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .chroma_order = CHROMA_ORDER_CR_CB, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 64, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, { .fourcc = V4L2_PIX_FMT_JPEG, .num_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, .subsampling = 0, .chroma_order = 0, .frmsize = { MIN_DIMENSION, MAX_DIMENSION, 16, MIN_DIMENSION, MAX_DIMENSION, 8 }, }, }; static unsigned int debug; module_param(debug, uint, 0644); MODULE_PARM_DESC(debug, "debug level"); #define dprintk(dev, lvl, fmt, arg...) \ v4l2_dbg(lvl, debug, &(dev)->v4l2_dev, "%s: " fmt, __func__, ## arg) static const struct v4l2_event e5010_eos_event = { .type = V4L2_EVENT_EOS }; static const char *type_name(enum v4l2_buf_type type) { switch (type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: return "Output"; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: return "Capture"; default: return "Invalid"; } } static struct e5010_q_data *get_queue(struct e5010_context *ctx, enum v4l2_buf_type type) { return (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? &ctx->out_queue : &ctx->cap_queue; } static void calculate_qp_tables(struct e5010_context *ctx) { long long luminosity, contrast; int quality, i; quality = 50 - ctx->quality; luminosity = LUMINOSITY * quality / 50; contrast = CONTRAST * quality / 50; if (quality > 0) { luminosity *= INCREASE; contrast *= INCREASE; } for (i = 0; i < V4L2_JPEG_PIXELS_IN_BLOCK; i++) { long long delta = v4l2_jpeg_ref_table_chroma_qt[i] * contrast + luminosity; int val = (int)(v4l2_jpeg_ref_table_chroma_qt[i] + delta); clamp(val, 1, 255); ctx->chroma_qp[i] = quality == -50 ? 1 : val; delta = v4l2_jpeg_ref_table_luma_qt[i] * contrast + luminosity; val = (int)(v4l2_jpeg_ref_table_luma_qt[i] + delta); clamp(val, 1, 255); ctx->luma_qp[i] = quality == -50 ? 1 : val; } ctx->update_qp = true; } static int update_qp_tables(struct e5010_context *ctx) { struct e5010_dev *e5010 = ctx->e5010; int i, ret = 0; u32 lvalue, cvalue; lvalue = 0; cvalue = 0; for (i = 0; i < QP_TABLE_SIZE; i++) { lvalue |= ctx->luma_qp[i] << (8 * (i % 4)); cvalue |= ctx->chroma_qp[i] << (8 * (i % 4)); if (i % 4 == 3) { ret |= e5010_hw_set_qpvalue(e5010->core_base, JASPER_LUMA_QUANTIZATION_TABLE0_OFFSET + QP_TABLE_FIELD_OFFSET * ((i - 3) / 4), lvalue); ret |= e5010_hw_set_qpvalue(e5010->core_base, JASPER_CHROMA_QUANTIZATION_TABLE0_OFFSET + QP_TABLE_FIELD_OFFSET * ((i - 3) / 4), cvalue); lvalue = 0; cvalue = 0; } } return ret; } static int e5010_set_input_subsampling(void __iomem *core_base, int subsampling) { switch (subsampling) { case V4L2_JPEG_CHROMA_SUBSAMPLING_420: return e5010_hw_set_input_subsampling(core_base, SUBSAMPLING_420); case V4L2_JPEG_CHROMA_SUBSAMPLING_422: return e5010_hw_set_input_subsampling(core_base, SUBSAMPLING_422); default: return -EINVAL; }; } static int e5010_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strscpy(cap->driver, E5010_MODULE_NAME, sizeof(cap->driver)); strscpy(cap->card, E5010_MODULE_NAME, sizeof(cap->card)); return 0; } static struct e5010_fmt *find_format(struct v4l2_format *f) { int i; for (i = 0; i < ARRAY_SIZE(e5010_formats); ++i) { if (e5010_formats[i].fourcc == f->fmt.pix_mp.pixelformat && e5010_formats[i].type == f->type) return &e5010_formats[i]; } return NULL; } static int e5010_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) { int i, index = 0; struct e5010_fmt *fmt = NULL; struct e5010_context *ctx = file->private_data; if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { v4l2_err(&ctx->e5010->v4l2_dev, "ENUMFMT with Invalid type: %d\n", f->type); return -EINVAL; } for (i = 0; i < ARRAY_SIZE(e5010_formats); ++i) { if (e5010_formats[i].type == f->type) { if (index == f->index) { fmt = &e5010_formats[i]; break; } index++; } } if (!fmt) return -EINVAL; f->pixelformat = fmt->fourcc; return 0; } static int e5010_g_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct e5010_context *ctx = file->private_data; struct e5010_q_data *queue; int i; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *plane_fmt = pix_mp->plane_fmt; queue = get_queue(ctx, f->type); pix_mp->flags = 0; pix_mp->field = V4L2_FIELD_NONE; pix_mp->pixelformat = queue->fmt->fourcc; pix_mp->width = queue->width_adjusted; pix_mp->height = queue->height_adjusted; pix_mp->num_planes = queue->fmt->num_planes; if (V4L2_TYPE_IS_OUTPUT(f->type)) { if (!pix_mp->colorspace) pix_mp->colorspace = V4L2_COLORSPACE_SRGB; for (i = 0; i < queue->fmt->num_planes; i++) { plane_fmt[i].sizeimage = queue->sizeimage[i]; plane_fmt[i].bytesperline = queue->bytesperline[i]; } } else { pix_mp->colorspace = V4L2_COLORSPACE_JPEG; plane_fmt[0].bytesperline = 0; plane_fmt[0].sizeimage = queue->sizeimage[0]; } pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; pix_mp->xfer_func = V4L2_XFER_FUNC_DEFAULT; pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT; return 0; } static int e5010_jpeg_try_fmt(struct v4l2_format *f, struct e5010_context *ctx) { struct e5010_fmt *fmt; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *plane_fmt = pix_mp->plane_fmt; fmt = find_format(f); if (!fmt) { if (V4L2_TYPE_IS_OUTPUT(f->type)) pix_mp->pixelformat = V4L2_PIX_FMT_NV12; else pix_mp->pixelformat = V4L2_PIX_FMT_JPEG; fmt = find_format(f); if (!fmt) return -EINVAL; } if (V4L2_TYPE_IS_OUTPUT(f->type)) { if (!pix_mp->colorspace) pix_mp->colorspace = V4L2_COLORSPACE_JPEG; if (!pix_mp->ycbcr_enc) pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; if (!pix_mp->quantization) pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT; if (!pix_mp->xfer_func) pix_mp->xfer_func = V4L2_XFER_FUNC_DEFAULT; v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, &fmt->frmsize); v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, pix_mp->width, pix_mp->height); } else { pix_mp->colorspace = V4L2_COLORSPACE_JPEG; pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT; pix_mp->xfer_func = V4L2_XFER_FUNC_DEFAULT; v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, &fmt->frmsize); plane_fmt[0].sizeimage = pix_mp->width * pix_mp->height * JPEG_MAX_BYTES_PER_PIXEL; plane_fmt[0].sizeimage += HEADER_SIZE; plane_fmt[0].bytesperline = 0; pix_mp->pixelformat = fmt->fourcc; pix_mp->num_planes = fmt->num_planes; } pix_mp->flags = 0; pix_mp->field = V4L2_FIELD_NONE; dprintk(ctx->e5010, 2, "ctx: 0x%p: format type %s:, wxh: %dx%d (plane0 : %d bytes, plane1 : %d bytes),fmt: %c%c%c%c\n", ctx, type_name(f->type), pix_mp->width, pix_mp->height, plane_fmt[0].sizeimage, plane_fmt[1].sizeimage, (fmt->fourcc & 0xff), (fmt->fourcc >> 8) & 0xff, (fmt->fourcc >> 16) & 0xff, (fmt->fourcc >> 24) & 0xff); return 0; } static int e5010_try_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct e5010_context *ctx = file->private_data; return e5010_jpeg_try_fmt(f, ctx); } static int e5010_s_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct e5010_context *ctx = file->private_data; struct vb2_queue *vq; int ret = 0, i = 0; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *plane_fmt = pix_mp->plane_fmt; struct e5010_q_data *queue; struct e5010_fmt *fmt; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; if (vb2_is_busy(vq)) { v4l2_err(&ctx->e5010->v4l2_dev, "queue busy\n"); return -EBUSY; } ret = e5010_jpeg_try_fmt(f, ctx); if (ret) return ret; fmt = find_format(f); queue = get_queue(ctx, f->type); queue->fmt = fmt; queue->width = pix_mp->width; queue->height = pix_mp->height; if (V4L2_TYPE_IS_OUTPUT(f->type)) { for (i = 0; i < fmt->num_planes; i++) { queue->bytesperline[i] = plane_fmt[i].bytesperline; queue->sizeimage[i] = plane_fmt[i].sizeimage; } queue->crop.left = 0; queue->crop.top = 0; queue->crop.width = queue->width; queue->crop.height = queue->height; } else { queue->sizeimage[0] = plane_fmt[0].sizeimage; queue->sizeimage[1] = 0; queue->bytesperline[0] = 0; queue->bytesperline[1] = 0; } return 0; } static int e5010_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct v4l2_format f; struct e5010_fmt *fmt; if (fsize->index != 0) return -EINVAL; f.fmt.pix_mp.pixelformat = fsize->pixel_format; if (f.fmt.pix_mp.pixelformat == V4L2_PIX_FMT_JPEG) f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; else f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; fmt = find_format(&f); if (!fmt) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; fsize->stepwise = fmt->frmsize; fsize->reserved[0] = 0; fsize->reserved[1] = 0; return 0; } static int e5010_g_selection(struct file *file, void *fh, struct v4l2_selection *s) { struct e5010_context *ctx = file->private_data; struct e5010_q_data *queue; if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) return -EINVAL; queue = get_queue(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); switch (s->target) { case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP_BOUNDS: s->r.left = 0; s->r.top = 0; s->r.width = queue->width; s->r.height = queue->height; break; case V4L2_SEL_TGT_CROP: memcpy(&s->r, &queue->crop, sizeof(s->r)); break; default: return -EINVAL; } return 0; } static int e5010_s_selection(struct file *file, void *fh, struct v4l2_selection *s) { struct e5010_context *ctx = file->private_data; struct e5010_q_data *queue; struct vb2_queue *vq; struct v4l2_rect base_rect; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, s->type); if (!vq) return -EINVAL; if (vb2_is_streaming(vq)) return -EBUSY; if (s->target != V4L2_SEL_TGT_CROP || s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) return -EINVAL; queue = get_queue(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); base_rect.top = 0; base_rect.left = 0; base_rect.width = queue->width; base_rect.height = queue->height; switch (s->flags) { case 0: s->r.width = round_down(s->r.width, queue->fmt->frmsize.step_width); s->r.height = round_down(s->r.height, queue->fmt->frmsize.step_height); s->r.left = round_down(s->r.left, queue->fmt->frmsize.step_width); s->r.top = round_down(s->r.top, 2); if (s->r.left + s->r.width > queue->width) s->r.width = round_down(s->r.width + s->r.left - queue->width, queue->fmt->frmsize.step_width); if (s->r.top + s->r.height > queue->height) s->r.top = round_down(s->r.top + s->r.height - queue->height, 2); break; case V4L2_SEL_FLAG_GE: s->r.width = round_up(s->r.width, queue->fmt->frmsize.step_width); s->r.height = round_up(s->r.height, queue->fmt->frmsize.step_height); s->r.left = round_up(s->r.left, queue->fmt->frmsize.step_width); s->r.top = round_up(s->r.top, 2); break; case V4L2_SEL_FLAG_LE: s->r.width = round_down(s->r.width, queue->fmt->frmsize.step_width); s->r.height = round_down(s->r.height, queue->fmt->frmsize.step_height); s->r.left = round_down(s->r.left, queue->fmt->frmsize.step_width); s->r.top = round_down(s->r.top, 2); break; case V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE: if (!IS_ALIGNED(s->r.width, queue->fmt->frmsize.step_width) || !IS_ALIGNED(s->r.height, queue->fmt->frmsize.step_height) || !IS_ALIGNED(s->r.left, queue->fmt->frmsize.step_width) || !IS_ALIGNED(s->r.top, 2)) return -ERANGE; break; default: return -EINVAL; } if (!v4l2_rect_enclosed(&s->r, &base_rect)) return -ERANGE; memcpy(&queue->crop, &s->r, sizeof(s->r)); if (!v4l2_rect_equal(&s->r, &base_rect)) queue->crop_set = true; dprintk(ctx->e5010, 2, "ctx: 0x%p: crop rectangle: w: %d, h : %d, l : %d, t : %d\n", ctx, queue->crop.width, queue->crop.height, queue->crop.left, queue->crop.top); return 0; } static int e5010_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { switch (sub->type) { case V4L2_EVENT_EOS: return v4l2_event_subscribe(fh, sub, 0, NULL); case V4L2_EVENT_CTRL: return v4l2_ctrl_subscribe_event(fh, sub); default: return -EINVAL; } return 0; } static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { struct e5010_context *ctx = priv; struct e5010_dev *e5010 = ctx->e5010; int ret = 0; /* src_vq */ memset(src_vq, 0, sizeof(*src_vq)); 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 e5010_buffer); src_vq->ops = &e5010_video_ops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; src_vq->lock = &e5010->mutex; src_vq->dev = e5010->v4l2_dev.dev; ret = vb2_queue_init(src_vq); if (ret) return ret; /* dst_vq */ memset(dst_vq, 0, sizeof(*dst_vq)); 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 e5010_buffer); dst_vq->ops = &e5010_video_ops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; dst_vq->lock = &e5010->mutex; dst_vq->dev = e5010->v4l2_dev.dev; ret = vb2_queue_init(dst_vq); if (ret) { vb2_queue_release(src_vq); return ret; } return 0; } static int e5010_s_ctrl(struct v4l2_ctrl *ctrl) { struct e5010_context *ctx = container_of(ctrl->handler, struct e5010_context, ctrl_handler); switch (ctrl->id) { case V4L2_CID_JPEG_COMPRESSION_QUALITY: ctx->quality = ctrl->val; calculate_qp_tables(ctx); dprintk(ctx->e5010, 2, "ctx: 0x%p compression quality set to : %d\n", ctx, ctx->quality); break; default: return -EINVAL; } return 0; } static const struct v4l2_ctrl_ops e5010_ctrl_ops = { .s_ctrl = e5010_s_ctrl, }; static void e5010_encode_ctrls(struct e5010_context *ctx) { v4l2_ctrl_new_std(&ctx->ctrl_handler, &e5010_ctrl_ops, V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 75); } static int e5010_ctrls_setup(struct e5010_context *ctx) { int err; v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); e5010_encode_ctrls(ctx); if (ctx->ctrl_handler.error) { err = ctx->ctrl_handler.error; v4l2_ctrl_handler_free(&ctx->ctrl_handler); return err; } err = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); if (err) v4l2_ctrl_handler_free(&ctx->ctrl_handler); return err; } static void e5010_jpeg_set_default_params(struct e5010_context *ctx) { struct e5010_q_data *queue; struct v4l2_format f; struct e5010_fmt *fmt; struct v4l2_pix_format_mplane *pix_mp = &f.fmt.pix_mp; struct v4l2_plane_pix_format *plane_fmt = pix_mp->plane_fmt; int i = 0; f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; fmt = find_format(&f); queue = &ctx->out_queue; queue->fmt = fmt; queue->width = DEFAULT_WIDTH; queue->height = DEFAULT_HEIGHT; pix_mp->width = queue->width; pix_mp->height = queue->height; queue->crop.left = 0; queue->crop.top = 0; queue->crop.width = queue->width; queue->crop.height = queue->height; v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, &fmt->frmsize); v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, pix_mp->width, pix_mp->height); for (i = 0; i < fmt->num_planes; i++) { queue->bytesperline[i] = plane_fmt[i].bytesperline; queue->sizeimage[i] = plane_fmt[i].sizeimage; } queue->width_adjusted = pix_mp->width; queue->height_adjusted = pix_mp->height; f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_JPEG; fmt = find_format(&f); queue = &ctx->cap_queue; queue->fmt = fmt; queue->width = DEFAULT_WIDTH; queue->height = DEFAULT_HEIGHT; pix_mp->width = queue->width; pix_mp->height = queue->height; v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, &fmt->frmsize); queue->sizeimage[0] = pix_mp->width * pix_mp->height * JPEG_MAX_BYTES_PER_PIXEL; queue->sizeimage[0] += HEADER_SIZE; queue->sizeimage[1] = 0; queue->bytesperline[0] = 0; queue->bytesperline[1] = 0; queue->width_adjusted = pix_mp->width; queue->height_adjusted = pix_mp->height; } static int e5010_open(struct file *file) { struct e5010_dev *e5010 = video_drvdata(file); struct video_device *vdev = video_devdata(file); struct e5010_context *ctx; int ret = 0; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; if (mutex_lock_interruptible(&e5010->mutex)) { ret = -ERESTARTSYS; goto free; } v4l2_fh_init(&ctx->fh, vdev); file->private_data = ctx; v4l2_fh_add(&ctx->fh); ctx->e5010 = e5010; ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(e5010->m2m_dev, ctx, queue_init); if (IS_ERR(ctx->fh.m2m_ctx)) { v4l2_err(&e5010->v4l2_dev, "failed to init m2m ctx\n"); ret = PTR_ERR(ctx->fh.m2m_ctx); goto exit; } ret = e5010_ctrls_setup(ctx); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to setup e5010 jpeg controls\n"); goto err_ctrls_setup; } ctx->fh.ctrl_handler = &ctx->ctrl_handler; e5010_jpeg_set_default_params(ctx); dprintk(e5010, 1, "Created instance: 0x%p, m2m_ctx: 0x%p\n", ctx, ctx->fh.m2m_ctx); mutex_unlock(&e5010->mutex); return 0; err_ctrls_setup: v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); exit: v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); mutex_unlock(&e5010->mutex); free: kfree(ctx); return ret; } static int e5010_release(struct file *file) { struct e5010_dev *e5010 = video_drvdata(file); struct e5010_context *ctx = file->private_data; dprintk(e5010, 1, "Releasing instance: 0x%p, m2m_ctx: 0x%p\n", ctx, ctx->fh.m2m_ctx); mutex_lock(&e5010->mutex); v4l2_ctrl_handler_free(&ctx->ctrl_handler); v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); kfree(ctx); mutex_unlock(&e5010->mutex); return 0; } static void header_write(struct e5010_context *ctx, u8 *addr, unsigned int *offset, unsigned int no_bytes, unsigned long bits) { u8 *w_addr = addr + *offset; int i; if ((*offset + no_bytes) > HEADER_SIZE) { v4l2_warn(&ctx->e5010->v4l2_dev, "%s: %s: %d: Problem writing header. %d > HEADER_SIZE %d\n", __FILE__, __func__, __LINE__, *offset + no_bytes, HEADER_SIZE); return; } for (i = no_bytes - 1; i >= 0; i--) *(w_addr++) = ((u8 *)&bits)[i]; *offset += no_bytes; } static void encode_marker_segment(struct e5010_context *ctx, void *addr, unsigned int *offset) { u8 *buffer = (u8 *)addr; int i; header_write(ctx, buffer, offset, 2, START_OF_IMAGE); header_write(ctx, buffer, offset, 2, DQT_MARKER); header_write(ctx, buffer, offset, 3, LQPQ << 4); for (i = 0; i < V4L2_JPEG_PIXELS_IN_BLOCK; i++) header_write(ctx, buffer, offset, 1, ctx->luma_qp[v4l2_jpeg_zigzag_scan_index[i]]); header_write(ctx, buffer, offset, 2, DQT_MARKER); header_write(ctx, buffer, offset, 3, (LQPQ << 4) | 1); for (i = 0; i < V4L2_JPEG_PIXELS_IN_BLOCK; i++) header_write(ctx, buffer, offset, 1, ctx->chroma_qp[v4l2_jpeg_zigzag_scan_index[i]]); /* Huffman tables */ header_write(ctx, buffer, offset, 2, DHT_MARKER); header_write(ctx, buffer, offset, 2, LH_DC); header_write(ctx, buffer, offset, 1, V4L2_JPEG_LUM_HT | V4L2_JPEG_DC_HT); for (i = 0 ; i < V4L2_JPEG_REF_HT_DC_LEN; i++) header_write(ctx, buffer, offset, 1, v4l2_jpeg_ref_table_luma_dc_ht[i]); header_write(ctx, buffer, offset, 2, DHT_MARKER); header_write(ctx, buffer, offset, 2, LH_AC); header_write(ctx, buffer, offset, 1, V4L2_JPEG_LUM_HT | V4L2_JPEG_AC_HT); for (i = 0 ; i < V4L2_JPEG_REF_HT_AC_LEN; i++) header_write(ctx, buffer, offset, 1, v4l2_jpeg_ref_table_luma_ac_ht[i]); header_write(ctx, buffer, offset, 2, DHT_MARKER); header_write(ctx, buffer, offset, 2, LH_DC); header_write(ctx, buffer, offset, 1, V4L2_JPEG_CHR_HT | V4L2_JPEG_DC_HT); for (i = 0 ; i < V4L2_JPEG_REF_HT_DC_LEN; i++) header_write(ctx, buffer, offset, 1, v4l2_jpeg_ref_table_chroma_dc_ht[i]); header_write(ctx, buffer, offset, 2, DHT_MARKER); header_write(ctx, buffer, offset, 2, LH_AC); header_write(ctx, buffer, offset, 1, V4L2_JPEG_CHR_HT | V4L2_JPEG_AC_HT); for (i = 0 ; i < V4L2_JPEG_REF_HT_AC_LEN; i++) header_write(ctx, buffer, offset, 1, v4l2_jpeg_ref_table_chroma_ac_ht[i]); } static void encode_frame_header(struct e5010_context *ctx, void *addr, unsigned int *offset) { u8 *buffer = (u8 *)addr; header_write(ctx, buffer, offset, 2, SOF_BASELINE_DCT); header_write(ctx, buffer, offset, 2, 8 + (3 * UC_NUM_COMP)); header_write(ctx, buffer, offset, 1, PRECISION); header_write(ctx, buffer, offset, 2, ctx->out_queue.crop.height); header_write(ctx, buffer, offset, 2, ctx->out_queue.crop.width); header_write(ctx, buffer, offset, 1, UC_NUM_COMP); /* Luma details */ header_write(ctx, buffer, offset, 1, 1); if (ctx->out_queue.fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) header_write(ctx, buffer, offset, 1, HORZ_SAMPLING_FACTOR | (VERT_SAMPLING_FACTOR_422)); else header_write(ctx, buffer, offset, 1, HORZ_SAMPLING_FACTOR | (VERT_SAMPLING_FACTOR_420)); header_write(ctx, buffer, offset, 1, 0); /* Chroma details */ header_write(ctx, buffer, offset, 1, 2); header_write(ctx, buffer, offset, 1, (HORZ_SAMPLING_FACTOR >> 1) | 1); header_write(ctx, buffer, offset, 1, 1); header_write(ctx, buffer, offset, 1, 3); header_write(ctx, buffer, offset, 1, (HORZ_SAMPLING_FACTOR >> 1) | 1); header_write(ctx, buffer, offset, 1, 1); } static void jpg_encode_sos_header(struct e5010_context *ctx, void *addr, unsigned int *offset) { u8 *buffer = (u8 *)addr; int i; header_write(ctx, buffer, offset, 2, START_OF_SCAN); header_write(ctx, buffer, offset, 2, 6 + (COMPONENTS_IN_SCAN << 1)); header_write(ctx, buffer, offset, 1, COMPONENTS_IN_SCAN); for (i = 0; i < COMPONENTS_IN_SCAN; i++) { header_write(ctx, buffer, offset, 1, i + 1); if (i == 0) header_write(ctx, buffer, offset, 1, 0); else header_write(ctx, buffer, offset, 1, 17); } header_write(ctx, buffer, offset, 1, 0); header_write(ctx, buffer, offset, 1, 63); header_write(ctx, buffer, offset, 1, 0); } static void write_header(struct e5010_context *ctx, void *addr) { unsigned int offset = 0; encode_marker_segment(ctx, addr, &offset); encode_frame_header(ctx, addr, &offset); jpg_encode_sos_header(ctx, addr, &offset); } static irqreturn_t e5010_irq(int irq, void *data) { struct e5010_dev *e5010 = data; struct e5010_context *ctx; int output_size; struct vb2_v4l2_buffer *src_buf, *dst_buf; bool pic_done, out_addr_err; spin_lock(&e5010->hw_lock); pic_done = e5010_hw_pic_done_irq(e5010->core_base); out_addr_err = e5010_hw_output_address_irq(e5010->core_base); if (!pic_done && !out_addr_err) { spin_unlock(&e5010->hw_lock); return IRQ_NONE; } ctx = v4l2_m2m_get_curr_priv(e5010->m2m_dev); if (WARN_ON(!ctx)) goto job_unlock; dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); if (!dst_buf || !src_buf) { v4l2_err(&e5010->v4l2_dev, "ctx: 0x%p No source or destination buffer\n", ctx); goto job_unlock; } if (out_addr_err) { e5010_hw_clear_output_error(e5010->core_base, 1); v4l2_warn(&e5010->v4l2_dev, "ctx: 0x%p Output bitstream size exceeded max size\n", ctx); v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); vb2_set_plane_payload(&dst_buf->vb2_buf, 0, dst_buf->planes[0].length); v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); if (v4l2_m2m_is_last_draining_src_buf(ctx->fh.m2m_ctx, src_buf)) { dst_buf->flags |= V4L2_BUF_FLAG_LAST; v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx); v4l2_event_queue_fh(&ctx->fh, &e5010_eos_event); dprintk(e5010, 2, "ctx: 0x%p Sending EOS\n", ctx); } } if (pic_done) { e5010_hw_clear_picture_done(e5010->core_base, 1); dprintk(e5010, 3, "ctx: 0x%p Got output bitstream of size %d bytes\n", ctx, readl(e5010->core_base + JASPER_OUTPUT_SIZE_OFFSET)); if (v4l2_m2m_is_last_draining_src_buf(ctx->fh.m2m_ctx, src_buf)) { dst_buf->flags |= V4L2_BUF_FLAG_LAST; v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx); v4l2_event_queue_fh(&ctx->fh, &e5010_eos_event); dprintk(e5010, 2, "ctx: 0x%p Sending EOS\n", ctx); } v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); output_size = e5010_hw_get_output_size(e5010->core_base); vb2_set_plane_payload(&dst_buf->vb2_buf, 0, output_size + HEADER_SIZE); v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); dprintk(e5010, 3, "ctx: 0x%p frame done for dst_buf->sequence: %d src_buf->sequence: %d\n", ctx, dst_buf->sequence, src_buf->sequence); } v4l2_m2m_job_finish(e5010->m2m_dev, ctx->fh.m2m_ctx); dprintk(e5010, 3, "ctx: 0x%p Finish job\n", ctx); job_unlock: spin_unlock(&e5010->hw_lock); return IRQ_HANDLED; } static int e5010_init_device(struct e5010_dev *e5010) { int ret = 0; /*TODO: Set MMU in bypass mode until support for the same is added in driver*/ e5010_hw_bypass_mmu(e5010->mmu_base, 1); if (e5010_hw_enable_auto_clock_gating(e5010->core_base, 1)) v4l2_warn(&e5010->v4l2_dev, "failed to enable auto clock gating\n"); if (e5010_hw_enable_manual_clock_gating(e5010->core_base, 0)) v4l2_warn(&e5010->v4l2_dev, "failed to disable manual clock gating\n"); if (e5010_hw_enable_crc_check(e5010->core_base, 0)) v4l2_warn(&e5010->v4l2_dev, "failed to disable CRC check\n"); if (e5010_hw_enable_output_address_error_irq(e5010->core_base, 1)) v4l2_err(&e5010->v4l2_dev, "failed to enable Output Address Error interrupts\n"); ret = e5010_hw_set_input_source_to_memory(e5010->core_base, 1); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set input source to memory\n"); return ret; } ret = e5010_hw_enable_picture_done_irq(e5010->core_base, 1); if (ret) v4l2_err(&e5010->v4l2_dev, "failed to enable Picture Done interrupts\n"); return ret; } static int e5010_probe(struct platform_device *pdev) { struct e5010_dev *e5010; int irq, ret = 0; struct device *dev = &pdev->dev; ret = dma_set_mask(dev, DMA_BIT_MASK(32)); if (ret) return dev_err_probe(dev, ret, "32-bit consistent DMA enable failed\n"); e5010 = devm_kzalloc(dev, sizeof(*e5010), GFP_KERNEL); if (!e5010) return -ENOMEM; platform_set_drvdata(pdev, e5010); e5010->dev = dev; mutex_init(&e5010->mutex); spin_lock_init(&e5010->hw_lock); e5010->vdev = video_device_alloc(); if (!e5010->vdev) { dev_err(dev, "failed to allocate video device\n"); return -ENOMEM; } snprintf(e5010->vdev->name, sizeof(e5010->vdev->name), "%s", E5010_MODULE_NAME); e5010->vdev->fops = &e5010_fops; e5010->vdev->ioctl_ops = &e5010_ioctl_ops; e5010->vdev->minor = -1; e5010->vdev->release = video_device_release; e5010->vdev->vfl_dir = VFL_DIR_M2M; e5010->vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; e5010->vdev->v4l2_dev = &e5010->v4l2_dev; e5010->vdev->lock = &e5010->mutex; ret = v4l2_device_register(dev, &e5010->v4l2_dev); if (ret) return dev_err_probe(dev, ret, "failed to register v4l2 device\n"); e5010->m2m_dev = v4l2_m2m_init(&e5010_m2m_ops); if (IS_ERR(e5010->m2m_dev)) { ret = PTR_ERR(e5010->m2m_dev); e5010->m2m_dev = NULL; dev_err_probe(dev, ret, "failed to init mem2mem device\n"); goto fail_after_v4l2_register; } video_set_drvdata(e5010->vdev, e5010); e5010->core_base = devm_platform_ioremap_resource_byname(pdev, "core"); if (IS_ERR(e5010->core_base)) { ret = PTR_ERR(e5010->core_base); dev_err_probe(dev, ret, "Missing 'core' resources area\n"); goto fail_after_v4l2_register; } e5010->mmu_base = devm_platform_ioremap_resource_byname(pdev, "mmu"); if (IS_ERR(e5010->mmu_base)) { ret = PTR_ERR(e5010->mmu_base); dev_err_probe(dev, ret, "Missing 'mmu' resources area\n"); goto fail_after_v4l2_register; } e5010->last_context_run = NULL; irq = platform_get_irq(pdev, 0); ret = devm_request_irq(dev, irq, e5010_irq, 0, E5010_MODULE_NAME, e5010); if (ret) { dev_err_probe(dev, ret, "failed to register IRQ %d\n", irq); goto fail_after_v4l2_register; } e5010->clk = devm_clk_get(dev, NULL); if (IS_ERR(e5010->clk)) { ret = PTR_ERR(e5010->clk); dev_err_probe(dev, ret, "failed to get clock\n"); goto fail_after_v4l2_register; } pm_runtime_enable(dev); ret = video_register_device(e5010->vdev, VFL_TYPE_VIDEO, 0); if (ret) { dev_err_probe(dev, ret, "failed to register video device\n"); goto fail_after_video_register_device; } v4l2_info(&e5010->v4l2_dev, "Device registered as /dev/video%d\n", e5010->vdev->num); return 0; fail_after_video_register_device: v4l2_m2m_release(e5010->m2m_dev); fail_after_v4l2_register: v4l2_device_unregister(&e5010->v4l2_dev); return ret; } static void e5010_remove(struct platform_device *pdev) { struct e5010_dev *e5010 = platform_get_drvdata(pdev); pm_runtime_disable(e5010->dev); video_unregister_device(e5010->vdev); v4l2_m2m_release(e5010->m2m_dev); v4l2_device_unregister(&e5010->v4l2_dev); } static void e5010_vb2_buffers_return(struct vb2_queue *q, enum vb2_buffer_state state) { struct vb2_v4l2_buffer *vbuf; struct e5010_context *ctx = vb2_get_drv_priv(q); if (V4L2_TYPE_IS_OUTPUT(q->type)) { while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) { dprintk(ctx->e5010, 2, "ctx: 0x%p, buf type %s | index %d\n", ctx, type_name(vbuf->vb2_buf.type), vbuf->vb2_buf.index); v4l2_m2m_buf_done(vbuf, state); } } else { while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) { dprintk(ctx->e5010, 2, "ctx: 0x%p, buf type %s | index %d\n", ctx, type_name(vbuf->vb2_buf.type), vbuf->vb2_buf.index); vb2_set_plane_payload(&vbuf->vb2_buf, 0, 0); v4l2_m2m_buf_done(vbuf, state); } } } static int e5010_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { struct e5010_context *ctx = vb2_get_drv_priv(vq); struct e5010_q_data *queue; int i; queue = get_queue(ctx, vq->type); if (*nplanes) { if (*nplanes != queue->fmt->num_planes) return -EINVAL; for (i = 0; i < *nplanes; i++) { if (sizes[i] < queue->sizeimage[i]) return -EINVAL; } return 0; } *nplanes = queue->fmt->num_planes; for (i = 0; i < *nplanes; i++) sizes[i] = queue->sizeimage[i]; dprintk(ctx->e5010, 2, "ctx: 0x%p, type %s, buffer(s): %d, planes %d, plane1: bytes %d plane2: %d bytes\n", ctx, type_name(vq->type), *nbuffers, *nplanes, sizes[0], sizes[1]); return 0; } static void e5010_buf_finish(struct vb2_buffer *vb) { struct e5010_context *ctx = vb2_get_drv_priv(vb->vb2_queue); void *d_addr; if (vb->state != VB2_BUF_STATE_DONE || V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) return; d_addr = vb2_plane_vaddr(vb, 0); write_header(ctx, d_addr); } static int e5010_buf_out_validate(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct e5010_context *ctx = vb2_get_drv_priv(vb->vb2_queue); if (vbuf->field != V4L2_FIELD_NONE) dprintk(ctx->e5010, 1, "ctx: 0x%p, field isn't supported\n", ctx); vbuf->field = V4L2_FIELD_NONE; return 0; } static int e5010_buf_prepare(struct vb2_buffer *vb) { struct e5010_context *ctx = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct e5010_q_data *queue; int i; vbuf->field = V4L2_FIELD_NONE; queue = get_queue(ctx, vb->vb2_queue->type); for (i = 0; i < queue->fmt->num_planes; i++) { if (vb2_plane_size(vb, i) < (unsigned long)queue->sizeimage[i]) { v4l2_err(&ctx->e5010->v4l2_dev, "plane %d too small (%lu < %lu)", i, vb2_plane_size(vb, i), (unsigned long)queue->sizeimage[i]); return -EINVAL; } } if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) { vb2_set_plane_payload(vb, 0, 0); vb2_set_plane_payload(vb, 1, 0); } return 0; } static void e5010_buf_queue(struct vb2_buffer *vb) { struct e5010_context *ctx = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && vb2_is_streaming(vb->vb2_queue) && v4l2_m2m_dst_buf_is_last(ctx->fh.m2m_ctx)) { struct e5010_q_data *queue = get_queue(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); vbuf->sequence = queue->sequence++; v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vbuf); v4l2_event_queue_fh(&ctx->fh, &e5010_eos_event); return; } v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); } static int e5010_encoder_cmd(struct file *file, void *priv, struct v4l2_encoder_cmd *cmd) { struct e5010_context *ctx = file->private_data; int ret; struct vb2_queue *cap_vq; cap_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); ret = v4l2_m2m_ioctl_try_encoder_cmd(file, &ctx->fh, cmd); if (ret < 0) return ret; if (!vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx)) || !vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx))) return 0; ret = v4l2_m2m_ioctl_encoder_cmd(file, &ctx->fh, cmd); if (ret < 0) return ret; if (cmd->cmd == V4L2_ENC_CMD_STOP && v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) v4l2_event_queue_fh(&ctx->fh, &e5010_eos_event); if (cmd->cmd == V4L2_ENC_CMD_START && v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) vb2_clear_last_buffer_dequeued(cap_vq); return 0; } static int e5010_start_streaming(struct vb2_queue *q, unsigned int count) { struct e5010_context *ctx = vb2_get_drv_priv(q); int ret; struct e5010_q_data *queue = get_queue(ctx, q->type); v4l2_m2m_update_start_streaming_state(ctx->fh.m2m_ctx, q); queue->sequence = 0; ret = pm_runtime_resume_and_get(ctx->e5010->dev); if (ret < 0) { v4l2_err(&ctx->e5010->v4l2_dev, "failed to power up jpeg\n"); goto fail; } ret = e5010_init_device(ctx->e5010); if (ret) { v4l2_err(&ctx->e5010->v4l2_dev, "failed to Enable e5010 device\n"); goto fail; } return 0; fail: e5010_vb2_buffers_return(q, VB2_BUF_STATE_QUEUED); return ret; } static void e5010_stop_streaming(struct vb2_queue *q) { struct e5010_context *ctx = vb2_get_drv_priv(q); e5010_vb2_buffers_return(q, VB2_BUF_STATE_ERROR); if (V4L2_TYPE_IS_OUTPUT(q->type)) v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); if (V4L2_TYPE_IS_OUTPUT(q->type) && v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) { v4l2_event_queue_fh(&ctx->fh, &e5010_eos_event); } pm_runtime_put_sync(ctx->e5010->dev); } static void e5010_device_run(void *priv) { struct e5010_context *ctx = priv; struct e5010_dev *e5010 = ctx->e5010; struct vb2_v4l2_buffer *s_vb, *d_vb; u32 reg = 0; int ret = 0, luma_crop_offset = 0, chroma_crop_offset = 0; unsigned long flags; int num_planes = ctx->out_queue.fmt->num_planes; spin_lock_irqsave(&e5010->hw_lock, flags); s_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); WARN_ON(!s_vb); d_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); WARN_ON(!d_vb); if (!s_vb || !d_vb) goto no_ready_buf_err; s_vb->sequence = ctx->out_queue.sequence++; d_vb->sequence = ctx->cap_queue.sequence++; v4l2_m2m_buf_copy_metadata(s_vb, d_vb, false); if (ctx != e5010->last_context_run || ctx->update_qp) { dprintk(e5010, 1, "ctx updated: 0x%p -> 0x%p, updating qp tables\n", e5010->last_context_run, ctx); ret = update_qp_tables(ctx); } if (ret) { ctx->update_qp = true; v4l2_err(&e5010->v4l2_dev, "failed to update QP tables\n"); goto device_busy_err; } else { e5010->last_context_run = ctx; ctx->update_qp = false; } /* Set I/O Buffer addresses */ reg = (u32)vb2_dma_contig_plane_dma_addr(&s_vb->vb2_buf, 0); if (ctx->out_queue.crop_set) { luma_crop_offset = ctx->out_queue.bytesperline[0] * ctx->out_queue.crop.top + ctx->out_queue.crop.left; if (ctx->out_queue.fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) { chroma_crop_offset = ctx->out_queue.bytesperline[0] * ctx->out_queue.crop.top + ctx->out_queue.crop.left; } else { chroma_crop_offset = ctx->out_queue.bytesperline[0] * ctx->out_queue.crop.top / 2 + ctx->out_queue.crop.left; } dprintk(e5010, 1, "Luma crop offset : %x, chroma crop offset : %x\n", luma_crop_offset, chroma_crop_offset); } ret = e5010_hw_set_input_luma_addr(e5010->core_base, reg + luma_crop_offset); if (ret || !reg) { v4l2_err(&e5010->v4l2_dev, "failed to set input luma address\n"); goto device_busy_err; } if (num_planes == 1) reg += (ctx->out_queue.bytesperline[0]) * (ctx->out_queue.height); else reg = (u32)vb2_dma_contig_plane_dma_addr(&s_vb->vb2_buf, 1); dprintk(e5010, 3, "ctx: 0x%p, luma_addr: 0x%x, chroma_addr: 0x%x, out_addr: 0x%x\n", ctx, (u32)vb2_dma_contig_plane_dma_addr(&s_vb->vb2_buf, 0) + luma_crop_offset, reg + chroma_crop_offset, (u32)vb2_dma_contig_plane_dma_addr(&d_vb->vb2_buf, 0)); dprintk(e5010, 3, "ctx: 0x%p, buf indices: src_index: %d, dst_index: %d\n", ctx, s_vb->vb2_buf.index, d_vb->vb2_buf.index); ret = e5010_hw_set_input_chroma_addr(e5010->core_base, reg + chroma_crop_offset); if (ret || !reg) { v4l2_err(&e5010->v4l2_dev, "failed to set input chroma address\n"); goto device_busy_err; } reg = (u32)vb2_dma_contig_plane_dma_addr(&d_vb->vb2_buf, 0); reg += HEADER_SIZE; ret = e5010_hw_set_output_base_addr(e5010->core_base, reg); if (ret || !reg) { v4l2_err(&e5010->v4l2_dev, "failed to set output base address\n"); goto device_busy_err; } /* Set input settings */ ret = e5010_hw_set_horizontal_size(e5010->core_base, ctx->out_queue.crop.width - 1); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set input width\n"); goto device_busy_err; } ret = e5010_hw_set_vertical_size(e5010->core_base, ctx->out_queue.crop.height - 1); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set input width\n"); goto device_busy_err; } ret = e5010_hw_set_luma_stride(e5010->core_base, ctx->out_queue.bytesperline[0]); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set luma stride\n"); goto device_busy_err; } ret = e5010_hw_set_chroma_stride(e5010->core_base, ctx->out_queue.bytesperline[0]); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set chroma stride\n"); goto device_busy_err; } ret = e5010_set_input_subsampling(e5010->core_base, ctx->out_queue.fmt->subsampling); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set input subsampling\n"); goto device_busy_err; } ret = e5010_hw_set_chroma_order(e5010->core_base, ctx->out_queue.fmt->chroma_order); if (ret) { v4l2_err(&e5010->v4l2_dev, "failed to set chroma order\n"); goto device_busy_err; } e5010_hw_set_output_max_size(e5010->core_base, d_vb->planes[0].length); e5010_hw_encode_start(e5010->core_base, 1); spin_unlock_irqrestore(&e5010->hw_lock, flags); return; device_busy_err: e5010_reset(e5010->dev, e5010->core_base, e5010->mmu_base); no_ready_buf_err: if (s_vb) { v4l2_m2m_src_buf_remove_by_buf(ctx->fh.m2m_ctx, s_vb); v4l2_m2m_buf_done(s_vb, VB2_BUF_STATE_ERROR); } if (d_vb) { v4l2_m2m_dst_buf_remove_by_buf(ctx->fh.m2m_ctx, d_vb); /* Payload set to 1 since 0 payload can trigger EOS */ vb2_set_plane_payload(&d_vb->vb2_buf, 0, 1); v4l2_m2m_buf_done(d_vb, VB2_BUF_STATE_ERROR); } v4l2_m2m_job_finish(e5010->m2m_dev, ctx->fh.m2m_ctx); spin_unlock_irqrestore(&e5010->hw_lock, flags); } #ifdef CONFIG_PM static int e5010_runtime_resume(struct device *dev) { struct e5010_dev *e5010 = dev_get_drvdata(dev); int ret; ret = clk_prepare_enable(e5010->clk); if (ret < 0) { v4l2_err(&e5010->v4l2_dev, "failed to enable clock\n"); return ret; } return 0; } static int e5010_runtime_suspend(struct device *dev) { struct e5010_dev *e5010 = dev_get_drvdata(dev); clk_disable_unprepare(e5010->clk); return 0; } #endif #ifdef CONFIG_PM_SLEEP static int e5010_suspend(struct device *dev) { struct e5010_dev *e5010 = dev_get_drvdata(dev); v4l2_m2m_suspend(e5010->m2m_dev); return pm_runtime_force_suspend(dev); } static int e5010_resume(struct device *dev) { struct e5010_dev *e5010 = dev_get_drvdata(dev); int ret; ret = pm_runtime_force_resume(dev); if (ret < 0) return ret; ret = e5010_init_device(e5010); if (ret) { dev_err(dev, "Failed to re-enable e5010 device\n"); return ret; } v4l2_m2m_resume(e5010->m2m_dev); return ret; } #endif static const struct dev_pm_ops e5010_pm_ops = { SET_RUNTIME_PM_OPS(e5010_runtime_suspend, e5010_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(e5010_suspend, e5010_resume) }; static const struct v4l2_ioctl_ops e5010_ioctl_ops = { .vidioc_querycap = e5010_querycap, .vidioc_enum_fmt_vid_cap = e5010_enum_fmt, .vidioc_g_fmt_vid_cap_mplane = e5010_g_fmt, .vidioc_try_fmt_vid_cap_mplane = e5010_try_fmt, .vidioc_s_fmt_vid_cap_mplane = e5010_s_fmt, .vidioc_enum_fmt_vid_out = e5010_enum_fmt, .vidioc_g_fmt_vid_out_mplane = e5010_g_fmt, .vidioc_try_fmt_vid_out_mplane = e5010_try_fmt, .vidioc_s_fmt_vid_out_mplane = e5010_s_fmt, .vidioc_g_selection = e5010_g_selection, .vidioc_s_selection = e5010_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_expbuf = v4l2_m2m_ioctl_expbuf, .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, .vidioc_streamon = v4l2_m2m_ioctl_streamon, .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_log_status = v4l2_ctrl_log_status, .vidioc_subscribe_event = e5010_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, .vidioc_encoder_cmd = e5010_encoder_cmd, .vidioc_enum_framesizes = e5010_enum_framesizes, }; static const struct vb2_ops e5010_video_ops = { .queue_setup = e5010_queue_setup, .buf_queue = e5010_buf_queue, .buf_finish = e5010_buf_finish, .buf_prepare = e5010_buf_prepare, .buf_out_validate = e5010_buf_out_validate, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, .start_streaming = e5010_start_streaming, .stop_streaming = e5010_stop_streaming, }; static const struct v4l2_file_operations e5010_fops = { .owner = THIS_MODULE, .open = e5010_open, .release = e5010_release, .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = v4l2_m2m_fop_mmap, }; static const struct v4l2_m2m_ops e5010_m2m_ops = { .device_run = e5010_device_run, }; static const struct of_device_id e5010_of_match[] = { {.compatible = "img,e5010-jpeg-enc"}, { /* end */}, }; MODULE_DEVICE_TABLE(of, e5010_of_match); static struct platform_driver e5010_driver = { .probe = e5010_probe, .remove_new = e5010_remove, .driver = { .name = E5010_MODULE_NAME, .of_match_table = e5010_of_match, .pm = &e5010_pm_ops, }, }; module_platform_driver(e5010_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Imagination E5010 JPEG encoder driver");