| /* |
| * drivers/amlogic/media/video_sink/video_receiver.c |
| * |
| * Copyright (C) 2017 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. |
| * |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/fs.h> |
| #include <linux/string.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/err.h> |
| #include <linux/mutex.h> |
| #include <linux/ctype.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_VSYNC_RDMA |
| #include "../common/rdma/rdma.h" |
| #endif |
| #include <linux/amlogic/media/vfm/vframe.h> |
| #include <linux/amlogic/media/vfm/vframe_provider.h> |
| #include <linux/amlogic/media/vfm/vframe_receiver.h> |
| #include <linux/amlogic/media/video_sink/vpp.h> |
| #include <linux/amlogic/media/video_sink/video.h> |
| #include <linux/amlogic/media/amdolbyvision/dolby_vision.h> |
| #include <linux/amlogic/media/di/di.h> |
| |
| #include "video_receiver.h" |
| |
| /* #define ENABLE_DV */ |
| |
| /********************************************************* |
| * vframe APIs |
| *********************************************************/ |
| static inline struct vframe_s *common_vf_peek(struct video_recv_s *ins) |
| { |
| struct vframe_s *vf = NULL; |
| |
| if (!ins || !ins->recv_name || !ins->active) |
| return NULL; |
| |
| vf = vf_peek(ins->recv_name); |
| if (vf && vf->disp_pts && vf->disp_pts_us64) { |
| vf->pts = vf->disp_pts; |
| vf->pts_us64 = vf->disp_pts_us64; |
| vf->disp_pts = 0; |
| vf->disp_pts_us64 = 0; |
| } |
| return vf; |
| } |
| |
| static inline struct vframe_s *common_vf_get(struct video_recv_s *ins) |
| { |
| struct vframe_s *vf = NULL; |
| |
| if (!ins || !ins->recv_name || !ins->active) |
| return NULL; |
| |
| vf = vf_get(ins->recv_name); |
| |
| if (vf) { |
| if (vf->type & VIDTYPE_V4L_EOS) { |
| vf_put(vf, ins->recv_name); |
| return NULL; |
| } |
| if (vf->disp_pts && vf->disp_pts_us64) { |
| vf->pts = vf->disp_pts; |
| vf->pts_us64 = vf->disp_pts_us64; |
| vf->disp_pts = 0; |
| vf->disp_pts_us64 = 0; |
| } |
| ins->notify_flag |= VIDEO_NOTIFY_PROVIDER_GET; |
| } |
| return vf; |
| } |
| |
| #ifdef MORE_FUNCTION |
| static int common_vf_get_states( |
| struct video_recv_s *ins, |
| struct vframe_states *states) |
| { |
| int ret = -1; |
| unsigned long flags; |
| |
| if (!ins || !ins->recv_name || !ins->active || !states) |
| return ret; |
| |
| spin_lock_irqsave(&ins->lock, flags); |
| ret = vf_get_states_by_name(ins->recv_name, states); |
| spin_unlock_irqrestore(&ins->lock, flags); |
| return ret; |
| } |
| #endif |
| |
| static inline void common_vf_put( |
| struct video_recv_s *ins, |
| struct vframe_s *vf) |
| { |
| struct vframe_provider_s *vfp = NULL; |
| |
| if (!ins || !ins->recv_name) |
| return; |
| |
| vfp = vf_get_provider(ins->recv_name); |
| if (vfp && vf) { |
| vf_put(vf, ins->recv_name); |
| #ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION |
| if ((glayer_info[0].display_path_id == ins->path_id) && |
| is_dolby_vision_enable()) |
| dolby_vision_vf_put(vf); |
| #endif |
| ins->notify_flag |= VIDEO_NOTIFY_PROVIDER_PUT; |
| } |
| } |
| |
| /* TODO: need add keep frame function */ |
| static void common_vf_unreg_provider(struct video_recv_s *ins) |
| { |
| ulong flags; |
| bool layer1_used = false; |
| bool layer2_used = false; |
| int i; |
| bool wait = false; |
| |
| if (!ins) |
| return; |
| |
| /* FIXME: remove the global variable */ |
| atomic_inc(&video_unreg_flag); |
| while (atomic_read(&video_inirq_flag) > 0) |
| schedule(); |
| |
| spin_lock_irqsave(&ins->lock, flags); |
| ins->buf_to_put_num = 0; |
| for (i = 0; i < DISPBUF_TO_PUT_MAX; i++) |
| ins->buf_to_put[i] = NULL; |
| ins->rdma_buf = NULL; |
| ins->original_vf = NULL; |
| ins->switch_vf = false; |
| ins->last_switch_state = false; |
| |
| if (ins->cur_buf) { |
| ins->local_buf = *ins->cur_buf; |
| ins->cur_buf = &ins->local_buf; |
| } |
| spin_unlock_irqrestore(&ins->lock, flags); |
| |
| if (vd_layer[0].dispbuf_mapping |
| == &ins->cur_buf) { |
| layer1_used = true; |
| } |
| if (vd_layer[1].dispbuf_mapping |
| == &ins->cur_buf) { |
| layer2_used = true; |
| } |
| |
| if (!layer1_used && !layer2_used) { |
| ins->cur_buf = NULL; |
| } else { |
| ins->request_exit = true; |
| ins->do_exit = false; |
| ins->exited = false; |
| wait = true; |
| } |
| |
| if (layer1_used) |
| safe_switch_videolayer( |
| 0, false, true); |
| if (layer2_used) |
| safe_switch_videolayer( |
| 1, false, true); |
| |
| pr_info("common_vf_unreg_provider %s: vd1 used: %s, vd2 used: %s, black_out:%d, cur_buf:%p\n", |
| ins->recv_name, |
| layer1_used ? "true" : "false", |
| layer2_used ? "true" : "false", |
| ins->blackout | force_blackout, |
| ins->cur_buf); |
| |
| ins->active = false; |
| atomic_dec(&video_unreg_flag); |
| |
| while (wait && (!ins->exited || ins->request_exit)) |
| schedule(); |
| } |
| |
| static void common_vf_light_unreg_provider( |
| struct video_recv_s *ins) |
| { |
| ulong flags; |
| int i; |
| |
| if (!ins) |
| return; |
| |
| /* FIXME: remove the global variable */ |
| atomic_inc(&video_unreg_flag); |
| while (atomic_read(&video_inirq_flag) > 0) |
| schedule(); |
| |
| spin_lock_irqsave(&ins->lock, flags); |
| ins->buf_to_put_num = 0; |
| for (i = 0; i < DISPBUF_TO_PUT_MAX; i++) |
| ins->buf_to_put[i] = NULL; |
| ins->rdma_buf = NULL; |
| |
| if (ins->cur_buf) { |
| ins->local_buf = *ins->cur_buf; |
| ins->cur_buf = &ins->local_buf; |
| } |
| spin_unlock_irqrestore(&ins->lock, flags); |
| |
| atomic_dec(&video_unreg_flag); |
| } |
| |
| static int common_receiver_event_fun( |
| int type, void *data, void *private_data) |
| { |
| struct video_recv_s *ins = (struct video_recv_s *)private_data; |
| |
| if (!ins) { |
| pr_err("common_receiver_event: ins invalid, type:%d\n", type); |
| return -1; |
| } |
| if (type == VFRAME_EVENT_PROVIDER_UNREG) { |
| common_vf_unreg_provider(ins); |
| atomic_dec(&video_recv_cnt); |
| } else if (type == VFRAME_EVENT_PROVIDER_RESET) { |
| common_vf_light_unreg_provider(ins); |
| } else if (type == VFRAME_EVENT_PROVIDER_LIGHT_UNREG) { |
| common_vf_light_unreg_provider(ins); |
| } else if (type == VFRAME_EVENT_PROVIDER_REG) { |
| atomic_inc(&video_recv_cnt); |
| common_vf_light_unreg_provider(ins); |
| ins->drop_vf_cnt = 0; |
| ins->active = true; |
| ins->request_exit = false; |
| ins->do_exit = false; |
| ins->exited = false; |
| } |
| return 0; |
| } |
| |
| static inline int recycle_dvel_vf_put(void) |
| { |
| #define DVEL_RECV_NAME "dvel" |
| |
| int event = 0, ret = 0; |
| struct vframe_s *vf = NULL; |
| struct vframe_provider_s *vfp = vf_get_provider(DVEL_RECV_NAME); |
| |
| if (vfp) { |
| vf = vf_get(DVEL_RECV_NAME); |
| if (vf) { |
| vf_put(vf, DVEL_RECV_NAME); |
| event |= VFRAME_EVENT_RECEIVER_PUT; |
| vf_notify_provider(DVEL_RECV_NAME, event, NULL); |
| ret = 1; |
| } |
| } |
| return ret; |
| } |
| |
| static const struct vframe_receiver_op_s common_recv_func = { |
| .event_cb = common_receiver_event_fun |
| }; |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION |
| static int dolby_vision_need_wait_common(struct video_recv_s *ins) |
| { |
| struct vframe_s *vf; |
| |
| if (!is_dolby_vision_enable() || !ins) |
| return 0; |
| |
| vf = common_vf_peek(ins); |
| if (!vf || (dolby_vision_wait_metadata(vf) == 1)) |
| return 1; |
| return 0; |
| } |
| #endif |
| |
| static void common_toggle_frame( |
| struct video_recv_s *ins, struct vframe_s *vf) |
| { |
| if (!ins || !vf) |
| return; |
| |
| if ((vf->width == 0) || (vf->height == 0)) { |
| pr_err("common_toggle_frame %s: invalid frame dimension\n", |
| ins->recv_name); |
| return; |
| } |
| if (ins->cur_buf && |
| (ins->cur_buf != &ins->local_buf) && |
| (ins->original_vf != vf)) { |
| #ifdef CONFIG_AMLOGIC_MEDIA_VSYNC_RDMA |
| int i = 0; |
| |
| if (is_vsync_rdma_enable()) { |
| if (ins->rdma_buf == ins->cur_buf) { |
| if (ins->buf_to_put_num < DISPBUF_TO_PUT_MAX) { |
| ins->buf_to_put[ins->buf_to_put_num] = |
| ins->original_vf; |
| ins->buf_to_put_num++; |
| } else { |
| common_vf_put( |
| ins, ins->original_vf); |
| } |
| } else { |
| common_vf_put( |
| ins, ins->original_vf); |
| } |
| } else { |
| for (i = 0; i < ins->buf_to_put_num; i++) { |
| if (ins->buf_to_put[i]) { |
| common_vf_put( |
| ins, ins->buf_to_put[i]); |
| ins->buf_to_put[i] = NULL; |
| } |
| } |
| ins->buf_to_put_num = 0; |
| common_vf_put(ins, ins->original_vf); |
| } |
| #else |
| common_vf_put(ins, ins->original_vf); |
| #endif |
| ins->frame_count++; |
| } |
| if (ins->original_vf != vf) |
| vf->type_backup = vf->type; |
| ins->cur_buf = vf; |
| ins->original_vf = vf; |
| } |
| |
| /********************************************************* |
| * recv func APIs |
| *********************************************************/ |
| static s32 recv_common_early_process( |
| struct video_recv_s *ins, u32 op) |
| { |
| int i; |
| |
| if (!ins) { |
| pr_err("recv_common_early_process error, empty ins\n"); |
| return -1; |
| } |
| |
| /* not over vsync */ |
| if (!op) { |
| for (i = 0; i < ins->buf_to_put_num; i++) { |
| if (ins->buf_to_put[i]) { |
| ins->buf_to_put[i]->rendered = true; |
| common_vf_put( |
| ins, ins->buf_to_put[i]); |
| ins->buf_to_put[i] = NULL; |
| } |
| } |
| ins->buf_to_put_num = 0; |
| } |
| if (ins->do_exit) { |
| ins->exited = true; |
| ins->do_exit = false; |
| } |
| return 0; |
| } |
| |
| static struct vframe_s *recv_common_dequeue_frame( |
| struct video_recv_s *ins) |
| { |
| struct vframe_s *vf = NULL; |
| struct vframe_s *toggle_vf = NULL; |
| s32 drop_count = -1; |
| enum vframe_signal_fmt_e fmt; |
| |
| if (!ins) { |
| pr_err("recv_common_dequeue_frame error, empty ins\n"); |
| return NULL; |
| } |
| |
| if (ins->request_exit) { |
| ins->do_exit = true; |
| ins->exited = false; |
| ins->request_exit = false; |
| } |
| |
| vf = common_vf_peek(ins); |
| #ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION |
| if ((glayer_info[0].display_path_id == ins->path_id) && |
| is_dolby_vision_enable()) { |
| struct provider_aux_req_s req; |
| u32 i, bl_cnt = 0xffffffff; |
| |
| if (vf) { |
| dolby_vision_check_mvc(vf); |
| dolby_vision_check_hdr10(vf); |
| dolby_vision_check_hdr10plus(vf); |
| dolby_vision_check_hlg(vf); |
| } |
| |
| fmt = get_vframe_src_fmt(vf); |
| if (((fmt == VFRAME_SIGNAL_FMT_DOVI) || |
| (fmt == VFRAME_SIGNAL_FMT_INVALID)) && |
| vf_peek("dvel")) { |
| req.vf = NULL; |
| req.bot_flag = 0; |
| req.aux_buf = NULL; |
| req.aux_size = 0xffffffff; |
| req.dv_enhance_exist = 0; |
| vf_notify_provider_by_name( |
| "dvbldec", |
| VFRAME_EVENT_RECEIVER_GET_AUX_DATA, |
| (void *)&req); |
| bl_cnt = req.aux_size; |
| |
| req.vf = NULL; |
| req.bot_flag = 0; |
| req.aux_buf = NULL; |
| req.aux_size = 0xffffffff; |
| req.dv_enhance_exist = 0; |
| vf_notify_provider_by_name( |
| "dveldec", |
| VFRAME_EVENT_RECEIVER_GET_AUX_DATA, |
| (void *)&req); |
| if ((req.aux_size != 0xffffffff) && |
| (bl_cnt != 0xffffffff) && |
| (bl_cnt > req.aux_size)) { |
| i = bl_cnt - req.aux_size; |
| while (i > 0) { |
| if (!recycle_dvel_vf_put()) |
| break; |
| i--; |
| } |
| } |
| } |
| } |
| #endif |
| while (vf) { |
| if (!vf->frame_dirty) { |
| #ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION |
| if ((glayer_info[0].display_path_id == ins->path_id) && |
| dolby_vision_need_wait_common(ins)) |
| break; |
| #endif |
| vf = common_vf_get(ins); |
| if (vf) { |
| common_toggle_frame(ins, vf); |
| toggle_vf = vf; |
| #ifdef CONFIG_AMLOGIC_MEDIA_ENHANCEMENT_DOLBYVISION |
| if (glayer_info[0].display_path_id == |
| ins->path_id) |
| dvel_toggle_frame(vf, true); |
| #endif |
| } |
| } else { |
| vf = common_vf_get(ins); |
| if (vf) |
| common_vf_put(ins, vf); |
| } |
| drop_count++; |
| vf = common_vf_peek(ins); |
| } |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_DEINTERLACE |
| if (toggle_vf && |
| (toggle_vf->flag & VFRAME_FLAG_DOUBLE_FRAM) && |
| (glayer_info[0].display_path_id == ins->path_id)) { |
| if (toggle_vf->di_instance_id == di_api_get_instance_id()) { |
| if (ins->switch_vf) { |
| ins->switch_vf = false; |
| pr_info("set switch_vf false\n"); |
| } |
| } else { |
| if (ins->switch_vf == false) { |
| ins->switch_vf = true; |
| pr_info("set switch_vf true\n"); |
| } |
| } |
| } |
| #endif |
| |
| if (toggle_vf && (toggle_vf->flag & VFRAME_FLAG_DOUBLE_FRAM)) { |
| /* new frame */ |
| if (ins->switch_vf && toggle_vf->vf_ext) { |
| ins->cur_buf = toggle_vf->vf_ext; |
| toggle_vf = ins->cur_buf; |
| } |
| } else if (ins->cur_buf) { |
| /* repeat frame */ |
| if (ins->switch_vf && ins->cur_buf->vf_ext |
| && (ins->cur_buf->flag & VFRAME_FLAG_DOUBLE_FRAM)) { |
| ins->cur_buf = ins->cur_buf->vf_ext; |
| toggle_vf = ins->cur_buf; |
| } else if (!ins->switch_vf && |
| (ins->cur_buf != ins->original_vf)) { |
| ins->cur_buf = ins->original_vf; |
| toggle_vf = ins->cur_buf; |
| } |
| } |
| #ifdef CONFIG_AMLOGIC_MEDIA_DEINTERLACE |
| if (ins->switch_vf |
| && (ins->switch_vf != ins->last_switch_state)) { |
| di_api_post_disable(); |
| } |
| #endif |
| |
| ins->last_switch_state = ins->switch_vf; |
| return toggle_vf; |
| } |
| |
| static s32 recv_common_return_frame( |
| struct video_recv_s *ins, struct vframe_s *vf) |
| { |
| if (!ins || !vf) { |
| pr_err("recv_common_return_frame error, ins: %p, vf: %p\n", |
| ins, vf); |
| return -1; |
| } |
| |
| if ((vf == ins->cur_buf) && |
| (ins->cur_buf == &ins->local_buf)) { |
| pr_info("recv_common_return_frame skip the local vf =%p\n", |
| ins->original_vf); |
| return 0; |
| } |
| |
| if (vf == ins->cur_buf) { |
| common_vf_put(ins, ins->original_vf); |
| ins->cur_buf = NULL; |
| ins->original_vf = NULL; |
| pr_info("recv_common_return_frame force return the display vf: %p\n", |
| ins->original_vf); |
| } else { |
| common_vf_put(ins, vf); |
| } |
| return 0; |
| } |
| |
| static s32 recv_common_late_proc( |
| struct video_recv_s *ins) |
| { |
| if (!ins) { |
| pr_err("recv_common_late_proc error, empty ins\n"); |
| return -1; |
| } |
| #ifdef CONFIG_AMLOGIC_MEDIA_VSYNC_RDMA |
| ins->rdma_buf = ins->cur_buf; |
| #endif |
| return 0; |
| } |
| |
| const struct recv_func_s recv_common_ops = { |
| .early_proc = recv_common_early_process, |
| .dequeue_frame = recv_common_dequeue_frame, |
| .return_frame = recv_common_return_frame, |
| .late_proc = recv_common_late_proc, |
| }; |
| |
| struct video_recv_s *create_video_receiver(const char *recv_name, u8 path_id) |
| { |
| struct video_recv_s *ins = NULL; |
| |
| if (!recv_name) { |
| pr_err("create_video_receiver: recv_name is NULL.\n"); |
| goto CREATE_FAIL; |
| } |
| ins = kmalloc(sizeof(*ins), GFP_KERNEL); |
| if (!ins) |
| goto CREATE_FAIL; |
| |
| memset(ins, 0, sizeof(struct video_recv_s)); |
| ins->recv_name = (char *)recv_name; |
| ins->vf_ops = (struct vframe_receiver_op_s *)&common_recv_func; |
| ins->func = (struct recv_func_s *)&recv_common_ops; |
| ins->path_id = path_id; |
| spin_lock_init(&ins->lock); |
| vf_receiver_init( |
| &ins->handle, |
| ins->recv_name, |
| ins->vf_ops, ins); |
| if (vf_reg_receiver(&ins->handle)) { |
| pr_err( |
| "create_video_receiver %s: reg recv fail\n", |
| recv_name); |
| goto CREATE_FAIL; |
| } |
| pr_info( |
| "create_video_receiver %s %p, path_id:%d success\n", |
| recv_name, ins, ins->path_id); |
| return ins; |
| CREATE_FAIL: |
| kfree(ins); |
| return NULL; |
| } |
| |
| void destroy_video_receiver(struct video_recv_s *ins) |
| { |
| if (!ins) { |
| pr_err("destroy_video_receiver: ins is NULL.\n"); |
| return; |
| } |
| vf_unreg_receiver(&ins->handle); |
| kfree(ins); |
| pr_info("destroy_video_receiver\n"); |
| } |
| |
| void switch_vf(struct video_recv_s *ins, bool switch_flag) |
| { |
| if (!ins) { |
| pr_err("switch_vf: ins is NULL.\n"); |
| return; |
| } |
| ins->switch_vf = switch_flag; |
| pr_info("set switch_flag %d\n", switch_flag); |
| } |
| |