forked from Minki/linux
5800571960
-----BEGIN PGP SIGNATURE----- iQFSBAABCAA8FiEEq68RxlopcLEwq+PEeb4+QwBBGIYFAlz8fAYeHHRvcnZhbGRz QGxpbnV4LWZvdW5kYXRpb24ub3JnAAoJEHm+PkMAQRiG1asH/3ySguxqtqL1MCBa 4/SZ37PHeWKMerfX6ZyJdgEqK3B+PWlmuLiOMNK5h2bPLzeQQQAmHU/mfKmpXqgB dHwUbG9yNnyUtTfsfRqAnCA6vpuw9Yb1oIzTCVQrgJLSWD0j7scBBvmzYqguOkto ThwigLUq3AILr8EfR4rh+GM+5Dn9OTEFAxwil9fPHQo7QoczwZxpURhScT6Co9TB DqLA3fvXbBvLs/CZy/S5vKM9hKzC+p39ApFTURvFPrelUVnythAM0dPDJg3pIn5u g+/+gDxDFa+7ANxvxO2ng1sJPDqJMeY/xmjJYlYyLpA33B7zLNk2vDHhAP06VTtr XCMhQ9s= =cb80 -----END PGP SIGNATURE----- Merge tag 'v5.2-rc4' into media/master There are some conflicts due to SPDX changes. We also have more patches being merged via media tree touching them. So, let's merge back from upstream and address those. Linux 5.2-rc4 * tag 'v5.2-rc4': (767 commits) Linux 5.2-rc4 MAINTAINERS: Karthikeyan Ramasubramanian is MIA i2c: xiic: Add max_read_len quirk lockref: Limit number of cmpxchg loop retries uaccess: add noop untagged_addr definition x86/insn-eval: Fix use-after-free access to LDT entry kbuild: use more portable 'command -v' for cc-cross-prefix s390/unwind: correct stack switching during unwind block, bfq: add weight symlink to the bfq.weight cgroup parameter cgroup: let a symlink too be created with a cftype file drm/nouveau/secboot/gp10[2467]: support newer FW to fix SEC2 failures on some boards drm/nouveau/secboot: enable loading of versioned LS PMU/SEC2 ACR msgqueue FW drm/nouveau/secboot: split out FW version-specific LS function pointers drm/nouveau/secboot: pass max supported FW version to LS load funcs drm/nouveau/core: support versioned firmware loading drm/nouveau/core: pass subdev into nvkm_firmware_get, rather than device block: free sched's request pool in blk_cleanup_queue pktgen: do not sleep with the thread lock held. net: mvpp2: Use strscpy to handle stat strings net: rds: fix memory leak in rds_ib_flush_mr_pool ... Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
421 lines
11 KiB
C
421 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2016 MediaTek Inc.
|
|
* Author: PC Chen <pc.chen@mediatek.com>
|
|
* Tiffany Lin <tiffany.lin@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/v4l2-mem2mem.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include "mtk_vcodec_drv.h"
|
|
#include "mtk_vcodec_enc.h"
|
|
#include "mtk_vcodec_enc_pm.h"
|
|
#include "mtk_vcodec_intr.h"
|
|
#include "mtk_vcodec_util.h"
|
|
#include "mtk_vpu.h"
|
|
|
|
module_param(mtk_v4l2_dbg_level, int, S_IRUGO | S_IWUSR);
|
|
module_param(mtk_vcodec_dbg, bool, S_IRUGO | S_IWUSR);
|
|
|
|
/* Wake up context wait_queue */
|
|
static void wake_up_ctx(struct mtk_vcodec_ctx *ctx, unsigned int reason)
|
|
{
|
|
ctx->int_cond = 1;
|
|
ctx->int_type = reason;
|
|
wake_up_interruptible(&ctx->queue);
|
|
}
|
|
|
|
static void clean_irq_status(unsigned int irq_status, void __iomem *addr)
|
|
{
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_PAUSE)
|
|
writel(MTK_VENC_IRQ_STATUS_PAUSE, addr);
|
|
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_SWITCH)
|
|
writel(MTK_VENC_IRQ_STATUS_SWITCH, addr);
|
|
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_DRAM)
|
|
writel(MTK_VENC_IRQ_STATUS_DRAM, addr);
|
|
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_SPS)
|
|
writel(MTK_VENC_IRQ_STATUS_SPS, addr);
|
|
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_PPS)
|
|
writel(MTK_VENC_IRQ_STATUS_PPS, addr);
|
|
|
|
if (irq_status & MTK_VENC_IRQ_STATUS_FRM)
|
|
writel(MTK_VENC_IRQ_STATUS_FRM, addr);
|
|
|
|
}
|
|
static irqreturn_t mtk_vcodec_enc_irq_handler(int irq, void *priv)
|
|
{
|
|
struct mtk_vcodec_dev *dev = priv;
|
|
struct mtk_vcodec_ctx *ctx;
|
|
unsigned long flags;
|
|
void __iomem *addr;
|
|
|
|
spin_lock_irqsave(&dev->irqlock, flags);
|
|
ctx = dev->curr_ctx;
|
|
spin_unlock_irqrestore(&dev->irqlock, flags);
|
|
|
|
mtk_v4l2_debug(1, "id=%d", ctx->id);
|
|
addr = dev->reg_base[VENC_SYS] + MTK_VENC_IRQ_ACK_OFFSET;
|
|
|
|
ctx->irq_status = readl(dev->reg_base[VENC_SYS] +
|
|
(MTK_VENC_IRQ_STATUS_OFFSET));
|
|
|
|
clean_irq_status(ctx->irq_status, addr);
|
|
|
|
wake_up_ctx(ctx, MTK_INST_IRQ_RECEIVED);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mtk_vcodec_enc_lt_irq_handler(int irq, void *priv)
|
|
{
|
|
struct mtk_vcodec_dev *dev = priv;
|
|
struct mtk_vcodec_ctx *ctx;
|
|
unsigned long flags;
|
|
void __iomem *addr;
|
|
|
|
spin_lock_irqsave(&dev->irqlock, flags);
|
|
ctx = dev->curr_ctx;
|
|
spin_unlock_irqrestore(&dev->irqlock, flags);
|
|
|
|
mtk_v4l2_debug(1, "id=%d", ctx->id);
|
|
ctx->irq_status = readl(dev->reg_base[VENC_LT_SYS] +
|
|
(MTK_VENC_IRQ_STATUS_OFFSET));
|
|
|
|
addr = dev->reg_base[VENC_LT_SYS] + MTK_VENC_IRQ_ACK_OFFSET;
|
|
|
|
clean_irq_status(ctx->irq_status, addr);
|
|
|
|
wake_up_ctx(ctx, MTK_INST_IRQ_RECEIVED);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void mtk_vcodec_enc_reset_handler(void *priv)
|
|
{
|
|
struct mtk_vcodec_dev *dev = priv;
|
|
struct mtk_vcodec_ctx *ctx;
|
|
|
|
mtk_v4l2_debug(0, "Watchdog timeout!!");
|
|
|
|
mutex_lock(&dev->dev_mutex);
|
|
list_for_each_entry(ctx, &dev->ctx_list, list) {
|
|
ctx->state = MTK_STATE_ABORT;
|
|
mtk_v4l2_debug(0, "[%d] Change to state MTK_STATE_ABORT",
|
|
ctx->id);
|
|
}
|
|
mutex_unlock(&dev->dev_mutex);
|
|
}
|
|
|
|
static int fops_vcodec_open(struct file *file)
|
|
{
|
|
struct mtk_vcodec_dev *dev = video_drvdata(file);
|
|
struct mtk_vcodec_ctx *ctx = NULL;
|
|
int ret = 0;
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&dev->dev_mutex);
|
|
/*
|
|
* Use simple counter to uniquely identify this context. Only
|
|
* used for logging.
|
|
*/
|
|
ctx->id = dev->id_counter++;
|
|
v4l2_fh_init(&ctx->fh, video_devdata(file));
|
|
file->private_data = &ctx->fh;
|
|
v4l2_fh_add(&ctx->fh);
|
|
INIT_LIST_HEAD(&ctx->list);
|
|
ctx->dev = dev;
|
|
init_waitqueue_head(&ctx->queue);
|
|
|
|
ctx->type = MTK_INST_ENCODER;
|
|
ret = mtk_vcodec_enc_ctrls_setup(ctx);
|
|
if (ret) {
|
|
mtk_v4l2_err("Failed to setup controls() (%d)",
|
|
ret);
|
|
goto err_ctrls_setup;
|
|
}
|
|
ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev_enc, ctx,
|
|
&mtk_vcodec_enc_queue_init);
|
|
if (IS_ERR((__force void *)ctx->m2m_ctx)) {
|
|
ret = PTR_ERR((__force void *)ctx->m2m_ctx);
|
|
mtk_v4l2_err("Failed to v4l2_m2m_ctx_init() (%d)",
|
|
ret);
|
|
goto err_m2m_ctx_init;
|
|
}
|
|
mtk_vcodec_enc_set_default_params(ctx);
|
|
|
|
if (v4l2_fh_is_singular(&ctx->fh)) {
|
|
/*
|
|
* vpu_load_firmware checks if it was loaded already and
|
|
* does nothing in that case
|
|
*/
|
|
ret = vpu_load_firmware(dev->vpu_plat_dev);
|
|
if (ret < 0) {
|
|
/*
|
|
* Return 0 if downloading firmware successfully,
|
|
* otherwise it is failed
|
|
*/
|
|
mtk_v4l2_err("vpu_load_firmware failed!");
|
|
goto err_load_fw;
|
|
}
|
|
|
|
dev->enc_capability =
|
|
vpu_get_venc_hw_capa(dev->vpu_plat_dev);
|
|
mtk_v4l2_debug(0, "encoder capability %x", dev->enc_capability);
|
|
}
|
|
|
|
mtk_v4l2_debug(2, "Create instance [%d]@%p m2m_ctx=%p ",
|
|
ctx->id, ctx, ctx->m2m_ctx);
|
|
|
|
list_add(&ctx->list, &dev->ctx_list);
|
|
|
|
mutex_unlock(&dev->dev_mutex);
|
|
mtk_v4l2_debug(0, "%s encoder [%d]", dev_name(&dev->plat_dev->dev),
|
|
ctx->id);
|
|
return ret;
|
|
|
|
/* Deinit when failure occurred */
|
|
err_load_fw:
|
|
v4l2_m2m_ctx_release(ctx->m2m_ctx);
|
|
err_m2m_ctx_init:
|
|
v4l2_ctrl_handler_free(&ctx->ctrl_hdl);
|
|
err_ctrls_setup:
|
|
v4l2_fh_del(&ctx->fh);
|
|
v4l2_fh_exit(&ctx->fh);
|
|
kfree(ctx);
|
|
mutex_unlock(&dev->dev_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fops_vcodec_release(struct file *file)
|
|
{
|
|
struct mtk_vcodec_dev *dev = video_drvdata(file);
|
|
struct mtk_vcodec_ctx *ctx = fh_to_ctx(file->private_data);
|
|
|
|
mtk_v4l2_debug(1, "[%d] encoder", ctx->id);
|
|
mutex_lock(&dev->dev_mutex);
|
|
|
|
mtk_vcodec_enc_release(ctx);
|
|
v4l2_fh_del(&ctx->fh);
|
|
v4l2_fh_exit(&ctx->fh);
|
|
v4l2_ctrl_handler_free(&ctx->ctrl_hdl);
|
|
v4l2_m2m_ctx_release(ctx->m2m_ctx);
|
|
|
|
list_del_init(&ctx->list);
|
|
kfree(ctx);
|
|
mutex_unlock(&dev->dev_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_file_operations mtk_vcodec_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = fops_vcodec_open,
|
|
.release = fops_vcodec_release,
|
|
.poll = v4l2_m2m_fop_poll,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = v4l2_m2m_fop_mmap,
|
|
};
|
|
|
|
static int mtk_vcodec_probe(struct platform_device *pdev)
|
|
{
|
|
struct mtk_vcodec_dev *dev;
|
|
struct video_device *vfd_enc;
|
|
struct resource *res;
|
|
int i, j, ret;
|
|
|
|
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&dev->ctx_list);
|
|
dev->plat_dev = pdev;
|
|
|
|
dev->vpu_plat_dev = vpu_get_plat_device(dev->plat_dev);
|
|
if (dev->vpu_plat_dev == NULL) {
|
|
mtk_v4l2_err("[VPU] vpu device in not ready");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
vpu_wdt_reg_handler(dev->vpu_plat_dev, mtk_vcodec_enc_reset_handler,
|
|
dev, VPU_RST_ENC);
|
|
|
|
ret = mtk_vcodec_init_enc_pm(dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to get mt vcodec clock source!");
|
|
return ret;
|
|
}
|
|
|
|
for (i = VENC_SYS, j = 0; i < NUM_MAX_VCODEC_REG_BASE; i++, j++) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, j);
|
|
dev->reg_base[i] = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR((__force void *)dev->reg_base[i])) {
|
|
ret = PTR_ERR((__force void *)dev->reg_base[i]);
|
|
goto err_res;
|
|
}
|
|
mtk_v4l2_debug(2, "reg[%d] base=0x%p", i, dev->reg_base[i]);
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (res == NULL) {
|
|
dev_err(&pdev->dev, "failed to get irq resource");
|
|
ret = -ENOENT;
|
|
goto err_res;
|
|
}
|
|
|
|
dev->enc_irq = platform_get_irq(pdev, 0);
|
|
ret = devm_request_irq(&pdev->dev, dev->enc_irq,
|
|
mtk_vcodec_enc_irq_handler,
|
|
0, pdev->name, dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to install dev->enc_irq %d (%d)",
|
|
dev->enc_irq,
|
|
ret);
|
|
ret = -EINVAL;
|
|
goto err_res;
|
|
}
|
|
|
|
dev->enc_lt_irq = platform_get_irq(pdev, 1);
|
|
ret = devm_request_irq(&pdev->dev,
|
|
dev->enc_lt_irq, mtk_vcodec_enc_lt_irq_handler,
|
|
0, pdev->name, dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to install dev->enc_lt_irq %d (%d)",
|
|
dev->enc_lt_irq, ret);
|
|
ret = -EINVAL;
|
|
goto err_res;
|
|
}
|
|
|
|
disable_irq(dev->enc_irq);
|
|
disable_irq(dev->enc_lt_irq); /* VENC_LT */
|
|
mutex_init(&dev->enc_mutex);
|
|
mutex_init(&dev->dev_mutex);
|
|
spin_lock_init(&dev->irqlock);
|
|
|
|
snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s",
|
|
"[MTK_V4L2_VENC]");
|
|
|
|
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
|
|
if (ret) {
|
|
mtk_v4l2_err("v4l2_device_register err=%d", ret);
|
|
goto err_res;
|
|
}
|
|
|
|
init_waitqueue_head(&dev->queue);
|
|
|
|
/* allocate video device for encoder and register it */
|
|
vfd_enc = video_device_alloc();
|
|
if (!vfd_enc) {
|
|
mtk_v4l2_err("Failed to allocate video device");
|
|
ret = -ENOMEM;
|
|
goto err_enc_alloc;
|
|
}
|
|
vfd_enc->fops = &mtk_vcodec_fops;
|
|
vfd_enc->ioctl_ops = &mtk_venc_ioctl_ops;
|
|
vfd_enc->release = video_device_release;
|
|
vfd_enc->lock = &dev->dev_mutex;
|
|
vfd_enc->v4l2_dev = &dev->v4l2_dev;
|
|
vfd_enc->vfl_dir = VFL_DIR_M2M;
|
|
vfd_enc->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE |
|
|
V4L2_CAP_STREAMING;
|
|
|
|
snprintf(vfd_enc->name, sizeof(vfd_enc->name), "%s",
|
|
MTK_VCODEC_ENC_NAME);
|
|
video_set_drvdata(vfd_enc, dev);
|
|
dev->vfd_enc = vfd_enc;
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
dev->m2m_dev_enc = v4l2_m2m_init(&mtk_venc_m2m_ops);
|
|
if (IS_ERR((__force void *)dev->m2m_dev_enc)) {
|
|
mtk_v4l2_err("Failed to init mem2mem enc device");
|
|
ret = PTR_ERR((__force void *)dev->m2m_dev_enc);
|
|
goto err_enc_mem_init;
|
|
}
|
|
|
|
dev->encode_workqueue =
|
|
alloc_ordered_workqueue(MTK_VCODEC_ENC_NAME,
|
|
WQ_MEM_RECLAIM |
|
|
WQ_FREEZABLE);
|
|
if (!dev->encode_workqueue) {
|
|
mtk_v4l2_err("Failed to create encode workqueue");
|
|
ret = -EINVAL;
|
|
goto err_event_workq;
|
|
}
|
|
|
|
ret = video_register_device(vfd_enc, VFL_TYPE_GRABBER, 1);
|
|
if (ret) {
|
|
mtk_v4l2_err("Failed to register video device");
|
|
goto err_enc_reg;
|
|
}
|
|
|
|
mtk_v4l2_debug(0, "encoder registered as /dev/video%d",
|
|
vfd_enc->num);
|
|
|
|
return 0;
|
|
|
|
err_enc_reg:
|
|
destroy_workqueue(dev->encode_workqueue);
|
|
err_event_workq:
|
|
v4l2_m2m_release(dev->m2m_dev_enc);
|
|
err_enc_mem_init:
|
|
video_unregister_device(vfd_enc);
|
|
err_enc_alloc:
|
|
v4l2_device_unregister(&dev->v4l2_dev);
|
|
err_res:
|
|
mtk_vcodec_release_enc_pm(dev);
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id mtk_vcodec_enc_match[] = {
|
|
{.compatible = "mediatek,mt8173-vcodec-enc",},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mtk_vcodec_enc_match);
|
|
|
|
static int mtk_vcodec_enc_remove(struct platform_device *pdev)
|
|
{
|
|
struct mtk_vcodec_dev *dev = platform_get_drvdata(pdev);
|
|
|
|
mtk_v4l2_debug_enter();
|
|
flush_workqueue(dev->encode_workqueue);
|
|
destroy_workqueue(dev->encode_workqueue);
|
|
if (dev->m2m_dev_enc)
|
|
v4l2_m2m_release(dev->m2m_dev_enc);
|
|
|
|
if (dev->vfd_enc)
|
|
video_unregister_device(dev->vfd_enc);
|
|
|
|
v4l2_device_unregister(&dev->v4l2_dev);
|
|
mtk_vcodec_release_enc_pm(dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver mtk_vcodec_enc_driver = {
|
|
.probe = mtk_vcodec_probe,
|
|
.remove = mtk_vcodec_enc_remove,
|
|
.driver = {
|
|
.name = MTK_VCODEC_ENC_NAME,
|
|
.of_match_table = mtk_vcodec_enc_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(mtk_vcodec_enc_driver);
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Mediatek video codec V4L2 encoder driver");
|