mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
media: platform: Add Raspberry Pi HEVC decoder driver
The BCM2711 and BCM2712 SoCs used on Rapsberry Pi 4 and Raspberry Pi 5 boards include an HEVC decoder block. Add a driver for it. Signed-off-by: John Cox <john.cox@raspberrypi.com> Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com> media: hevc_dec: Add in downstream single planar SAND variant Upstream will take the multi-planar SAND format, but add back in the downstream single planar variant for backwards compatibility Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com> media: hevc_dec: Add module parameter for video_nr To avoid user complaints that /dev/video0 isn't their USB webcam, add downstream patch that allows setting the preferred video device number. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
This commit is contained in:
10
MAINTAINERS
10
MAINTAINERS
@@ -21102,6 +21102,16 @@ S: Maintained
|
|||||||
F: Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
|
F: Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
|
||||||
F: drivers/spi/spi-rp2040-gpio-bridge.c
|
F: drivers/spi/spi-rp2040-gpio-bridge.c
|
||||||
|
|
||||||
|
RASPBERRY PI HEVC DECODER
|
||||||
|
M: John Cox <john.cox@raspberrypi.com>
|
||||||
|
M: Dom Cobley <dom@raspberrypi.com>
|
||||||
|
M: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||||
|
M: Raspberry Pi Internal Kernel List <kernel-list@raspberrypi.com>
|
||||||
|
L: linux-media@vger.kernel.org
|
||||||
|
S: Maintained
|
||||||
|
F: Documentation/devicetree/bindings/media/raspberrypi,rpi_hevc_dec.yaml
|
||||||
|
F: drivers/media/platform/raspberrypi/hevc_dec
|
||||||
|
|
||||||
RASPBERRY PI PISP BACK END
|
RASPBERRY PI PISP BACK END
|
||||||
M: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
|
M: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
|
||||||
R: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
|
R: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
comment "Raspberry Pi media platform drivers"
|
comment "Raspberry Pi media platform drivers"
|
||||||
|
|
||||||
|
source "drivers/media/platform/raspberrypi/hevc_dec/Kconfig"
|
||||||
source "drivers/media/platform/raspberrypi/pisp_be/Kconfig"
|
source "drivers/media/platform/raspberrypi/pisp_be/Kconfig"
|
||||||
source "drivers/media/platform/raspberrypi/rp1-cfe/Kconfig"
|
source "drivers/media/platform/raspberrypi/rp1-cfe/Kconfig"
|
||||||
source "drivers/media/platform/raspberrypi/rp1_cfe/Kconfig"
|
source "drivers/media/platform/raspberrypi/rp1_cfe/Kconfig"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
obj-y += hevc_dec/
|
||||||
obj-y += pisp_be/
|
obj-y += pisp_be/
|
||||||
obj-y += rp1-cfe/
|
obj-y += rp1-cfe/
|
||||||
obj-y += rp1_cfe/
|
obj-y += rp1_cfe/
|
||||||
|
|||||||
17
drivers/media/platform/raspberrypi/hevc_dec/Kconfig
Normal file
17
drivers/media/platform/raspberrypi/hevc_dec/Kconfig
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
config VIDEO_RPI_HEVC_DEC
|
||||||
|
tristate "Rasperry Pi HEVC decoder"
|
||||||
|
depends on VIDEO_DEV && VIDEO_DEV
|
||||||
|
depends on OF
|
||||||
|
select MEDIA_CONTROLLER
|
||||||
|
select MEDIA_CONTROLLER_REQUEST_API
|
||||||
|
select VIDEOBUF2_DMA_CONTIG
|
||||||
|
select V4L2_MEM2MEM_DEV
|
||||||
|
help
|
||||||
|
Support for the Raspberry Pi HEVC / H265 H/W decoder as a stateless
|
||||||
|
V4L2 decoder device.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here: the module
|
||||||
|
will be called rpi-hevc-dec.
|
||||||
|
|
||||||
5
drivers/media/platform/raspberrypi/hevc_dec/Makefile
Normal file
5
drivers/media/platform/raspberrypi/hevc_dec/Makefile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
obj-$(CONFIG_VIDEO_RPI_HEVC_DEC) += rpi-hevc-dec.o
|
||||||
|
|
||||||
|
rpi-hevc-dec-y = hevc_d.o hevc_d_video.o hevc_d_hw.o\
|
||||||
|
hevc_d_h265.o
|
||||||
459
drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
Normal file
459
drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include <media/v4l2-device.h>
|
||||||
|
#include <media/v4l2-ioctl.h>
|
||||||
|
#include <media/v4l2-ctrls.h>
|
||||||
|
#include <media/v4l2-mem2mem.h>
|
||||||
|
|
||||||
|
#include "hevc_d.h"
|
||||||
|
#include "hevc_d_h265.h"
|
||||||
|
#include "hevc_d_video.h"
|
||||||
|
#include "hevc_d_hw.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default /dev/videoN node number.
|
||||||
|
* Deliberately avoid the very low numbers as these are often taken by webcams
|
||||||
|
* etc, and simple apps tend to only go for /dev/video0.
|
||||||
|
*/
|
||||||
|
static int video_nr = 19;
|
||||||
|
module_param(video_nr, int, 0644);
|
||||||
|
MODULE_PARM_DESC(video_nr, "decoder video device number");
|
||||||
|
|
||||||
|
static const struct hevc_d_control hevc_d_ctrls[] = {
|
||||||
|
{
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_SPS,
|
||||||
|
.ops = &hevc_d_hevc_sps_ctrl_ops,
|
||||||
|
},
|
||||||
|
.required = false,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_PPS,
|
||||||
|
.ops = &hevc_d_hevc_pps_ctrl_ops,
|
||||||
|
},
|
||||||
|
.required = false,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX,
|
||||||
|
},
|
||||||
|
.required = false,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS,
|
||||||
|
},
|
||||||
|
.required = true,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.name = "Slice param array",
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS,
|
||||||
|
.type = V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS,
|
||||||
|
.flags = V4L2_CTRL_FLAG_DYNAMIC_ARRAY,
|
||||||
|
.dims = { 0x1000 },
|
||||||
|
},
|
||||||
|
.required = true,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE,
|
||||||
|
.min = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
|
||||||
|
.max = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
|
||||||
|
.def = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
|
||||||
|
},
|
||||||
|
.required = false,
|
||||||
|
}, {
|
||||||
|
.cfg = {
|
||||||
|
.id = V4L2_CID_STATELESS_HEVC_START_CODE,
|
||||||
|
.min = V4L2_STATELESS_HEVC_START_CODE_NONE,
|
||||||
|
.max = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B,
|
||||||
|
.def = V4L2_STATELESS_HEVC_START_CODE_NONE,
|
||||||
|
},
|
||||||
|
.required = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HEVC_D_CTRLS_COUNT ARRAY_SIZE(hevc_d_ctrls)
|
||||||
|
|
||||||
|
struct v4l2_ctrl *hevc_d_find_ctrl(struct hevc_d_ctx *ctx, u32 id)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < HEVC_D_CTRLS_COUNT; i++)
|
||||||
|
if (ctx->ctrls[i]->id == id)
|
||||||
|
return ctx->ctrls[i];
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hevc_d_find_control_data(struct hevc_d_ctx *ctx, u32 id)
|
||||||
|
{
|
||||||
|
struct v4l2_ctrl *const ctrl = hevc_d_find_ctrl(ctx, id);
|
||||||
|
|
||||||
|
return !ctrl ? NULL : ctrl->p_cur.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_init_ctrls(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
|
||||||
|
{
|
||||||
|
struct v4l2_ctrl_handler *hdl = &ctx->hdl;
|
||||||
|
struct v4l2_ctrl *ctrl;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
v4l2_ctrl_handler_init(hdl, HEVC_D_CTRLS_COUNT);
|
||||||
|
if (hdl->error) {
|
||||||
|
v4l2_err(&dev->v4l2_dev,
|
||||||
|
"Failed to initialize control handler\n");
|
||||||
|
return hdl->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->ctrls = kzalloc(HEVC_D_CTRLS_COUNT * sizeof(ctrl), GFP_KERNEL);
|
||||||
|
if (!ctx->ctrls)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
for (i = 0; i < HEVC_D_CTRLS_COUNT; i++) {
|
||||||
|
ctrl = v4l2_ctrl_new_custom(hdl, &hevc_d_ctrls[i].cfg,
|
||||||
|
ctx);
|
||||||
|
if (hdl->error) {
|
||||||
|
v4l2_err(&dev->v4l2_dev,
|
||||||
|
"Failed to create new custom control id=%#x\n",
|
||||||
|
hevc_d_ctrls[i].cfg.id);
|
||||||
|
|
||||||
|
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 hevc_d_request_validate(struct media_request *req)
|
||||||
|
{
|
||||||
|
struct media_request_object *obj;
|
||||||
|
struct v4l2_ctrl_handler *parent_hdl, *hdl;
|
||||||
|
struct hevc_d_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 < HEVC_D_CTRLS_COUNT; i++) {
|
||||||
|
if (!hevc_d_ctrls[i].required)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ctrl_test =
|
||||||
|
v4l2_ctrl_request_hdl_ctrl_find(hdl,
|
||||||
|
hevc_d_ctrls[i].cfg.id);
|
||||||
|
if (!ctrl_test) {
|
||||||
|
v4l2_info(&ctx->dev->v4l2_dev,
|
||||||
|
"Missing required codec control %d: id=%#x\n",
|
||||||
|
i, hevc_d_ctrls[i].cfg.id);
|
||||||
|
v4l2_ctrl_request_hdl_put(hdl);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v4l2_ctrl_request_hdl_put(hdl);
|
||||||
|
|
||||||
|
return vb2_request_validate(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_open(struct file *file)
|
||||||
|
{
|
||||||
|
struct hevc_d_dev *dev = video_drvdata(file);
|
||||||
|
struct hevc_d_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);
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_init(&ctx->ctx_mutex);
|
||||||
|
|
||||||
|
v4l2_fh_init(&ctx->fh, video_devdata(file));
|
||||||
|
file->private_data = &ctx->fh;
|
||||||
|
ctx->dev = dev;
|
||||||
|
|
||||||
|
ret = hevc_d_init_ctrls(dev, ctx);
|
||||||
|
if (ret)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx,
|
||||||
|
&hevc_d_queue_init);
|
||||||
|
if (IS_ERR(ctx->fh.m2m_ctx)) {
|
||||||
|
ret = PTR_ERR(ctx->fh.m2m_ctx);
|
||||||
|
goto err_ctrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The only bit of format info that we can guess now is H265 src
|
||||||
|
* Everything else we need more info for
|
||||||
|
*/
|
||||||
|
hevc_d_prepare_src_format(&ctx->src_fmt);
|
||||||
|
|
||||||
|
v4l2_fh_add(&ctx->fh);
|
||||||
|
|
||||||
|
mutex_unlock(&dev->dev_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_ctrls:
|
||||||
|
v4l2_ctrl_handler_free(&ctx->hdl);
|
||||||
|
kfree(ctx->ctrls);
|
||||||
|
err_free:
|
||||||
|
mutex_destroy(&ctx->ctx_mutex);
|
||||||
|
kfree(ctx);
|
||||||
|
err_unlock:
|
||||||
|
mutex_unlock(&dev->dev_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_release(struct file *file)
|
||||||
|
{
|
||||||
|
struct hevc_d_dev *dev = video_drvdata(file);
|
||||||
|
struct hevc_d_ctx *ctx = container_of(file->private_data,
|
||||||
|
struct hevc_d_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);
|
||||||
|
mutex_destroy(&ctx->ctx_mutex);
|
||||||
|
|
||||||
|
kfree(ctx);
|
||||||
|
|
||||||
|
mutex_unlock(&dev->dev_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hevc_d_media_req_queue(struct media_request *req)
|
||||||
|
{
|
||||||
|
media_request_mark_manual_completion(req);
|
||||||
|
v4l2_m2m_request_queue(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct v4l2_file_operations hevc_d_fops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.open = hevc_d_open,
|
||||||
|
.release = hevc_d_release,
|
||||||
|
.poll = v4l2_m2m_fop_poll,
|
||||||
|
.unlocked_ioctl = video_ioctl2,
|
||||||
|
.mmap = v4l2_m2m_fop_mmap,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct video_device hevc_d_video_device = {
|
||||||
|
.name = HEVC_D_NAME,
|
||||||
|
.vfl_dir = VFL_DIR_M2M,
|
||||||
|
.fops = &hevc_d_fops,
|
||||||
|
.ioctl_ops = &hevc_d_ioctl_ops,
|
||||||
|
.minor = -1,
|
||||||
|
.release = video_device_release_empty,
|
||||||
|
.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct v4l2_m2m_ops hevc_d_m2m_ops = {
|
||||||
|
.device_run = hevc_d_device_run,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct media_device_ops hevc_d_m2m_media_ops = {
|
||||||
|
.req_validate = hevc_d_request_validate,
|
||||||
|
.req_queue = hevc_d_media_req_queue,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hevc_d_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct hevc_d_dev *dev;
|
||||||
|
struct video_device *vfd;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
dev->vfd = hevc_d_video_device;
|
||||||
|
dev->dev = &pdev->dev;
|
||||||
|
dev->pdev = pdev;
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
ret = hevc_d_hw_probe(dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Failed to probe hardware - %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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", hevc_d_video_device.name);
|
||||||
|
video_set_drvdata(vfd, dev);
|
||||||
|
|
||||||
|
ret = dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(36));
|
||||||
|
if (ret) {
|
||||||
|
v4l2_err(&dev->v4l2_dev,
|
||||||
|
"Failed dma_set_mask_and_coherent\n");
|
||||||
|
goto err_v4l2;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->m2m_dev = v4l2_m2m_init(&hevc_d_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, HEVC_D_NAME, sizeof(dev->mdev.model));
|
||||||
|
strscpy(dev->mdev.bus_info, "platform:" HEVC_D_NAME,
|
||||||
|
sizeof(dev->mdev.bus_info));
|
||||||
|
|
||||||
|
media_device_init(&dev->mdev);
|
||||||
|
dev->mdev.ops = &hevc_d_m2m_media_ops;
|
||||||
|
dev->v4l2_dev.mdev = &dev->mdev;
|
||||||
|
|
||||||
|
ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr);
|
||||||
|
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 void hevc_d_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct hevc_d_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);
|
||||||
|
|
||||||
|
hevc_d_hw_remove(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id hevc_d_dt_match[] = {
|
||||||
|
{ .compatible = "raspberrypi,hevc-dec", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, hevc_d_dt_match);
|
||||||
|
|
||||||
|
static struct platform_driver hevc_d_driver = {
|
||||||
|
.probe = hevc_d_probe,
|
||||||
|
.remove = hevc_d_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = HEVC_D_NAME,
|
||||||
|
.of_match_table = of_match_ptr(hevc_d_dt_match),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_platform_driver(hevc_d_driver);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("John Cox <john.cox@raspberrypi.com>");
|
||||||
|
MODULE_DESCRIPTION("Raspberry Pi HEVC V4L2 driver");
|
||||||
189
drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
Normal file
189
drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HEVC_D_H_
|
||||||
|
#define _HEVC_D_H_
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <media/v4l2-ctrls.h>
|
||||||
|
#include <media/v4l2-device.h>
|
||||||
|
#include <media/v4l2-mem2mem.h>
|
||||||
|
#include <media/videobuf2-v4l2.h>
|
||||||
|
#include <media/videobuf2-dma-contig.h>
|
||||||
|
|
||||||
|
#define HEVC_D_DEC_ENV_COUNT 6
|
||||||
|
#define HEVC_D_P1BUF_COUNT 3
|
||||||
|
#define HEVC_D_P2BUF_COUNT 3
|
||||||
|
|
||||||
|
#define HEVC_D_NAME "rpi-hevc-dec"
|
||||||
|
|
||||||
|
#define HEVC_D_CAPABILITY_UNTILED BIT(0)
|
||||||
|
#define HEVC_D_CAPABILITY_H265_DEC BIT(1)
|
||||||
|
|
||||||
|
#define HEVC_D_QUIRK_NO_DMA_OFFSET BIT(0)
|
||||||
|
|
||||||
|
enum hevc_d_irq_status {
|
||||||
|
HEVC_D_IRQ_NONE,
|
||||||
|
HEVC_D_IRQ_ERROR,
|
||||||
|
HEVC_D_IRQ_OK,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_control {
|
||||||
|
struct v4l2_ctrl_config cfg;
|
||||||
|
unsigned char required:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_h265_run {
|
||||||
|
u32 slice_ents;
|
||||||
|
const struct v4l2_ctrl_hevc_sps *sps;
|
||||||
|
const struct v4l2_ctrl_hevc_pps *pps;
|
||||||
|
const struct v4l2_ctrl_hevc_decode_params *dec;
|
||||||
|
const struct v4l2_ctrl_hevc_slice_params *slice_params;
|
||||||
|
const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_run {
|
||||||
|
struct vb2_v4l2_buffer *src;
|
||||||
|
struct vb2_v4l2_buffer *dst;
|
||||||
|
|
||||||
|
struct hevc_d_h265_run h265;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_buffer {
|
||||||
|
struct v4l2_m2m_buffer m2m_buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_dec_state;
|
||||||
|
struct hevc_d_dec_env;
|
||||||
|
|
||||||
|
struct hevc_d_gptr {
|
||||||
|
size_t size;
|
||||||
|
__u8 *ptr;
|
||||||
|
dma_addr_t addr;
|
||||||
|
unsigned long attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_dev;
|
||||||
|
typedef void (*hevc_d_irq_callback)(struct hevc_d_dev *dev, void *ctx);
|
||||||
|
|
||||||
|
struct hevc_d_q_aux;
|
||||||
|
#define HEVC_D_AUX_ENT_COUNT VB2_MAX_FRAME
|
||||||
|
|
||||||
|
struct hevc_d_ctx {
|
||||||
|
struct v4l2_fh fh;
|
||||||
|
struct hevc_d_dev *dev;
|
||||||
|
|
||||||
|
struct v4l2_pix_format_mplane src_fmt;
|
||||||
|
struct v4l2_pix_format_mplane dst_fmt;
|
||||||
|
int dst_fmt_set;
|
||||||
|
|
||||||
|
int src_stream_on;
|
||||||
|
int dst_stream_on;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fatal_err is set if an error has occurred s.t. decode cannot
|
||||||
|
* continue (such as running out of CMA)
|
||||||
|
*/
|
||||||
|
int fatal_err;
|
||||||
|
|
||||||
|
/* Lock for queue operations */
|
||||||
|
struct mutex ctx_mutex;
|
||||||
|
|
||||||
|
struct v4l2_ctrl_handler hdl;
|
||||||
|
struct v4l2_ctrl **ctrls;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* state contains stuff that is only needed in phase0
|
||||||
|
* it could be held in dec_env but that would be wasteful
|
||||||
|
*/
|
||||||
|
struct hevc_d_dec_state *state;
|
||||||
|
struct hevc_d_dec_env *dec0;
|
||||||
|
|
||||||
|
/* Spinlock protecting dec_free */
|
||||||
|
spinlock_t dec_lock;
|
||||||
|
struct hevc_d_dec_env *dec_free;
|
||||||
|
|
||||||
|
struct hevc_d_dec_env *dec_pool;
|
||||||
|
|
||||||
|
unsigned int p1idx;
|
||||||
|
atomic_t p1out;
|
||||||
|
|
||||||
|
unsigned int p2idx;
|
||||||
|
struct hevc_d_gptr pu_bufs[HEVC_D_P2BUF_COUNT];
|
||||||
|
struct hevc_d_gptr coeff_bufs[HEVC_D_P2BUF_COUNT];
|
||||||
|
|
||||||
|
/* Spinlock protecting aux_free */
|
||||||
|
spinlock_t aux_lock;
|
||||||
|
struct hevc_d_q_aux *aux_free;
|
||||||
|
|
||||||
|
struct hevc_d_q_aux *aux_ents[HEVC_D_AUX_ENT_COUNT];
|
||||||
|
|
||||||
|
unsigned int colmv_stride;
|
||||||
|
unsigned int colmv_picsize;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_variant {
|
||||||
|
unsigned int capabilities;
|
||||||
|
unsigned int quirks;
|
||||||
|
unsigned int mod_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_hw_irq_ent;
|
||||||
|
|
||||||
|
#define HEVC_D_ICTL_ENABLE_UNLIMITED (-1)
|
||||||
|
|
||||||
|
struct hevc_d_hw_irq_ctrl {
|
||||||
|
/* Spinlock protecting claim and tail */
|
||||||
|
spinlock_t lock;
|
||||||
|
struct hevc_d_hw_irq_ent *claim;
|
||||||
|
struct hevc_d_hw_irq_ent *tail;
|
||||||
|
|
||||||
|
/* Ent for pending irq - also prevents sched */
|
||||||
|
struct hevc_d_hw_irq_ent *irq;
|
||||||
|
/* Non-zero => do not start a new job - outer layer sched pending */
|
||||||
|
int no_sched;
|
||||||
|
/* Enable count. -1 always OK, 0 do not sched, +ve shed & count down */
|
||||||
|
int enable;
|
||||||
|
/* Thread CB requested */
|
||||||
|
bool thread_reqed;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hevc_d_dev {
|
||||||
|
struct v4l2_device v4l2_dev;
|
||||||
|
struct video_device vfd;
|
||||||
|
struct media_device mdev;
|
||||||
|
struct media_pad pad[2];
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct device *dev;
|
||||||
|
struct v4l2_m2m_dev *m2m_dev;
|
||||||
|
|
||||||
|
/* Device file mutex */
|
||||||
|
struct mutex dev_mutex;
|
||||||
|
|
||||||
|
void __iomem *base_irq;
|
||||||
|
void __iomem *base_h265;
|
||||||
|
|
||||||
|
struct clk *clock;
|
||||||
|
unsigned long max_clock_rate;
|
||||||
|
|
||||||
|
int cache_align;
|
||||||
|
|
||||||
|
struct hevc_d_hw_irq_ctrl ic_active1;
|
||||||
|
struct hevc_d_hw_irq_ctrl ic_active2;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct v4l2_ctrl *hevc_d_find_ctrl(struct hevc_d_ctx *ctx, u32 id);
|
||||||
|
void *hevc_d_find_control_data(struct hevc_d_ctx *ctx, u32 id);
|
||||||
|
|
||||||
|
#endif
|
||||||
2585
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
Normal file
2585
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
Normal file
File diff suppressed because it is too large
Load Diff
23
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h
Normal file
23
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HEVC_D_H265_H_
|
||||||
|
#define _HEVC_D_H265_H_
|
||||||
|
#include "hevc_d.h"
|
||||||
|
|
||||||
|
extern const struct v4l2_ctrl_ops hevc_d_hevc_sps_ctrl_ops;
|
||||||
|
extern const struct v4l2_ctrl_ops hevc_d_hevc_pps_ctrl_ops;
|
||||||
|
|
||||||
|
void hevc_d_h265_setup(struct hevc_d_ctx *ctx, struct hevc_d_run *run);
|
||||||
|
int hevc_d_h265_start(struct hevc_d_ctx *ctx);
|
||||||
|
void hevc_d_h265_stop(struct hevc_d_ctx *ctx);
|
||||||
|
void hevc_d_h265_trigger(struct hevc_d_ctx *ctx);
|
||||||
|
|
||||||
|
void hevc_d_device_run(void *priv);
|
||||||
|
|
||||||
|
#endif
|
||||||
376
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
Normal file
376
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/component.h>
|
||||||
|
#include <linux/dma-mapping.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/of_reserved_mem.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/reset.h>
|
||||||
|
|
||||||
|
#include <media/videobuf2-core.h>
|
||||||
|
#include <media/v4l2-mem2mem.h>
|
||||||
|
|
||||||
|
#include <soc/bcm2835/raspberrypi-firmware.h>
|
||||||
|
|
||||||
|
#include "hevc_d.h"
|
||||||
|
#include "hevc_d_hw.h"
|
||||||
|
|
||||||
|
static void pre_irq(struct hevc_d_dev *dev, struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback cb, void *v,
|
||||||
|
struct hevc_d_hw_irq_ctrl *ictl)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (ictl->irq) {
|
||||||
|
v4l2_err(&dev->v4l2_dev, "Attempt to claim IRQ when already claimed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ient->cb = cb;
|
||||||
|
ient->v = v;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
ictl->irq = ient;
|
||||||
|
ictl->no_sched++;
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should be called from inside ictl->lock */
|
||||||
|
static inline bool sched_enabled(const struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
return ictl->no_sched <= 0 && ictl->enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should be called from inside ictl->lock & after checking sched_enabled() */
|
||||||
|
static inline void set_claimed(struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
if (ictl->enable > 0)
|
||||||
|
--ictl->enable;
|
||||||
|
ictl->no_sched = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should be called from inside ictl->lock */
|
||||||
|
static struct hevc_d_hw_irq_ent *get_sched(struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
struct hevc_d_hw_irq_ent *ient;
|
||||||
|
|
||||||
|
if (!sched_enabled(ictl))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
ient = ictl->claim;
|
||||||
|
if (!ient)
|
||||||
|
return NULL;
|
||||||
|
ictl->claim = ient->next;
|
||||||
|
|
||||||
|
set_claimed(ictl);
|
||||||
|
return ient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run a callback & check to see if there is anything else to run */
|
||||||
|
static void sched_cb(struct hevc_d_dev * const dev,
|
||||||
|
struct hevc_d_hw_irq_ctrl * const ictl,
|
||||||
|
struct hevc_d_hw_irq_ent *ient)
|
||||||
|
{
|
||||||
|
while (ient) {
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
ient->cb(dev, ient->v);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Always dec no_sched after cb exec - must have been set
|
||||||
|
* on entry to cb
|
||||||
|
*/
|
||||||
|
--ictl->no_sched;
|
||||||
|
ient = get_sched(ictl);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should only ever be called from its own IRQ cb so no lock required */
|
||||||
|
static void pre_thread(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback cb, void *v,
|
||||||
|
struct hevc_d_hw_irq_ctrl *ictl)
|
||||||
|
{
|
||||||
|
ient->cb = cb;
|
||||||
|
ient->v = v;
|
||||||
|
ictl->irq = ient;
|
||||||
|
ictl->thread_reqed = true;
|
||||||
|
ictl->no_sched++; /* This is unwound in do_thread */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called in irq context */
|
||||||
|
static void do_irq(struct hevc_d_dev * const dev,
|
||||||
|
struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
struct hevc_d_hw_irq_ent *ient;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
ient = ictl->irq;
|
||||||
|
ictl->irq = NULL;
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
|
||||||
|
sched_cb(dev, ictl, ient);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_claim(struct hevc_d_dev * const dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
const hevc_d_irq_callback cb, void * const v,
|
||||||
|
struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
ient->next = NULL;
|
||||||
|
ient->cb = cb;
|
||||||
|
ient->v = v;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
|
||||||
|
if (ictl->claim) {
|
||||||
|
/* If we have a Q then add to end */
|
||||||
|
ictl->tail->next = ient;
|
||||||
|
ictl->tail = ient;
|
||||||
|
ient = NULL;
|
||||||
|
} else if (!sched_enabled(ictl)) {
|
||||||
|
/* Empty Q but other activity in progress so Q */
|
||||||
|
ictl->claim = ient;
|
||||||
|
ictl->tail = ient;
|
||||||
|
ient = NULL;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Nothing else going on - schedule immediately and
|
||||||
|
* prevent anything else scheduling claims
|
||||||
|
*/
|
||||||
|
set_claimed(ictl);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
|
||||||
|
sched_cb(dev, ictl, ient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable n claims.
|
||||||
|
* n < 0 set to unlimited (default on init)
|
||||||
|
* n = 0 if previously unlimited then disable otherwise nop
|
||||||
|
* n > 0 if previously unlimited then set to n enables
|
||||||
|
* otherwise add n enables
|
||||||
|
* The enable count is automatically decremented every time a claim is run
|
||||||
|
*/
|
||||||
|
static void do_enable_claim(struct hevc_d_dev * const dev,
|
||||||
|
int n,
|
||||||
|
struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
struct hevc_d_hw_irq_ent *ient;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
ictl->enable = n < 0 ? -1 : ictl->enable <= 0 ? n : ictl->enable + n;
|
||||||
|
ient = get_sched(ictl);
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
|
||||||
|
sched_cb(dev, ictl, ient);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ictl_init(struct hevc_d_hw_irq_ctrl * const ictl, int enables)
|
||||||
|
{
|
||||||
|
spin_lock_init(&ictl->lock);
|
||||||
|
ictl->claim = NULL;
|
||||||
|
ictl->tail = NULL;
|
||||||
|
ictl->irq = NULL;
|
||||||
|
ictl->no_sched = 0;
|
||||||
|
ictl->enable = enables;
|
||||||
|
ictl->thread_reqed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ictl_uninit(struct hevc_d_hw_irq_ctrl * const ictl)
|
||||||
|
{
|
||||||
|
/* Nothing to do */
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t hevc_d_irq_irq(int irq, void *data)
|
||||||
|
{
|
||||||
|
struct hevc_d_dev * const dev = data;
|
||||||
|
__u32 ictrl;
|
||||||
|
|
||||||
|
ictrl = irq_read(dev, ARG_IC_ICTRL);
|
||||||
|
if (!(ictrl & ARG_IC_ICTRL_ALL_IRQ_MASK)) {
|
||||||
|
v4l2_warn(&dev->v4l2_dev, "IRQ but no IRQ bits set\n");
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cancel any/all irqs */
|
||||||
|
irq_write(dev, ARG_IC_ICTRL, ictrl & ~ARG_IC_ICTRL_SET_ZERO_MASK);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Service Active2 before Active1 so Phase 1 can transition to Phase 2
|
||||||
|
* without delay
|
||||||
|
*/
|
||||||
|
if (ictrl & ARG_IC_ICTRL_ACTIVE2_INT_SET)
|
||||||
|
do_irq(dev, &dev->ic_active2);
|
||||||
|
if (ictrl & ARG_IC_ICTRL_ACTIVE1_INT_SET)
|
||||||
|
do_irq(dev, &dev->ic_active1);
|
||||||
|
|
||||||
|
return dev->ic_active1.thread_reqed || dev->ic_active2.thread_reqed ?
|
||||||
|
IRQ_WAKE_THREAD : IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_thread(struct hevc_d_dev * const dev,
|
||||||
|
struct hevc_d_hw_irq_ctrl *const ictl)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
struct hevc_d_hw_irq_ent *ient = NULL;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ictl->lock, flags);
|
||||||
|
|
||||||
|
if (ictl->thread_reqed) {
|
||||||
|
ient = ictl->irq;
|
||||||
|
ictl->thread_reqed = false;
|
||||||
|
ictl->irq = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&ictl->lock, flags);
|
||||||
|
|
||||||
|
sched_cb(dev, ictl, ient);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t hevc_d_irq_thread(int irq, void *data)
|
||||||
|
{
|
||||||
|
struct hevc_d_dev * const dev = data;
|
||||||
|
|
||||||
|
do_thread(dev, &dev->ic_active1);
|
||||||
|
do_thread(dev, &dev->ic_active2);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* May only be called from Active1 CB
|
||||||
|
* IRQs should not be expected until execution continues in the cb
|
||||||
|
*/
|
||||||
|
void hevc_d_hw_irq_active1_thread(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback thread_cb, void *ctx)
|
||||||
|
{
|
||||||
|
pre_thread(dev, ient, thread_cb, ctx, &dev->ic_active1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_irq_active1_enable_claim(struct hevc_d_dev *dev,
|
||||||
|
int n)
|
||||||
|
{
|
||||||
|
do_enable_claim(dev, n, &dev->ic_active1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_irq_active1_claim(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback ready_cb, void *ctx)
|
||||||
|
{
|
||||||
|
do_claim(dev, ient, ready_cb, ctx, &dev->ic_active1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_irq_active1_irq(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback irq_cb, void *ctx)
|
||||||
|
{
|
||||||
|
pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_irq_active2_claim(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback ready_cb, void *ctx)
|
||||||
|
{
|
||||||
|
do_claim(dev, ient, ready_cb, ctx, &dev->ic_active2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback irq_cb, void *ctx)
|
||||||
|
{
|
||||||
|
pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hevc_d_hw_probe(struct hevc_d_dev *dev)
|
||||||
|
{
|
||||||
|
struct rpi_firmware *firmware;
|
||||||
|
struct device_node *node;
|
||||||
|
__u32 irq_stat;
|
||||||
|
int irq_dec;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ictl_init(&dev->ic_active1, HEVC_D_P2BUF_COUNT);
|
||||||
|
ictl_init(&dev->ic_active2, HEVC_D_ICTL_ENABLE_UNLIMITED);
|
||||||
|
|
||||||
|
dev->base_irq = devm_platform_ioremap_resource_byname(dev->pdev, "intc");
|
||||||
|
if (IS_ERR(dev->base_irq))
|
||||||
|
return PTR_ERR(dev->base_irq);
|
||||||
|
|
||||||
|
dev->base_h265 = devm_platform_ioremap_resource_byname(dev->pdev, "hevc");
|
||||||
|
if (IS_ERR(dev->base_h265))
|
||||||
|
return PTR_ERR(dev->base_h265);
|
||||||
|
|
||||||
|
dev->clock = devm_clk_get(&dev->pdev->dev, NULL);
|
||||||
|
if (IS_ERR(dev->clock))
|
||||||
|
return PTR_ERR(dev->clock);
|
||||||
|
|
||||||
|
node = rpi_firmware_find_node();
|
||||||
|
if (!node)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
firmware = rpi_firmware_get(node);
|
||||||
|
of_node_put(node);
|
||||||
|
if (!firmware)
|
||||||
|
return -EPROBE_DEFER;
|
||||||
|
|
||||||
|
dev->max_clock_rate = rpi_firmware_clk_get_max_rate(firmware,
|
||||||
|
RPI_FIRMWARE_HEVC_CLK_ID);
|
||||||
|
rpi_firmware_put(firmware);
|
||||||
|
|
||||||
|
dev->cache_align = dma_get_cache_alignment();
|
||||||
|
|
||||||
|
/* Disable IRQs & reset anything pending */
|
||||||
|
irq_write(dev, 0,
|
||||||
|
ARG_IC_ICTRL_ACTIVE1_EN_SET | ARG_IC_ICTRL_ACTIVE2_EN_SET);
|
||||||
|
irq_stat = irq_read(dev, 0);
|
||||||
|
irq_write(dev, 0, irq_stat);
|
||||||
|
|
||||||
|
irq_dec = platform_get_irq(dev->pdev, 0);
|
||||||
|
if (irq_dec <= 0)
|
||||||
|
return irq_dec;
|
||||||
|
ret = devm_request_threaded_irq(dev->dev, irq_dec,
|
||||||
|
hevc_d_irq_irq,
|
||||||
|
hevc_d_irq_thread,
|
||||||
|
0, dev_name(dev->dev), dev);
|
||||||
|
if (ret)
|
||||||
|
dev_err(dev->dev, "Failed to request IRQ - %d\n", ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_hw_remove(struct hevc_d_dev *dev)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* IRQ auto freed on unload so no need to do it here
|
||||||
|
* ioremap auto freed on unload
|
||||||
|
*/
|
||||||
|
ictl_uninit(&dev->ic_active1);
|
||||||
|
ictl_uninit(&dev->ic_active2);
|
||||||
|
}
|
||||||
|
|
||||||
303
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
Normal file
303
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HEVC_D_HW_H_
|
||||||
|
#define _HEVC_D_HW_H_
|
||||||
|
|
||||||
|
struct hevc_d_hw_irq_ent {
|
||||||
|
struct hevc_d_hw_irq_ent *next;
|
||||||
|
hevc_d_irq_callback cb;
|
||||||
|
void *v;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Phase 1 Register offsets */
|
||||||
|
|
||||||
|
#define RPI_SPS0 0
|
||||||
|
#define RPI_SPS1 4
|
||||||
|
#define RPI_PPS 8
|
||||||
|
#define RPI_SLICE 12
|
||||||
|
#define RPI_TILESTART 16
|
||||||
|
#define RPI_TILEEND 20
|
||||||
|
#define RPI_SLICESTART 24
|
||||||
|
#define RPI_MODE 28
|
||||||
|
#define RPI_LEFT0 32
|
||||||
|
#define RPI_LEFT1 36
|
||||||
|
#define RPI_LEFT2 40
|
||||||
|
#define RPI_LEFT3 44
|
||||||
|
#define RPI_QP 48
|
||||||
|
#define RPI_CONTROL 52
|
||||||
|
#define RPI_STATUS 56
|
||||||
|
#define RPI_VERSION 60
|
||||||
|
#define RPI_BFBASE 64
|
||||||
|
#define RPI_BFNUM 68
|
||||||
|
#define RPI_BFCONTROL 72
|
||||||
|
#define RPI_BFSTATUS 76
|
||||||
|
#define RPI_PUWBASE 80
|
||||||
|
#define RPI_PUWSTRIDE 84
|
||||||
|
#define RPI_COEFFWBASE 88
|
||||||
|
#define RPI_COEFFWSTRIDE 92
|
||||||
|
#define RPI_SLICECMDS 96
|
||||||
|
#define RPI_BEGINTILEEND 100
|
||||||
|
#define RPI_TRANSFER 104
|
||||||
|
#define RPI_CFBASE 108
|
||||||
|
#define RPI_CFNUM 112
|
||||||
|
#define RPI_CFSTATUS 116
|
||||||
|
|
||||||
|
/* Phase 2 Register offsets */
|
||||||
|
|
||||||
|
#define RPI_PURBASE 0x8000
|
||||||
|
#define RPI_PURSTRIDE 0x8004
|
||||||
|
#define RPI_COEFFRBASE 0x8008
|
||||||
|
#define RPI_COEFFRSTRIDE 0x800C
|
||||||
|
#define RPI_NUMROWS 0x8010
|
||||||
|
#define RPI_CONFIG2 0x8014
|
||||||
|
#define RPI_OUTYBASE 0x8018
|
||||||
|
#define RPI_OUTYSTRIDE 0x801C
|
||||||
|
#define RPI_OUTCBASE 0x8020
|
||||||
|
#define RPI_OUTCSTRIDE 0x8024
|
||||||
|
#define RPI_STATUS2 0x8028
|
||||||
|
#define RPI_FRAMESIZE 0x802C
|
||||||
|
#define RPI_MVBASE 0x8030
|
||||||
|
#define RPI_MVSTRIDE 0x8034
|
||||||
|
#define RPI_COLBASE 0x8038
|
||||||
|
#define RPI_COLSTRIDE 0x803C
|
||||||
|
#define RPI_CURRPOC 0x8040
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a general register value
|
||||||
|
* Order is unimportant
|
||||||
|
*/
|
||||||
|
static inline void apb_write(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset, const u32 val)
|
||||||
|
{
|
||||||
|
writel_relaxed(val, dev->base_h265 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the final register value that actually starts the phase */
|
||||||
|
static inline void apb_write_final(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset, const u32 val)
|
||||||
|
{
|
||||||
|
writel(val, dev->base_h265 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 apb_read(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset)
|
||||||
|
{
|
||||||
|
return readl(dev->base_h265 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void irq_write(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset, const u32 val)
|
||||||
|
{
|
||||||
|
writel(val, dev->base_irq + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 irq_read(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset)
|
||||||
|
{
|
||||||
|
return readl(dev->base_irq + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void apb_write_vc_addr(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset,
|
||||||
|
const dma_addr_t a)
|
||||||
|
{
|
||||||
|
apb_write(dev, offset, (u32)(a >> 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void apb_write_vc_addr_final(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset,
|
||||||
|
const dma_addr_t a)
|
||||||
|
{
|
||||||
|
apb_write_final(dev, offset, (u32)(a >> 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void apb_write_vc_len(const struct hevc_d_dev * const dev,
|
||||||
|
const unsigned int offset,
|
||||||
|
const unsigned int x)
|
||||||
|
{
|
||||||
|
apb_write(dev, offset, (x + 63) >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *ARG_IC_ICTRL - Interrupt control for ARGON Core*
|
||||||
|
* Offset (byte space) = 40'h2b10000
|
||||||
|
* Physical Address (byte space) = 40'h7eb10000
|
||||||
|
* Verilog Macro Address = `ARG_IC_REG_START + `ARGON_INTCTRL_ICTRL
|
||||||
|
* Reset Value = 32'b100x100x_100xxxxx_xxxxxxx0_x100x100
|
||||||
|
* Access = RW (32-bit only)
|
||||||
|
* Interrupt control logic for ARGON Core.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL 0
|
||||||
|
|
||||||
|
/* acc=LWC ACTIVE1_INT FIELD ACCESS: LWC
|
||||||
|
*
|
||||||
|
* Interrupt 1
|
||||||
|
* This is set and held when an hevc_active1 interrupt edge is detected
|
||||||
|
* The polarity of the edge is set by the ACTIVE1_EDGE field
|
||||||
|
* Write a 1 to this bit to clear down the latched interrupt
|
||||||
|
* The latched interrupt is only enabled out onto the interrupt line if
|
||||||
|
* ACTIVE1_EN is set
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE1_INT_SET BIT(0)
|
||||||
|
|
||||||
|
/* ACTIVE1_EDGE Sets the polarity of the interrupt edge detection logic
|
||||||
|
* This logic detects edges of the hevc_active1 line from the argon core
|
||||||
|
* 0 = negedge, 1 = posedge
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE1_EDGE_SET BIT(1)
|
||||||
|
|
||||||
|
/* ACTIVE1_EN Enables ACTIVE1_INT out onto the argon interrupt line.
|
||||||
|
* If this isn't set, the interrupt logic will work but no interrupt will be
|
||||||
|
* set to the interrupt controller
|
||||||
|
* Reset value is *1* decimal.
|
||||||
|
*
|
||||||
|
* [JC] The above appears to be a lie - if unset then b0 is never set
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE1_EN_SET BIT(2)
|
||||||
|
|
||||||
|
/* acc=RO ACTIVE1_STATUS FIELD ACCESS: RO
|
||||||
|
*
|
||||||
|
* The current status of the hevc_active1 signal
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE1_STATUS_SET BIT(3)
|
||||||
|
|
||||||
|
/* acc=LWC ACTIVE2_INT FIELD ACCESS: LWC
|
||||||
|
*
|
||||||
|
* Interrupt 2
|
||||||
|
* This is set and held when an hevc_active2 interrupt edge is detected
|
||||||
|
* The polarity of the edge is set by the ACTIVE2_EDGE field
|
||||||
|
* Write a 1 to this bit to clear down the latched interrupt
|
||||||
|
* The latched interrupt is only enabled out onto the interrupt line if
|
||||||
|
* ACTIVE2_EN is set
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE2_INT_SET BIT(4)
|
||||||
|
|
||||||
|
/* ACTIVE2_EDGE Sets the polarity of the interrupt edge detection logic
|
||||||
|
* This logic detects edges of the hevc_active2 line from the argon core
|
||||||
|
* 0 = negedge, 1 = posedge
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE2_EDGE_SET BIT(5)
|
||||||
|
|
||||||
|
/* ACTIVE2_EN Enables ACTIVE2_INT out onto the argon interrupt line.
|
||||||
|
* If this isn't set, the interrupt logic will work but no interrupt will be
|
||||||
|
* set to the interrupt controller
|
||||||
|
* Reset value is *1* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE2_EN_SET BIT(6)
|
||||||
|
|
||||||
|
/* acc=RO ACTIVE2_STATUS FIELD ACCESS: RO
|
||||||
|
*
|
||||||
|
* The current status of the hevc_active2 signal
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_ACTIVE2_STATUS_SET BIT(7)
|
||||||
|
|
||||||
|
/* TEST_INT Forces the argon int high for test purposes.
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_TEST_INT BIT(8)
|
||||||
|
#define ARG_IC_ICTRL_SPARE BIT(9)
|
||||||
|
|
||||||
|
/* acc=RO VP9_INTERRUPT_STATUS FIELD ACCESS: RO
|
||||||
|
*
|
||||||
|
* The current status of the vp9_interrupt signal
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_VP9_INTERRUPT_STATUS BIT(10)
|
||||||
|
|
||||||
|
/* AIO_INT_ENABLE 1 = Or the AIO int in with the Argon int so the VPU can see
|
||||||
|
* it
|
||||||
|
* 0 = the AIO int is masked. (It should still be connected to the GIC though).
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_AIO_INT_ENABLE BIT(20)
|
||||||
|
#define ARG_IC_ICTRL_H264_ACTIVE_INT BIT(21)
|
||||||
|
#define ARG_IC_ICTRL_H264_ACTIVE_EDGE BIT(22)
|
||||||
|
#define ARG_IC_ICTRL_H264_ACTIVE_EN BIT(23)
|
||||||
|
#define ARG_IC_ICTRL_H264_ACTIVE_STATUS BIT(24)
|
||||||
|
#define ARG_IC_ICTRL_H264_INTERRUPT_INT BIT(25)
|
||||||
|
#define ARG_IC_ICTRL_H264_INTERRUPT_EDGE BIT(26)
|
||||||
|
#define ARG_IC_ICTRL_H264_INTERRUPT_EN BIT(27)
|
||||||
|
|
||||||
|
/* acc=RO H264_INTERRUPT_STATUS FIELD ACCESS: RO
|
||||||
|
*
|
||||||
|
* The current status of the h264_interrupt signal
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_H264_INTERRUPT_STATUS BIT(28)
|
||||||
|
|
||||||
|
/* acc=LWC VP9_INTERRUPT_INT FIELD ACCESS: LWC
|
||||||
|
*
|
||||||
|
* Interrupt 1
|
||||||
|
* This is set and held when an vp9_interrupt interrupt edge is detected
|
||||||
|
* The polarity of the edge is set by the VP9_INTERRUPT_EDGE field
|
||||||
|
* Write a 1 to this bit to clear down the latched interrupt
|
||||||
|
* The latched interrupt is only enabled out onto the interrupt line if
|
||||||
|
* VP9_INTERRUPT_EN is set
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_VP9_INTERRUPT_INT BIT(29)
|
||||||
|
|
||||||
|
/* VP9_INTERRUPT_EDGE Sets the polarity of the interrupt edge detection logic
|
||||||
|
* This logic detects edges of the vp9_interrupt line from the argon h264 core
|
||||||
|
* 0 = negedge, 1 = posedge
|
||||||
|
* Reset value is *0* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_VP9_INTERRUPT_EDGE BIT(30)
|
||||||
|
|
||||||
|
/* VP9_INTERRUPT_EN Enables VP9_INTERRUPT_INT out onto the argon interrupt line.
|
||||||
|
* If this isn't set, the interrupt logic will work but no interrupt will be
|
||||||
|
* set to the interrupt controller
|
||||||
|
* Reset value is *1* decimal.
|
||||||
|
*/
|
||||||
|
#define ARG_IC_ICTRL_VP9_INTERRUPT_EN BIT(31)
|
||||||
|
|
||||||
|
/* Bits 19:12, 11 reserved - read ?, write 0 */
|
||||||
|
#define ARG_IC_ICTRL_SET_ZERO_MASK ((0xff << 12) | BIT(11))
|
||||||
|
|
||||||
|
/* All IRQ bits */
|
||||||
|
#define ARG_IC_ICTRL_ALL_IRQ_MASK (\
|
||||||
|
ARG_IC_ICTRL_VP9_INTERRUPT_INT |\
|
||||||
|
ARG_IC_ICTRL_H264_INTERRUPT_INT |\
|
||||||
|
ARG_IC_ICTRL_ACTIVE1_INT_SET |\
|
||||||
|
ARG_IC_ICTRL_ACTIVE2_INT_SET)
|
||||||
|
|
||||||
|
/* Regulate claim Q */
|
||||||
|
void hevc_d_hw_irq_active1_enable_claim(struct hevc_d_dev *dev,
|
||||||
|
int n);
|
||||||
|
/* Auto release once all CBs called */
|
||||||
|
void hevc_d_hw_irq_active1_claim(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback ready_cb, void *ctx);
|
||||||
|
/* May only be called in claim cb */
|
||||||
|
void hevc_d_hw_irq_active1_irq(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback irq_cb, void *ctx);
|
||||||
|
/* May only be called in irq cb */
|
||||||
|
void hevc_d_hw_irq_active1_thread(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback thread_cb, void *ctx);
|
||||||
|
|
||||||
|
/* Auto release once all CBs called */
|
||||||
|
void hevc_d_hw_irq_active2_claim(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback ready_cb, void *ctx);
|
||||||
|
/* May only be called in claim cb */
|
||||||
|
void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev,
|
||||||
|
struct hevc_d_hw_irq_ent *ient,
|
||||||
|
hevc_d_irq_callback irq_cb, void *ctx);
|
||||||
|
|
||||||
|
int hevc_d_hw_probe(struct hevc_d_dev *dev);
|
||||||
|
void hevc_d_hw_remove(struct hevc_d_dev *dev);
|
||||||
|
|
||||||
|
#endif
|
||||||
749
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
Normal file
749
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
Normal file
@@ -0,0 +1,749 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <media/videobuf2-dma-contig.h>
|
||||||
|
#include <media/v4l2-device.h>
|
||||||
|
#include <media/v4l2-ioctl.h>
|
||||||
|
#include <media/v4l2-event.h>
|
||||||
|
#include <media/v4l2-mem2mem.h>
|
||||||
|
|
||||||
|
#include "hevc_d.h"
|
||||||
|
#include "hevc_d_h265.h"
|
||||||
|
#include "hevc_d_hw.h"
|
||||||
|
#include "hevc_d_video.h"
|
||||||
|
|
||||||
|
#define HEVC_D_DECODE_SRC BIT(0)
|
||||||
|
#define HEVC_D_DECODE_DST BIT(1)
|
||||||
|
|
||||||
|
#define HEVC_D_MIN_WIDTH 16U
|
||||||
|
#define HEVC_D_MIN_HEIGHT 16U
|
||||||
|
#define HEVC_D_DEFAULT_WIDTH 1920U
|
||||||
|
#define HEVC_D_DEFAULT_HEIGHT 1088U
|
||||||
|
#define HEVC_D_MAX_WIDTH 4096U
|
||||||
|
#define HEVC_D_MAX_HEIGHT 4096U
|
||||||
|
|
||||||
|
static inline struct hevc_d_ctx *hevc_d_file2ctx(struct file *file)
|
||||||
|
{
|
||||||
|
return container_of(file->private_data, struct hevc_d_ctx, fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* constrain x to y,y*2 */
|
||||||
|
static inline unsigned int constrain2x(unsigned int x, unsigned int y)
|
||||||
|
{
|
||||||
|
return (x < y) ?
|
||||||
|
y :
|
||||||
|
(x > y * 2) ? y : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hevc_d_round_up_size(const size_t x)
|
||||||
|
{
|
||||||
|
/* Admit no size < 256 */
|
||||||
|
const unsigned int n = x < 256 ? 8 : ilog2(x);
|
||||||
|
|
||||||
|
return x >= (3 << n) ? 4 << n : (3 << n);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8)
|
||||||
|
{
|
||||||
|
const size_t wxh = w * h;
|
||||||
|
size_t bits_alloc;
|
||||||
|
|
||||||
|
/* Annex A gives a min compression of 2 @ lvl 3.1
|
||||||
|
* (wxh <= 983040) and min 4 thereafter but avoid
|
||||||
|
* the odity of 983041 having a lower limit than
|
||||||
|
* 983040.
|
||||||
|
* Multiply by 3/2 for 4:2:0
|
||||||
|
*/
|
||||||
|
bits_alloc = wxh < 983040 ? wxh * 3 / 4 :
|
||||||
|
wxh < 983040 * 2 ? 983040 * 3 / 4 :
|
||||||
|
wxh * 3 / 8;
|
||||||
|
/* Allow for bit depth */
|
||||||
|
bits_alloc += (bits_alloc * bits_minus8) / 8;
|
||||||
|
return hevc_d_round_up_size(bits_alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
u32 w;
|
||||||
|
u32 h;
|
||||||
|
|
||||||
|
w = pix_fmt->width;
|
||||||
|
h = pix_fmt->height;
|
||||||
|
if (!w || !h) {
|
||||||
|
w = HEVC_D_DEFAULT_WIDTH;
|
||||||
|
h = HEVC_D_DEFAULT_HEIGHT;
|
||||||
|
}
|
||||||
|
if (w > HEVC_D_MAX_WIDTH)
|
||||||
|
w = HEVC_D_MAX_WIDTH;
|
||||||
|
if (h > HEVC_D_MAX_HEIGHT)
|
||||||
|
h = HEVC_D_MAX_HEIGHT;
|
||||||
|
|
||||||
|
if (!pix_fmt->plane_fmt[0].sizeimage ||
|
||||||
|
pix_fmt->plane_fmt[0].sizeimage > SZ_32M) {
|
||||||
|
/* Unspecified or way too big - pick max for size */
|
||||||
|
size = hevc_d_bit_buf_size(w, h, 2);
|
||||||
|
}
|
||||||
|
/* Set a minimum */
|
||||||
|
size = max_t(u32, SZ_4K, pix_fmt->plane_fmt[0].sizeimage);
|
||||||
|
|
||||||
|
pix_fmt->pixelformat = V4L2_PIX_FMT_HEVC_SLICE;
|
||||||
|
pix_fmt->width = w;
|
||||||
|
pix_fmt->height = h;
|
||||||
|
pix_fmt->num_planes = 1;
|
||||||
|
pix_fmt->field = V4L2_FIELD_NONE;
|
||||||
|
/* Zero bytes per line for encoded source. */
|
||||||
|
pix_fmt->plane_fmt[0].bytesperline = 0;
|
||||||
|
pix_fmt->plane_fmt[0].sizeimage = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Take any pix_format and make it valid */
|
||||||
|
static void hevc_d_prepare_dst_format(struct v4l2_pix_format_mplane *pix_fmt)
|
||||||
|
{
|
||||||
|
unsigned int width = pix_fmt->width;
|
||||||
|
unsigned int height = pix_fmt->height;
|
||||||
|
unsigned int sizeimage = pix_fmt->plane_fmt[0].sizeimage;
|
||||||
|
unsigned int bytesperline = pix_fmt->plane_fmt[0].bytesperline;
|
||||||
|
|
||||||
|
if (!width)
|
||||||
|
width = HEVC_D_DEFAULT_WIDTH;
|
||||||
|
if (width > HEVC_D_MAX_WIDTH)
|
||||||
|
width = HEVC_D_MAX_WIDTH;
|
||||||
|
if (!height)
|
||||||
|
height = HEVC_D_DEFAULT_HEIGHT;
|
||||||
|
if (height > HEVC_D_MAX_HEIGHT)
|
||||||
|
height = HEVC_D_MAX_HEIGHT;
|
||||||
|
|
||||||
|
/* For column formats set bytesperline to column height (stride2) */
|
||||||
|
switch (pix_fmt->pixelformat) {
|
||||||
|
default:
|
||||||
|
pix_fmt->pixelformat = V4L2_PIX_FMT_NV12MT_COL128;
|
||||||
|
fallthrough;
|
||||||
|
case V4L2_PIX_FMT_NV12MT_COL128:
|
||||||
|
/* Width rounds up to columns */
|
||||||
|
width = ALIGN(width, 128);
|
||||||
|
height = ALIGN(height, 8);
|
||||||
|
|
||||||
|
/* column height is sizeimage / bytesperline */
|
||||||
|
bytesperline = width;
|
||||||
|
sizeimage = bytesperline * height;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_PIX_FMT_NV12MT_10_COL128:
|
||||||
|
/* width in pixels (3 pels = 4 bytes) rounded to 128 byte
|
||||||
|
* columns
|
||||||
|
*/
|
||||||
|
width = ALIGN(((width + 2) / 3), 32) * 3;
|
||||||
|
height = ALIGN(height, 8);
|
||||||
|
|
||||||
|
/* column height is sizeimage / bytesperline */
|
||||||
|
bytesperline = width * 4 / 3;
|
||||||
|
sizeimage = bytesperline * height;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_PIX_FMT_NV12_COL128:
|
||||||
|
/* Width rounds up to columns */
|
||||||
|
width = ALIGN(width, 128);
|
||||||
|
height = ALIGN(height, 8);
|
||||||
|
|
||||||
|
/* column height
|
||||||
|
* Accept suggested shape if at least min & < 2 * min
|
||||||
|
*/
|
||||||
|
bytesperline = constrain2x(bytesperline, height * 3 / 2);
|
||||||
|
sizeimage = bytesperline * width;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case V4L2_PIX_FMT_NV12_10_COL128:
|
||||||
|
/* width in pixels (3 pels = 4 bytes) rounded to 128 byte
|
||||||
|
* columns
|
||||||
|
*/
|
||||||
|
width = ALIGN(((width + 2) / 3), 32) * 3;
|
||||||
|
height = ALIGN(height, 8);
|
||||||
|
|
||||||
|
/* column height
|
||||||
|
* Accept suggested shape if at least min & < 2 * min
|
||||||
|
*/
|
||||||
|
bytesperline = constrain2x(bytesperline, height * 3 / 2);
|
||||||
|
sizeimage = bytesperline * width * 4 / 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pix_fmt->width = width;
|
||||||
|
pix_fmt->height = height;
|
||||||
|
|
||||||
|
pix_fmt->field = V4L2_FIELD_NONE;
|
||||||
|
switch (pix_fmt->pixelformat) {
|
||||||
|
default:
|
||||||
|
case V4L2_PIX_FMT_NV12MT_COL128:
|
||||||
|
case V4L2_PIX_FMT_NV12MT_10_COL128:
|
||||||
|
pix_fmt->plane_fmt[0].bytesperline = bytesperline;
|
||||||
|
pix_fmt->plane_fmt[0].sizeimage = sizeimage;
|
||||||
|
pix_fmt->plane_fmt[1].bytesperline = bytesperline;
|
||||||
|
pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
|
||||||
|
pix_fmt->num_planes = 2;
|
||||||
|
break;
|
||||||
|
case V4L2_PIX_FMT_NV12_COL128:
|
||||||
|
case V4L2_PIX_FMT_NV12_10_COL128:
|
||||||
|
pix_fmt->plane_fmt[0].bytesperline = bytesperline;
|
||||||
|
pix_fmt->plane_fmt[0].sizeimage = sizeimage;
|
||||||
|
pix_fmt->num_planes = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_querycap(struct file *file, void *priv,
|
||||||
|
struct v4l2_capability *cap)
|
||||||
|
{
|
||||||
|
strscpy(cap->driver, HEVC_D_NAME, sizeof(cap->driver));
|
||||||
|
strscpy(cap->card, HEVC_D_NAME, sizeof(cap->card));
|
||||||
|
snprintf(cap->bus_info, sizeof(cap->bus_info),
|
||||||
|
"platform:%s", HEVC_D_NAME);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_enum_fmt_vid_out(struct file *file, void *priv,
|
||||||
|
struct v4l2_fmtdesc *f)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Input formats
|
||||||
|
* H.265 Slice only
|
||||||
|
*/
|
||||||
|
if (f->index == 0) {
|
||||||
|
f->pixelformat = V4L2_PIX_FMT_HEVC_SLICE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_hevc_validate_sps(const struct v4l2_ctrl_hevc_sps * const sps)
|
||||||
|
{
|
||||||
|
const unsigned int ctb_log2_size_y =
|
||||||
|
sps->log2_min_luma_coding_block_size_minus3 + 3 +
|
||||||
|
sps->log2_diff_max_min_luma_coding_block_size;
|
||||||
|
const unsigned int min_tb_log2_size_y =
|
||||||
|
sps->log2_min_luma_transform_block_size_minus2 + 2;
|
||||||
|
const unsigned int max_tb_log2_size_y = min_tb_log2_size_y +
|
||||||
|
sps->log2_diff_max_min_luma_transform_block_size;
|
||||||
|
|
||||||
|
/* Local limitations */
|
||||||
|
if (sps->pic_width_in_luma_samples < 32 ||
|
||||||
|
sps->pic_width_in_luma_samples > 4096)
|
||||||
|
return 0;
|
||||||
|
if (sps->pic_height_in_luma_samples < 32 ||
|
||||||
|
sps->pic_height_in_luma_samples > 4096)
|
||||||
|
return 0;
|
||||||
|
if (!(sps->bit_depth_luma_minus8 == 0 ||
|
||||||
|
sps->bit_depth_luma_minus8 == 2))
|
||||||
|
return 0;
|
||||||
|
if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8)
|
||||||
|
return 0;
|
||||||
|
if (sps->chroma_format_idc != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Limits from H.265 7.4.3.2.1 */
|
||||||
|
if (sps->log2_max_pic_order_cnt_lsb_minus4 > 12)
|
||||||
|
return 0;
|
||||||
|
if (sps->sps_max_dec_pic_buffering_minus1 > 15)
|
||||||
|
return 0;
|
||||||
|
if (sps->sps_max_num_reorder_pics >
|
||||||
|
sps->sps_max_dec_pic_buffering_minus1)
|
||||||
|
return 0;
|
||||||
|
if (ctb_log2_size_y > 6)
|
||||||
|
return 0;
|
||||||
|
if (max_tb_log2_size_y > 5)
|
||||||
|
return 0;
|
||||||
|
if (max_tb_log2_size_y > ctb_log2_size_y)
|
||||||
|
return 0;
|
||||||
|
if (sps->max_transform_hierarchy_depth_inter >
|
||||||
|
(ctb_log2_size_y - min_tb_log2_size_y))
|
||||||
|
return 0;
|
||||||
|
if (sps->max_transform_hierarchy_depth_intra >
|
||||||
|
(ctb_log2_size_y - min_tb_log2_size_y))
|
||||||
|
return 0;
|
||||||
|
/* Check pcm stuff */
|
||||||
|
if (sps->num_short_term_ref_pic_sets > 64)
|
||||||
|
return 0;
|
||||||
|
if (sps->num_long_term_ref_pics_sps > 32)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 pixelformat_from_sps(const struct v4l2_ctrl_hevc_sps * const sps,
|
||||||
|
const int index)
|
||||||
|
{
|
||||||
|
static const u32 all_formats[] = {
|
||||||
|
V4L2_PIX_FMT_NV12MT_COL128,
|
||||||
|
V4L2_PIX_FMT_NV12MT_10_COL128,
|
||||||
|
V4L2_PIX_FMT_NV12_COL128,
|
||||||
|
V4L2_PIX_FMT_NV12_10_COL128,
|
||||||
|
};
|
||||||
|
u32 pf = 0;
|
||||||
|
|
||||||
|
if (!is_sps_set(sps) || !hevc_d_hevc_validate_sps(sps)) {
|
||||||
|
/* Treat this as an error? For now return both */
|
||||||
|
|
||||||
|
if (index < ARRAY_SIZE(all_formats))
|
||||||
|
pf = all_formats[index];
|
||||||
|
} else {
|
||||||
|
if (index == 0) {
|
||||||
|
if (sps->bit_depth_luma_minus8 == 0)
|
||||||
|
pf = V4L2_PIX_FMT_NV12MT_COL128;
|
||||||
|
else if (sps->bit_depth_luma_minus8 == 2)
|
||||||
|
pf = V4L2_PIX_FMT_NV12MT_10_COL128;
|
||||||
|
} else if (index == 1) {
|
||||||
|
if (sps->bit_depth_luma_minus8 == 0)
|
||||||
|
pf = V4L2_PIX_FMT_NV12_COL128;
|
||||||
|
else if (sps->bit_depth_luma_minus8 == 2)
|
||||||
|
pf = V4L2_PIX_FMT_NV12_10_COL128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_color(struct v4l2_pix_format_mplane *d,
|
||||||
|
const struct v4l2_pix_format_mplane *s)
|
||||||
|
{
|
||||||
|
d->colorspace = s->colorspace;
|
||||||
|
d->xfer_func = s->xfer_func;
|
||||||
|
d->ycbcr_enc = s->ycbcr_enc;
|
||||||
|
d->quantization = s->quantization;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct v4l2_pix_format_mplane
|
||||||
|
hevc_d_hevc_default_dst_fmt(struct hevc_d_ctx * const ctx)
|
||||||
|
{
|
||||||
|
const struct v4l2_ctrl_hevc_sps * const sps =
|
||||||
|
hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
|
||||||
|
struct v4l2_pix_format_mplane pix_fmt;
|
||||||
|
|
||||||
|
memset(&pix_fmt, 0, sizeof(pix_fmt));
|
||||||
|
if (is_sps_set(sps)) {
|
||||||
|
pix_fmt.width = sps->pic_width_in_luma_samples;
|
||||||
|
pix_fmt.height = sps->pic_height_in_luma_samples;
|
||||||
|
pix_fmt.pixelformat = pixelformat_from_sps(sps, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hevc_d_prepare_dst_format(&pix_fmt);
|
||||||
|
copy_color(&pix_fmt, &ctx->src_fmt);
|
||||||
|
|
||||||
|
return pix_fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 hevc_d_hevc_get_dst_pixelformat(struct hevc_d_ctx * const ctx,
|
||||||
|
const int index)
|
||||||
|
{
|
||||||
|
const struct v4l2_ctrl_hevc_sps * const sps =
|
||||||
|
hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
|
||||||
|
|
||||||
|
return pixelformat_from_sps(sps, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_enum_fmt_vid_cap(struct file *file, void *priv,
|
||||||
|
struct v4l2_fmtdesc *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx * const ctx = hevc_d_file2ctx(file);
|
||||||
|
|
||||||
|
const u32 pf = hevc_d_hevc_get_dst_pixelformat(ctx, f->index);
|
||||||
|
|
||||||
|
if (pf == 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
f->pixelformat = pf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get dst format - sets it to default if otherwise unset
|
||||||
|
* returns a pointer to the struct as a convienience
|
||||||
|
*/
|
||||||
|
static struct v4l2_pix_format_mplane *get_dst_fmt(struct hevc_d_ctx *const ctx)
|
||||||
|
{
|
||||||
|
if (!ctx->dst_fmt_set)
|
||||||
|
ctx->dst_fmt = hevc_d_hevc_default_dst_fmt(ctx);
|
||||||
|
return &ctx->dst_fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_g_fmt_vid_cap(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
|
||||||
|
|
||||||
|
f->fmt.pix_mp = *get_dst_fmt(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_g_fmt_vid_out(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
|
||||||
|
|
||||||
|
f->fmt.pix_mp = ctx->src_fmt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_try_fmt_vid_cap(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
|
||||||
|
const struct v4l2_ctrl_hevc_sps * const sps =
|
||||||
|
hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
|
||||||
|
u32 pixelformat;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; (pixelformat = pixelformat_from_sps(sps, i)) != 0; i++) {
|
||||||
|
if (f->fmt.pix_mp.pixelformat == pixelformat)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't have any way of finding out colourspace so believe
|
||||||
|
* anything we are told - take anything set in src as a default
|
||||||
|
*/
|
||||||
|
if (f->fmt.pix_mp.colorspace == V4L2_COLORSPACE_DEFAULT)
|
||||||
|
copy_color(&f->fmt.pix_mp, &ctx->src_fmt);
|
||||||
|
|
||||||
|
f->fmt.pix_mp.pixelformat = pixelformat;
|
||||||
|
hevc_d_prepare_dst_format(&f->fmt.pix_mp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_try_fmt_vid_out(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
hevc_d_prepare_src_format(&f->fmt.pix_mp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_s_fmt_vid_cap(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
|
||||||
|
struct vb2_queue *vq;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
|
||||||
|
if (vb2_is_busy(vq))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
ret = hevc_d_try_fmt_vid_cap(file, priv, f);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ctx->dst_fmt = f->fmt.pix_mp;
|
||||||
|
ctx->dst_fmt_set = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_s_fmt_vid_out(struct file *file, void *priv,
|
||||||
|
struct v4l2_format *f)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
|
||||||
|
struct vb2_queue *vq;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
|
||||||
|
if (vb2_is_busy(vq))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
ret = hevc_d_try_fmt_vid_out(file, priv, f);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ctx->src_fmt = f->fmt.pix_mp;
|
||||||
|
ctx->dst_fmt_set = 0; /* Setting src invalidates dst */
|
||||||
|
|
||||||
|
/* Propagate colorspace information to capture. */
|
||||||
|
copy_color(&ctx->dst_fmt, &f->fmt.pix_mp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct v4l2_ioctl_ops hevc_d_ioctl_ops = {
|
||||||
|
.vidioc_querycap = hevc_d_querycap,
|
||||||
|
|
||||||
|
.vidioc_enum_fmt_vid_cap = hevc_d_enum_fmt_vid_cap,
|
||||||
|
.vidioc_g_fmt_vid_cap_mplane = hevc_d_g_fmt_vid_cap,
|
||||||
|
.vidioc_try_fmt_vid_cap_mplane = hevc_d_try_fmt_vid_cap,
|
||||||
|
.vidioc_s_fmt_vid_cap_mplane = hevc_d_s_fmt_vid_cap,
|
||||||
|
|
||||||
|
.vidioc_enum_fmt_vid_out = hevc_d_enum_fmt_vid_out,
|
||||||
|
.vidioc_g_fmt_vid_out_mplane = hevc_d_g_fmt_vid_out,
|
||||||
|
.vidioc_try_fmt_vid_out_mplane = hevc_d_try_fmt_vid_out,
|
||||||
|
.vidioc_s_fmt_vid_out_mplane = hevc_d_s_fmt_vid_out,
|
||||||
|
|
||||||
|
.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_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd,
|
||||||
|
.vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd,
|
||||||
|
|
||||||
|
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
||||||
|
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hevc_d_queue_setup(struct vb2_queue *vq, unsigned int *nbufs,
|
||||||
|
unsigned int *nplanes, unsigned int sizes[],
|
||||||
|
struct device *alloc_devs[])
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
|
||||||
|
struct v4l2_pix_format_mplane *pix_fmt;
|
||||||
|
int expected_nplanes;
|
||||||
|
|
||||||
|
if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
|
||||||
|
pix_fmt = &ctx->src_fmt;
|
||||||
|
expected_nplanes = 1;
|
||||||
|
} else {
|
||||||
|
pix_fmt = get_dst_fmt(ctx);
|
||||||
|
expected_nplanes = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*nplanes) {
|
||||||
|
if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
|
||||||
|
pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
|
||||||
|
if (*nplanes != expected_nplanes ||
|
||||||
|
sizes[0] < pix_fmt->plane_fmt[0].sizeimage ||
|
||||||
|
sizes[1] < pix_fmt->plane_fmt[1].sizeimage)
|
||||||
|
return -EINVAL;
|
||||||
|
} else {
|
||||||
|
if (sizes[0] < pix_fmt->plane_fmt[0].sizeimage)
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sizes[0] = pix_fmt->plane_fmt[0].sizeimage;
|
||||||
|
if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
|
||||||
|
*nplanes = 1;
|
||||||
|
} else {
|
||||||
|
if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
|
||||||
|
pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
|
||||||
|
sizes[1] = pix_fmt->plane_fmt[1].sizeimage;
|
||||||
|
*nplanes = 2;
|
||||||
|
} else {
|
||||||
|
*nplanes = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hevc_d_queue_cleanup(struct vb2_queue *vq, u32 state)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
|
||||||
|
struct vb2_v4l2_buffer *vbuf;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (V4L2_TYPE_IS_OUTPUT(vq->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_ctrl_request_complete(vbuf->vb2_buf.req_obj.req,
|
||||||
|
&ctx->hdl);
|
||||||
|
v4l2_m2m_buf_done(vbuf, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_buf_out_validate(struct vb2_buffer *vb)
|
||||||
|
{
|
||||||
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||||
|
|
||||||
|
vbuf->field = V4L2_FIELD_NONE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_buf_prepare(struct vb2_buffer *vb)
|
||||||
|
{
|
||||||
|
struct vb2_queue *vq = vb->vb2_queue;
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
|
||||||
|
struct v4l2_pix_format_mplane *pix_fmt;
|
||||||
|
|
||||||
|
if (V4L2_TYPE_IS_OUTPUT(vq->type))
|
||||||
|
pix_fmt = &ctx->src_fmt;
|
||||||
|
else
|
||||||
|
pix_fmt = &ctx->dst_fmt;
|
||||||
|
|
||||||
|
if (vb2_plane_size(vb, 0) < pix_fmt->plane_fmt[0].sizeimage ||
|
||||||
|
vb2_plane_size(vb, 1) < pix_fmt->plane_fmt[1].sizeimage)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
vb2_set_plane_payload(vb, 0, pix_fmt->plane_fmt[0].sizeimage);
|
||||||
|
vb2_set_plane_payload(vb, 1, pix_fmt->plane_fmt[1].sizeimage);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only stops the clock if streaom off on both output & capture */
|
||||||
|
static void stop_clock(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (ctx->src_stream_on ||
|
||||||
|
ctx->dst_stream_on)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clk_set_min_rate(dev->clock, 0);
|
||||||
|
clk_disable_unprepare(dev->clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always starts the clock if it isn't already on this ctx */
|
||||||
|
static int start_clock(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
rv = clk_set_min_rate(dev->clock, dev->max_clock_rate);
|
||||||
|
if (rv) {
|
||||||
|
dev_err(dev->dev, "Failed to set clock rate\n");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = clk_prepare_enable(dev->clock);
|
||||||
|
if (rv) {
|
||||||
|
dev_err(dev->dev, "Failed to enable clock\n");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hevc_d_start_streaming(struct vb2_queue *vq, unsigned int count)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
|
||||||
|
struct hevc_d_dev *dev = ctx->dev;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!V4L2_TYPE_IS_OUTPUT(vq->type)) {
|
||||||
|
ctx->dst_stream_on = 1;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->src_fmt.pixelformat != V4L2_PIX_FMT_HEVC_SLICE) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->src_stream_on)
|
||||||
|
goto ok;
|
||||||
|
|
||||||
|
ret = start_clock(dev, ctx);
|
||||||
|
if (ret)
|
||||||
|
goto fail_cleanup;
|
||||||
|
|
||||||
|
ret = hevc_d_h265_start(ctx);
|
||||||
|
if (ret)
|
||||||
|
goto fail_stop_clock;
|
||||||
|
|
||||||
|
ctx->src_stream_on = 1;
|
||||||
|
ok:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail_stop_clock:
|
||||||
|
stop_clock(dev, ctx);
|
||||||
|
fail_cleanup:
|
||||||
|
v4l2_err(&dev->v4l2_dev, "%s: qtype=%d: FAIL\n", __func__, vq->type);
|
||||||
|
hevc_d_queue_cleanup(vq, VB2_BUF_STATE_QUEUED);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hevc_d_stop_streaming(struct vb2_queue *vq)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
|
||||||
|
struct hevc_d_dev *dev = ctx->dev;
|
||||||
|
|
||||||
|
if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
|
||||||
|
ctx->src_stream_on = 0;
|
||||||
|
hevc_d_h265_stop(ctx);
|
||||||
|
} else {
|
||||||
|
ctx->dst_stream_on = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hevc_d_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
|
||||||
|
|
||||||
|
vb2_wait_for_all_buffers(vq);
|
||||||
|
|
||||||
|
stop_clock(dev, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hevc_d_buf_queue(struct vb2_buffer *vb)
|
||||||
|
{
|
||||||
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
|
||||||
|
|
||||||
|
v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hevc_d_buf_request_complete(struct vb2_buffer *vb)
|
||||||
|
{
|
||||||
|
struct hevc_d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
|
||||||
|
|
||||||
|
v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct vb2_ops hevc_d_qops = {
|
||||||
|
.queue_setup = hevc_d_queue_setup,
|
||||||
|
.buf_prepare = hevc_d_buf_prepare,
|
||||||
|
.buf_queue = hevc_d_buf_queue,
|
||||||
|
.buf_out_validate = hevc_d_buf_out_validate,
|
||||||
|
.buf_request_complete = hevc_d_buf_request_complete,
|
||||||
|
.start_streaming = hevc_d_start_streaming,
|
||||||
|
.stop_streaming = hevc_d_stop_streaming,
|
||||||
|
.wait_prepare = vb2_ops_wait_prepare,
|
||||||
|
.wait_finish = vb2_ops_wait_finish,
|
||||||
|
};
|
||||||
|
|
||||||
|
int hevc_d_queue_init(void *priv, struct vb2_queue *src_vq,
|
||||||
|
struct vb2_queue *dst_vq)
|
||||||
|
{
|
||||||
|
struct hevc_d_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 hevc_d_buffer);
|
||||||
|
src_vq->ops = &hevc_d_qops;
|
||||||
|
src_vq->mem_ops = &vb2_dma_contig_memops;
|
||||||
|
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
||||||
|
src_vq->lock = &ctx->ctx_mutex;
|
||||||
|
src_vq->dev = ctx->dev->dev;
|
||||||
|
src_vq->supports_requests = true;
|
||||||
|
src_vq->requires_requests = true;
|
||||||
|
|
||||||
|
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 hevc_d_buffer);
|
||||||
|
dst_vq->min_queued_buffers = 1;
|
||||||
|
dst_vq->ops = &hevc_d_qops;
|
||||||
|
dst_vq->mem_ops = &vb2_dma_contig_memops;
|
||||||
|
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
||||||
|
dst_vq->lock = &ctx->ctx_mutex;
|
||||||
|
dst_vq->dev = ctx->dev->dev;
|
||||||
|
|
||||||
|
return vb2_queue_init(dst_vq);
|
||||||
|
}
|
||||||
38
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
Normal file
38
drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Raspberry Pi HEVC driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Raspberry Pi Ltd
|
||||||
|
*
|
||||||
|
* Based on the Cedrus VPU driver, that is:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
|
||||||
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
||||||
|
* Copyright (C) 2018 Bootlin
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HEVC_D_VIDEO_H_
|
||||||
|
#define _HEVC_D_VIDEO_H_
|
||||||
|
|
||||||
|
struct hevc_d_format {
|
||||||
|
u32 pixelformat;
|
||||||
|
u32 directions;
|
||||||
|
unsigned int capabilities;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline int is_sps_set(const struct v4l2_ctrl_hevc_sps * const sps)
|
||||||
|
{
|
||||||
|
return sps && sps->pic_width_in_luma_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const struct v4l2_ioctl_ops hevc_d_ioctl_ops;
|
||||||
|
|
||||||
|
int hevc_d_queue_init(void *priv, struct vb2_queue *src_vq,
|
||||||
|
struct vb2_queue *dst_vq);
|
||||||
|
|
||||||
|
size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8);
|
||||||
|
size_t hevc_d_round_up_size(const size_t x);
|
||||||
|
|
||||||
|
void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user