// 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 "cedrus.h" #include "cedrus_video.h" #include "cedrus_dec.h" #include "cedrus_hw.h" static const struct cedrus_control cedrus_controls[] = { { .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS, .elem_size = sizeof(struct v4l2_ctrl_mpeg2_slice_params), .codec = CEDRUS_CODEC_MPEG2, .required = true, }, { .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION, .elem_size = sizeof(struct v4l2_ctrl_mpeg2_quantization), .codec = CEDRUS_CODEC_MPEG2, .required = false, }, }; #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; } 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; v4l2_ctrl_handler_init(hdl, CEDRUS_CONTROLS_COUNT); if (hdl->error) { v4l2_err(&dev->v4l2_dev, "Failed to initialize control handler\n"); return hdl->error; } ctrl_size = sizeof(ctrl) * CEDRUS_CONTROLS_COUNT + 1; ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL); if (!ctx->ctrls) return -ENOMEM; for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { struct v4l2_ctrl_config cfg = {}; cfg.elem_size = cedrus_controls[i].elem_size; cfg.id = cedrus_controls[i].id; ctrl = v4l2_ctrl_new_custom(hdl, &cfg, NULL); if (hdl->error) { v4l2_err(&dev->v4l2_dev, "Failed to create new custom control\n"); v4l2_ctrl_handler_free(hdl); kfree(ctx->ctrls); return hdl->error; } ctx->ctrls[i] = 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 v4l2_ctrl_handler *parent_hdl, *hdl; struct cedrus_ctx *ctx = NULL; struct v4l2_ctrl *ctrl_test; unsigned int count; unsigned int i; 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; } parent_hdl = &ctx->hdl; hdl = v4l2_ctrl_request_hdl_find(req, parent_hdl); if (!hdl) { v4l2_info(&ctx->dev->v4l2_dev, "Missing codec control(s)\n"); return -ENOENT; } for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { if (cedrus_controls[i].codec != ctx->current_codec || !cedrus_controls[i].required) continue; ctrl_test = v4l2_ctrl_request_hdl_ctrl_find(hdl, cedrus_controls[i].id); if (!ctrl_test) { v4l2_info(&ctx->dev->v4l2_dev, "Missing required codec control\n"); return -ENOENT; } } v4l2_ctrl_request_hdl_put(hdl); 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; ret = cedrus_init_ctrls(dev, ctx); if (ret) goto err_free; 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_ctrls; } v4l2_fh_add(&ctx->fh); mutex_unlock(&dev->dev_mutex); return 0; err_ctrls: v4l2_ctrl_handler_free(&ctx->hdl); 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; 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; } dev->dec_ops[CEDRUS_CODEC_MPEG2] = &cedrus_dec_ops_mpeg2; mutex_init(&dev->dev_mutex); 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_GRABBER, 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; } platform_set_drvdata(pdev, dev); 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 int cedrus_remove(struct platform_device *pdev) { struct cedrus_dev *dev = platform_get_drvdata(pdev); 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); return 0; } static const struct cedrus_variant sun4i_a10_cedrus_variant = { /* No particular capability. */ }; static const struct cedrus_variant sun5i_a13_cedrus_variant = { /* No particular capability. */ }; static const struct cedrus_variant sun7i_a20_cedrus_variant = { /* No particular capability. */ }; static const struct cedrus_variant sun8i_a33_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED, }; static const struct cedrus_variant sun8i_h3_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED, }; static const struct cedrus_variant sun50i_a64_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED, }; static const struct cedrus_variant sun50i_h5_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED, }; static const struct cedrus_variant sun50i_h6_cedrus_variant = { .capabilities = CEDRUS_CAPABILITY_UNTILED, .quirks = CEDRUS_QUIRK_NO_DMA_OFFSET, }; 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,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 struct platform_driver cedrus_driver = { .probe = cedrus_probe, .remove = cedrus_remove, .driver = { .name = CEDRUS_NAME, .of_match_table = of_match_ptr(cedrus_dt_match), }, }; 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");