blob: 64a14ee9161feeca1c531495b913c0a41264b331 [file] [log] [blame]
/*
* drivers/amlogic/media/stream_input/parser/stream_bufffer_interface.c
*
* Copyright (C) 2016 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define DEBUG
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <linux/delay.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include <linux/amlogic/media/frame_sync/ptsserv.h>
#include <linux/amlogic/media/frame_sync/tsync_pcr.h>
#include "../../frame_provider/decoder/utils/vdec.h"
#include "../../common/chips/decoder_cpu_ver_info.h"
#include "stream_buffer_base.h"
#include "amports_priv.h"
#include "thread_rw.h"
#define MEM_NAME "stbuf"
#define MAP_RANGE (SZ_1M)
static void stream_buffer_release(struct stream_buf_s *stbuf);
static const char *type_to_str(int t)
{
switch (t) {
case BUF_TYPE_VIDEO:
return "VIDEO";
case BUF_TYPE_AUDIO:
return "AUDIO";
case BUF_TYPE_SUBTITLE:
return "SUB";
case BUF_TYPE_USERDATA:
return "USER";
case BUF_TYPE_HEVC:
return "HEVC";
default:
return "ERR";
}
}
static int stream_buffer_init(struct stream_buf_s *stbuf, struct vdec_s *vdec)
{
int ret = 0;
u32 flags = CODEC_MM_FLAGS_DMA;
bool is_secure = 0;
u32 addr = 0;
int pages = 0;
u32 size;
if (stbuf->buf_start)
return 0;
snprintf(stbuf->name, sizeof(stbuf->name),
"%s-%d", MEM_NAME, vdec->id);
if (stbuf->ext_buf_addr) {
addr = stbuf->ext_buf_addr;
size = stbuf->buf_size;
is_secure = stbuf->is_secure;
pages = (size >> PAGE_SHIFT);
} else {
flags |= CODEC_MM_FLAGS_FOR_VDECODER;
if (vdec->port_flag & PORT_FLAG_DRM) {
flags |= CODEC_MM_FLAGS_TVP;
is_secure = true;
}
size = PAGE_ALIGN(stbuf->buf_size);
pages = (size >> PAGE_SHIFT);
addr = codec_mm_alloc_for_dma(stbuf->name,
pages, PAGE_SHIFT + 4, flags);
if (!addr) {
ret = -ENOMEM;
goto err;
}
stbuf->use_ptsserv = 1;
}
vdec_config_vld_reg(vdec, addr, size);
ret = vdec_set_input_buffer(vdec, addr, size);
if (ret) {
pr_err("[%d]: set input buffer err.\n", stbuf->id);
goto err;
}
atomic_set(&stbuf->payload, 0);
init_waitqueue_head(&stbuf->wq);
stbuf->buf_start = addr;
stbuf->buf_wp = addr;
stbuf->buf_rp = addr;
stbuf->buf_size = size;
stbuf->is_secure = is_secure;
stbuf->no_parser = true;
stbuf->buf_page_num = pages;
stbuf->canusebuf_size = size;
stbuf->stream_offset = 0;
/* init thread write. */
if (!(vdec_get_debug_flags() & 1) &&
!codec_mm_video_tvp_enabled() &&
(!stbuf->ext_buf_addr)) {
int block_size = PAGE_SIZE << 4;
int buf_num = (2 * SZ_1M) / (PAGE_SIZE << 4);
stbuf->write_thread =
threadrw_alloc(buf_num, block_size,
stream_buffer_write_ex, 0);
}
stbuf->flag |= BUF_FLAG_ALLOC;
stbuf->flag |= BUF_FLAG_IN_USE;
if (vdec_single(vdec))
pts_start(stbuf->type);
pr_info("[%d]: [%s-%s] addr: %lx, size: %x, thrRW: %d, extbuf: %d, secure: %d\n",
stbuf->id, type_to_str(stbuf->type), stbuf->name,
stbuf->buf_start, stbuf->buf_size,
!!stbuf->write_thread,
!!stbuf->ext_buf_addr,
stbuf->is_secure);
return 0;
err:
stream_buffer_release(stbuf);
return ret;
}
static void stream_buffer_release(struct stream_buf_s *stbuf)
{
if (stbuf->write_thread)
threadrw_release(stbuf);
if (vdec_single(container_of(stbuf, struct vdec_s, vbuf)))
pts_stop(stbuf->type);
if (stbuf->flag & BUF_FLAG_ALLOC && stbuf->buf_start) {
if (!stbuf->ext_buf_addr)
codec_mm_free_for_dma(MEM_NAME, stbuf->buf_start);
stbuf->flag &= ~BUF_FLAG_ALLOC;
stbuf->ext_buf_addr = 0;
stbuf->buf_start = 0;
stbuf->is_secure = false;
}
stbuf->flag &= ~BUF_FLAG_IN_USE;
}
static int get_free_space(struct stream_buf_s *stbuf)
{
u32 len = stbuf->buf_size;
int idle = 0;
if (!atomic_read(&stbuf->payload) && (stbuf->buf_rp == stbuf->buf_wp))
idle = len;
else if (stbuf->buf_wp > stbuf->buf_rp)
idle = len - (stbuf->buf_wp - stbuf->buf_rp);
else if (stbuf->buf_wp < stbuf->buf_rp)
idle = stbuf->buf_rp - stbuf->buf_wp;
/*pr_info("[%d]: wp: %x, rp: %x, payload: %d, free space: %d\n",
stbuf->id, stbuf->buf_wp, stbuf->buf_rp,
atomic_read(&stbuf->payload), idle);*/
return idle;
}
static int aml_copy_from_user(void *to, const void *from, ulong n)
{
int ret =0;
if (likely(access_ok(from, n)))
ret = copy_from_user(to, from, n);
else
memcpy(to, from, n);
return ret;
}
static int stream_buffer_copy(struct stream_buf_s *stbuf, const u8 *buf, u32 size)
{
int ret = 0;
void *src = NULL, *dst = NULL;
int i, len;
for (i = 0; i < size; i += MAP_RANGE) {
len = ((size - i) > MAP_RANGE) ? MAP_RANGE : size - i;
src = stbuf->is_phybuf ?
codec_mm_vmap((ulong) buf + i, len) :
(void *) buf;
dst = codec_mm_vmap(stbuf->buf_wp + i, len);
if (!src || !dst) {
ret = -EFAULT;
pr_err("[%d]: %s, src or dst is invalid.\n",
stbuf->id, __func__);
goto err;
}
if (aml_copy_from_user(dst, src, len)) {
ret = -EAGAIN;
goto err;
}
codec_mm_dma_flush(dst, len, DMA_TO_DEVICE);
codec_mm_unmap_phyaddr(dst);
if (stbuf->is_phybuf)
codec_mm_unmap_phyaddr(src);
}
return 0;
err:
if (stbuf->is_phybuf && src)
codec_mm_unmap_phyaddr(src);
if (dst)
codec_mm_unmap_phyaddr(dst);
return ret;
}
static int rb_push_data(struct stream_buf_s *stbuf, const u8 *in, u32 size)
{
int ret, len;
u32 wp = stbuf->buf_wp;
u32 sp = (stbuf->buf_wp + size);
u32 ep = (stbuf->buf_start + stbuf->buf_size);
len = sp > ep ? ep - wp : size;
if (!stbuf->ext_buf_addr) {
ret = stream_buffer_copy(stbuf, in, len);
if (ret)
return ret;
}
stbuf->ops->set_wp(stbuf, (wp + len >= ep) ?
stbuf->buf_start : (wp + len));
if (stbuf->buf_wp == stbuf->buf_rp) {
pr_debug("[%d]: stream buffer is full, payload: %d\n",
stbuf->id, atomic_read(&stbuf->payload));
}
return len;
}
static int stream_buffer_write_inner(struct stream_buf_s *stbuf,
const u8 *in, u32 size)
{
if (in == NULL || size > stbuf->buf_size) {
pr_err("[%d]: params are not valid.\n", stbuf->id);
return -1;
}
if (get_free_space(stbuf) < size)
return -EAGAIN;
return rb_push_data(stbuf, in, size);
}
static u32 stream_buffer_get_wp(struct stream_buf_s *stbuf)
{
return stbuf->buf_wp;
}
static void stream_buffer_set_wp(struct stream_buf_s *stbuf, u32 val)
{
int len = (val >= stbuf->buf_wp) ? (val - stbuf->buf_wp) :
(stbuf->buf_size - stbuf->buf_wp + val);
stbuf->buf_wp = val;
vdec_set_vld_wp(container_of(stbuf, struct vdec_s, vbuf), stbuf->buf_wp);
atomic_add(len, &stbuf->payload);
}
static u32 stream_buffer_get_rp(struct stream_buf_s *stbuf)
{
return stbuf->buf_rp;
}
static void stream_buffer_set_rp(struct stream_buf_s *stbuf, u32 val)
{
int len = (val >= stbuf->buf_rp) ? (val - stbuf->buf_rp) :
(stbuf->buf_size - stbuf->buf_rp + val);
stbuf->buf_rp = val;
atomic_sub(len, &stbuf->payload);
}
static struct stream_buf_ops stream_buffer_ops = {
.init = stream_buffer_init,
.release = stream_buffer_release,
.write = stream_buffer_write_inner,
.get_wp = stream_buffer_get_wp,
.set_wp = stream_buffer_set_wp,
.get_rp = stream_buffer_get_rp,
.set_rp = stream_buffer_set_rp,
};
struct stream_buf_ops *get_stbuf_ops(void)
{
return &stream_buffer_ops;
}
EXPORT_SYMBOL(get_stbuf_ops);