// SPDX-License-Identifier: GPL-2.0 /* * Cedrus VPU driver * * Copyright (C) 2016 Florent Revest * Copyright (C) 2018 Paul Kocialkowski * Copyright (C) 2018 Bootlin * * Based on the vim2m driver, that is: * * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. * Pawel Osciak, * Marek Szyprowski, */ #include #include #include #include #include #include #include #include #include "cedrus.h" #include "cedrus_video.h" #include "cedrus_dec.h" #include "cedrus_hw.h" static int cedrus_try_ctrl(struct v4l2_ctrl *ctrl) { if (ctrl->id == V4L2_CID_STATELESS_H264_SPS) { const struct v4l2_ctrl_h264_sps *sps = ctrl->p_new.p_h264_sps; if (sps->chroma_format_idc != 1) /* Only 4:2:0 is supported */ return -EINVAL; if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) /* Luma and chroma bit depth mismatch */ return -EINVAL; if (sps->bit_depth_luma_minus8 != 0) /* Only 8-bit is supported */ return -EINVAL; } else if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) { const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps; struct cedrus_ctx *ctx = container_of(ctrl->handler, struct cedrus_ctx, hdl); unsigned int bit_depth, max_depth; struct vb2_queue *vq; if (sps->chroma_format_idc != 1) /* Only 4:2:0 is supported */ return -EINVAL; bit_depth = max(sps->bit_depth_luma_minus8, sps->bit_depth_chroma_minus8) + 8; if (cedrus_is_capable(ctx, CEDRUS_CAPABILITY_H265_10_DEC)) max_depth = 10; else max_depth = 8; if (bit_depth > max_depth) return -EINVAL; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); /* * Bit depth can't be higher than currently set once * buffers are allocated. */ if (vb2_is_busy(vq)) { if (ctx->bit_depth < bit_depth) return -EINVAL; } else { ctx->bit_depth = bit_depth; cedrus_reset_cap_format(ctx); } } return 0; } static const struct v4l2_ctrl_ops cedrus_ctrl_ops = { .try_ctrl = cedrus_try_ctrl, }; static const struct cedrus_control cedrus_controls[] = { { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_SEQUENCE, }, .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_PICTURE, }, .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_MPEG2_QUANTISATION, }, .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SPS, .ops = &cedrus_ctrl_ops, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_PPS, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_PRED_WEIGHTS, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_DECODE_MODE, .max = V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED, .def = V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_H264_START_CODE, .max = V4L2_STATELESS_H264_START_CODE_NONE, .def = V4L2_STATELESS_H264_START_CODE_NONE, }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, /* * We only expose supported profiles information, * and not levels as it's not clear what is supported * for each hardware/core version. * In any case, TRY/S_FMT will clamp the format resolution * to the maximum supported. */ { .cfg = { .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, .menu_skip_mask = BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED), }, .capabilities = CEDRUS_CAPABILITY_H264_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_SPS, .ops = &cedrus_ctrl_ops, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_PPS, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS, /* The driver can only handle 1 entry per slice for now */ .dims = { 1 }, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS, /* maximum 256 entry point offsets per slice */ .dims = { 256 }, .max = 0xffffffff, .step = 1, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, .max = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, .def = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_START_CODE, .max = V4L2_STATELESS_HEVC_START_CODE_NONE, .def = V4L2_STATELESS_HEVC_START_CODE_NONE, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_VP8_FRAME, }, .capabilities = CEDRUS_CAPABILITY_VP8_DEC, }, { .cfg = { .id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, }, .capabilities = CEDRUS_CAPABILITY_H265_DEC, }, }; #define CEDRUS_CONTROLS_COUNT ARRAY_SIZE(cedrus_controls) void *cedrus_find_control_data(struct cedrus_ctx *ctx, u32 id) { unsigned int i; for (i = 0; ctx->ctrls[i]; i++) if (ctx->ctrls[i]->id == id) return ctx->ctrls[i]->p_cur.p; return NULL; } u32 cedrus_get_num_of_controls(struct cedrus_ctx *ctx, u32 id) { unsigned int i; for (i = 0; ctx->ctrls[i]; i++) if (ctx->ctrls[i]->id == id) return ctx->ctrls[i]->elems; return 0; } static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx) { struct v4l2_ctrl_handler *hdl = &ctx->hdl; struct v4l2_ctrl *ctrl; unsigned int ctrl_size; unsigned int i, j; v4l2_ctrl_handler_init(hdl, CEDRUS_CONTROLS_COUNT); if (hdl->error) { v4l2_err(&dev->v4l2_dev, "Failed to initialize control handler: %d\n", hdl->error); return hdl->error; } ctrl_size = sizeof(ctrl) * CEDRUS_CONTROLS_COUNT + 1; ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL); if (!ctx->ctrls) return -ENOMEM; j = 0; for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { if (!cedrus_is_capable(ctx, cedrus_controls[i].capabilities)) continue; ctrl = v4l2_ctrl_new_custom(hdl, &cedrus_controls[i].cfg, NULL); if (hdl->error) { v4l2_err(&dev->v4l2_dev, "Failed to create %s control: %d\n", v4l2_ctrl_get_name(cedrus_controls[i].cfg.id), hdl->error); v4l2_ctrl_handler_free(hdl); kfree(ctx->ctrls); ctx->ctrls = NULL; return hdl->error; } ctx->ctrls[j++] = ctrl; } ctx->fh.ctrl_handler = hdl; v4l2_ctrl_handler_setup(hdl); return 0; } static int cedrus_request_validate(struct media_request *req) { struct media_request_object *obj; struct cedrus_ctx *ctx = NULL; unsigned int count; list_for_each_entry(obj, &req->objects, list) { struct vb2_buffer *vb; if (vb2_request_object_is_buffer(obj)) { vb = container_of(obj, struct vb2_buffer, req_obj); ctx = vb2_get_drv_priv(vb->vb2_queue); break; } } if (!ctx) return -ENOENT; count = vb2_request_buffer_cnt(req); if (!count) { v4l2_info(&ctx->dev->v4l2_dev, "No buffer was provided with the request\n"); return -ENOENT; } else if (count > 1) { v4l2_info(&ctx->dev->v4l2_dev, "More than one buffer was provided with the request\n"); return -EINVAL; } return vb2_request_validate(req); } static int cedrus_open(struct file *file) { struct cedrus_dev *dev = video_drvdata(file); struct cedrus_ctx *ctx = NULL; int ret; if (mutex_lock_interruptible(&dev->dev_mutex)) return -ERESTARTSYS; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) { mutex_unlock(&dev->dev_mutex); return -ENOMEM; } v4l2_fh_init(&ctx->fh, video_devdata(file)); file->private_data = &ctx->fh; ctx->dev = dev; ctx->bit_depth = 8; ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &cedrus_queue_init); if (IS_ERR(ctx->fh.m2m_ctx)) { ret = PTR_ERR(ctx->fh.m2m_ctx); goto err_free; } cedrus_reset_out_format(ctx); ret = cedrus_init_ctrls(dev, ctx); if (ret) goto err_m2m_release; v4l2_fh_add(&ctx->fh); mutex_unlock(&dev->dev_mutex); return 0; err_m2m_release: v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); err_free: kfree(ctx); mutex_unlock(&dev->dev_mutex); return ret; } static int cedrus_release(struct file *file) { struct cedrus_dev *dev = video_drvdata(file); struct cedrus_ctx *ctx = container_of(file->private_data, struct cedrus_ctx, fh); mutex_lock(&dev->dev_mutex); v4l2_fh_del(&ctx->fh); v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); v4l2_ctrl_handler_free(&ctx->hdl); kfree(ctx->ctrls); v4l2_fh_exit(&ctx->fh); kfree(ctx); mutex_unlock(&dev->dev_mutex); return 0; } static const struct v4l2_file_operations cedrus_fops = { .owner = THIS_MODULE, .open = cedrus_open, .release = cedrus_release, .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = v4l2_m2m_fop_mmap, }; static const struct video_device cedrus_video_device = { .name = CEDRUS_NAME, .vfl_dir = VFL_DIR_M2M, .fops = &cedrus_fops, .ioctl_ops = &cedrus_ioctl_ops, .minor = -1, .release = video_device_release_empty, .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, }; static const struct v4l2_m2m_ops cedrus_m2m_ops = { .device_run = cedrus_device_run, }; static const struct media_device_ops cedrus_m2m_media_ops = { .req_validate = cedrus_request_validate, .req_queue = v4l2_m2m_request_queue, }; static int cedrus_probe(struct platform_device *pdev) { struct cedrus_dev *dev; struct video_device *vfd; int ret; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; platform_set_drvdata(pdev, dev); dev->vfd = cedrus_video_device; dev->dev = &pdev->dev; dev->pdev = pdev; ret = cedrus_hw_probe(dev); if (ret) { dev_err(&pdev->dev, "Failed to probe hardware\n"); return ret; } mutex_init(&dev->dev_mutex); INIT_DELAYED_WORK(&dev->watchdog_work, cedrus_watchdog); ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) { dev_err(&pdev->dev, "Failed to register V4L2 device\n"); return ret; } vfd = &dev->vfd; vfd->lock = &dev->dev_mutex; vfd->v4l2_dev = &dev->v4l2_dev; snprintf(vfd->name, sizeof(vfd->name), "%s", cedrus_video_device.name); video_set_drvdata(vfd, dev); dev->m2m_dev = v4l2_m2m_init(&cedrus_m2m_ops); if (IS_ERR(dev->m2m_dev)) { v4l2_err(&dev->v4l2_dev, "Failed to initialize V4L2 M2M device\n"); ret = PTR_ERR(dev->m2m_dev); goto err_v4l2; } dev->mdev.dev = &pdev->dev; strscpy(dev->mdev.model, CEDRUS_NAME, sizeof(dev->mdev.model)); strscpy(dev->mdev.bus_info, "platform:" CEDRUS_NAME, sizeof(dev->mdev.bus_info)); media_device_init(&dev->mdev); dev->mdev.ops = &cedrus_m2m_media_ops; dev->v4l2_dev.mdev = &dev->mdev; ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); if (ret) { v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); goto err_m2m; } v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d\n", vfd->num); ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, MEDIA_ENT_F_PROC_VIDEO_DECODER); if (ret) { v4l2_err(&dev->v4l2_dev, "Failed to initialize V4L2 M2M media controller\n"); goto err_video; } ret = media_device_register(&dev->mdev); if (ret) { v4l2_err(&dev->v4l2_dev, "Failed to register media device\n"); goto err_m2m_mc; } return 0; err_m2m_mc: v4l2_m2m_unregister_media_controller(dev->m2m_dev); err_video: video_unregister_device(&dev->vfd); err_m2m: v4l2_m2m_release(dev->m2m_dev); err_v4l2: v4l2_device_unregister(&dev->v4l2_dev); return ret; } static void cedrus_remove(struct platform_device *pdev) { struct cedrus_dev *dev = platform_get_drvdata(pdev); cancel_delayed_work_sync(&dev->watchdog_work); if (media_devnode_is_registered(dev->mdev.devnode)) { media_device_unregister(&dev->mdev); v4l2_m2m_unregister_media_controller(dev->m2m_dev); media_device_cleanup(&dev->mdev); } v4l2_m2m_release(dev->m2m_dev); video_unregister_device(&dev->vfd); v4l2_device_unregister(&dev->v4l2_dev); cedrus_hw_remove(dev); } static const struct cedrus_variant sun4i_a10_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 320000000, }; static const struct cedrus_variant sun5i_a13_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 320000000, }; static const struct cedrus_variant sun7i_a20_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 320000000, }; static const struct cedrus_variant sun8i_a33_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 320000000, }; static const struct cedrus_variant sun8i_h3_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_H265_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 402000000, }; static const struct cedrus_variant sun8i_v3s_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_H264_DEC, .mod_rate = 297000000, }; static const struct cedrus_variant sun8i_r40_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 297000000, }; static const struct cedrus_variant sun20i_d1_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_H265_DEC, .mod_rate = 432000000, }; static const struct cedrus_variant sun50i_a64_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_H265_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 402000000, }; static const struct cedrus_variant sun50i_h5_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_H265_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 402000000, }; static const struct cedrus_variant sun50i_h6_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED | CEDRUS_CAPABILITY_MPEG2_DEC | CEDRUS_CAPABILITY_H264_DEC | CEDRUS_CAPABILITY_H265_DEC | CEDRUS_CAPABILITY_H265_10_DEC | CEDRUS_CAPABILITY_VP8_DEC, .mod_rate = 600000000, }; static const struct of_device_id cedrus_dt_match[] = { { .compatible = "allwinner,sun4i-a10-video-engine", .data = &sun4i_a10_cedrus_variant, }, { .compatible = "allwinner,sun5i-a13-video-engine", .data = &sun5i_a13_cedrus_variant, }, { .compatible = "allwinner,sun7i-a20-video-engine", .data = &sun7i_a20_cedrus_variant, }, { .compatible = "allwinner,sun8i-a33-video-engine", .data = &sun8i_a33_cedrus_variant, }, { .compatible = "allwinner,sun8i-h3-video-engine", .data = &sun8i_h3_cedrus_variant, }, { .compatible = "allwinner,sun8i-v3s-video-engine", .data = &sun8i_v3s_cedrus_variant, }, { .compatible = "allwinner,sun8i-r40-video-engine", .data = &sun8i_r40_cedrus_variant, }, { .compatible = "allwinner,sun20i-d1-video-engine", .data = &sun20i_d1_cedrus_variant, }, { .compatible = "allwinner,sun50i-a64-video-engine", .data = &sun50i_a64_cedrus_variant, }, { .compatible = "allwinner,sun50i-h5-video-engine", .data = &sun50i_h5_cedrus_variant, }, { .compatible = "allwinner,sun50i-h6-video-engine", .data = &sun50i_h6_cedrus_variant, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, cedrus_dt_match); static const struct dev_pm_ops cedrus_dev_pm_ops = { SET_RUNTIME_PM_OPS(cedrus_hw_suspend, cedrus_hw_resume, NULL) }; static struct platform_driver cedrus_driver = { .probe = cedrus_probe, .remove_new = cedrus_remove, .driver = { .name = CEDRUS_NAME, .of_match_table = of_match_ptr(cedrus_dt_match), .pm = &cedrus_dev_pm_ops, }, }; module_platform_driver(cedrus_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Florent Revest "); MODULE_AUTHOR("Paul Kocialkowski "); MODULE_AUTHOR("Maxime Ripard "); MODULE_DESCRIPTION("Cedrus VPU driver");