// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018 BayLibre, SAS
* Author: Maxime Jourdan <mjourdan@baylibre.com>
*/
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/syscon.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-mem2mem.h>
#include <media/v4l2-dev.h>
#include <media/videobuf2-dma-contig.h>
#include "vdec.h"
#include "esparser.h"
#include "vdec_helpers.h"
struct dummy_buf {
struct vb2_v4l2_buffer vb;
struct list_head list;
};
/* 16 MiB for parsed bitstream swap exchange */
#define SIZE_VIFIFO SZ_16M
static u32 get_output_size(u32 width, u32 height)
{
return ALIGN(width * height, SZ_64K);
}
u32 amvdec_get_output_size(struct amvdec_session *sess)
{
return get_output_size(sess->width, sess->height);
}
EXPORT_SYMBOL_GPL(amvdec_get_output_size);
static int vdec_codec_needs_recycle(struct amvdec_session *sess)
{
struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
return codec_ops->can_recycle && codec_ops->recycle;
}
static int vdec_recycle_thread(void *data)
{
struct amvdec_session *sess = data;
struct amvdec_core *core = sess->core;
struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
struct amvdec_buffer *tmp, *n;
while (!kthread_should_stop()) {
mutex_lock(&sess->bufs_recycle_lock);
list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
if (!codec_ops->can_recycle(core))
break;
codec_ops->recycle(core, tmp->vb->index);
list_del(&tmp->list);
kfree(tmp);
}
mutex_unlock(&sess->bufs_recycle_lock);
usleep_range(5000, 10000);
}
return 0;
}
static int vdec_poweron(struct amvdec_session *sess)
{
int ret;
struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
ret = clk_prepare_enable(sess->core->dos_parser_clk);
if (ret)
return ret;
ret = clk_prepare_enable(sess->core->dos_clk);
if (ret)
goto disable_dos_parser;
ret = vdec_ops->start(sess);
if (ret)
goto disable_dos;
esparser_power_up(sess);
return 0;
disable_dos:
clk_disable_unprepare(sess->core->dos_clk);
disable_dos_parser:
clk_disable_unprepare(sess->core->dos_parser_clk);
return ret;
}
static void vdec_wait_inactive(struct amvdec_session *sess)
{
/* We consider 50ms with no IRQ to be inactive. */
while (time_is_after_jiffies64(sess->last_irq_jiffies +
msecs_to_jiffies(50)))
msleep(25);
}
static void vdec_poweroff(struct amvdec_session *sess)
{
struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
sess->should_stop = 1;
vdec_wait_inactive(sess);
if (codec_ops->drain)
codec_ops->drain(sess);
vdec_ops->stop(sess);
clk_disable_unprepare