| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define DEBUG |
| |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/device.h> |
| #include <linux/module.h> |
| #include <linux/kfifo.h> |
| #include <linux/of_platform.h> |
| #include <linux/sync_file.h> |
| #include <linux/anon_inodes.h> |
| #include <linux/amlogic/major.h> |
| #include <linux/platform_device.h> |
| #include <linux/time.h> |
| #include <linux/compat.h> |
| #include <linux/delay.h> |
| #include <uapi/linux/sched/types.h> |
| #include <linux/amlogic/cpu_version.h> |
| #include <linux/amlogic/media/vfm/vframe_provider.h> |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| #include <linux/amlogic/media/video_sink/v4lvideo_ext.h> |
| #endif |
| #include <linux/amlogic/meson_uvm_core.h> |
| #include <linux/amlogic/media/codec_mm/codec_mm.h> |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| #include <linux/amlogic/media/video_sink/video.h> |
| #endif |
| |
| #include "../../common/vfm/vfm.h" |
| #include "videoqueue.h" |
| #include "../videotunnel/videotunnel.h" |
| |
| #define RECEIVER_NAME "videoqueue" |
| #define videoqueue_DEVICE_NAME "videoqueue" |
| |
| #define HDMI_DELAY_FIRST_CHECK_COUNT 60 |
| #define HDMI_DELAY_CHECK_INDEX 500 |
| |
| #define P_ERROR 0X0 |
| #define P_OTHER 0X1 |
| #define P_FENCE 0X2 |
| #define P_SYNC 0X4 |
| #define P_AVSYNC 0X8 |
| |
| #define UNDEQUEU_COUNT 4 |
| #define CHECK_DELAYE_COUNT 6 |
| |
| static unsigned int n_devs = 1; |
| static int print_close; |
| static int print_flag; |
| static int total_get_count; |
| static int total_put_count; |
| static int di_get_count; |
| static int di_put_count; |
| static int dump_index; |
| static int dump_index_last; |
| static u64 vframe_get_delay; |
| static int force_delay_ms; |
| static int game_mode; |
| static int force_game_mode; |
| static int fence_dq_count; |
| static int fence_put_count; |
| static int fence_null_count; |
| static int low_latency_mode = 1; |
| static int resync_open; |
| |
| int vq_print(int debug_flag, const char *fmt, ...) |
| { |
| if ((print_flag & debug_flag) || |
| debug_flag == P_ERROR) { |
| unsigned char buf[256]; |
| int len = 0; |
| va_list args; |
| |
| va_start(args, fmt); |
| len = sprintf(buf, "vq:"); |
| vsnprintf(buf + len, 256 - len, fmt, args); |
| pr_info("%s", buf); |
| va_end(args); |
| } |
| return 0; |
| } |
| |
| static void dump_vf(struct vframe_s *vf) |
| { |
| struct file *fp; |
| char name_buf[32]; |
| int write_size; |
| u8 *data; |
| mm_segment_t fs; |
| loff_t pos; |
| |
| if (!vf) |
| return; |
| |
| snprintf(name_buf, sizeof(name_buf), "/data/tmp/DI.bin"); |
| fp = filp_open(name_buf, O_CREAT | O_RDWR, 0644); |
| if (IS_ERR(fp)) |
| return; |
| write_size = vf->canvas0_config[0].width * vf->canvas0_config[0].height |
| * 2 * 10 / 8; |
| data = codec_mm_vmap(vf->canvas0_config[0].phy_addr, write_size); |
| if (!data) |
| return; |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| pos = 0; |
| vfs_write(fp, data, write_size, &pos); |
| vfs_fsync(fp, 0); |
| vq_print(P_OTHER, "nn: write %u size to addr%p\n", write_size, data); |
| codec_mm_unmap_phyaddr(data); |
| filp_close(fp, NULL); |
| set_fs(fs); |
| } |
| |
| static void dump_hf(struct vframe_s *vf) |
| { |
| struct hf_info_t *hf_info; |
| struct file *fp; |
| char name_buf[32]; |
| int write_size; |
| u8 *data; |
| mm_segment_t fs; |
| loff_t pos; |
| |
| hf_info = vf->hf_info; |
| |
| if (!hf_info) |
| return; |
| |
| snprintf(name_buf, sizeof(name_buf), "/data/tmp/hf.yuv"); |
| fp = filp_open(name_buf, O_CREAT | O_RDWR, 0644); |
| if (IS_ERR(fp)) |
| return; |
| write_size = hf_info->width * hf_info->height; |
| data = codec_mm_vmap(hf_info->phy_addr, write_size); |
| if (!data) |
| return; |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| pos = 0; |
| vfs_write(fp, data, write_size, &pos); |
| vfs_fsync(fp, 0); |
| vq_print(P_OTHER, "nn: write %u size to addr%p\n", write_size, data); |
| codec_mm_unmap_phyaddr(data); |
| filp_close(fp, NULL); |
| set_fs(fs); |
| } |
| |
| static u64 time_cur; |
| static u32 vsync_pts_inc; |
| static u64 vpp_vsync_us; |
| static u64 pcr_time; |
| static u64 pcr_time64; |
| static u32 pcr_margin; |
| static u32 delay_vsync; |
| static bool sync_start; |
| static u32 vsync_no; |
| static bool videoq_start; |
| static wait_queue_head_t file_wq; |
| static int wakeup; |
| |
| void vsync_notify_videoqueue(void) |
| { |
| time_cur = ktime_to_us(ktime_get()); |
| } |
| |
| void videoqueue_pcrscr_update(s32 inc, u32 base) |
| { |
| vsync_pts_inc = 90000 * inc / base; |
| vpp_vsync_us = vsync_pts_inc * 100 / 9; //us |
| pcr_margin = vsync_pts_inc / 4; |
| if (sync_start) { |
| pcr_time += vsync_pts_inc; |
| pcr_time64 += vpp_vsync_us; |
| vsync_no++; |
| vq_print(P_SYNC, "vsync: NO=%d, pcr_time=%lld, time=%lld\n", |
| vsync_no, pcr_time, time_cur); |
| } else { |
| pcr_time = 0; |
| pcr_time64 = 0; |
| vsync_no = 0; |
| } |
| if (videoq_start) { |
| wakeup = 1; |
| wake_up_interruptible(&file_wq); |
| } |
| } |
| |
| static inline int DUR2PTS(int x) |
| { |
| int var = x; |
| |
| var -= var >> 4; |
| return var; |
| } |
| |
| static DEFINE_SPINLOCK(devlist_lock); |
| static unsigned long videoqueue_devlist_lock(void) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&devlist_lock, flags); |
| return flags; |
| } |
| |
| static void videoqueue_devlist_unlock(unsigned long flags) |
| { |
| spin_unlock_irqrestore(&devlist_lock, flags); |
| } |
| |
| static LIST_HEAD(videoqueue_devlist); |
| |
| static void file_pop_display_q(struct video_queue_dev *dev, |
| struct file *disp_file) |
| { |
| struct file *file_tmp = NULL; |
| int k = kfifo_len(&dev->display_q); |
| |
| while (kfifo_len(&dev->display_q) > 0) { |
| if (kfifo_get(&dev->display_q, &file_tmp)) { |
| if (disp_file == file_tmp) |
| break; |
| if (!kfifo_put(&dev->display_q, file_tmp)) |
| vq_print(P_ERROR, "display_q is full!\n"); |
| } |
| k--; |
| if (k < 0) { |
| vq_print(P_ERROR, "can find vf in display_q\n"); |
| break; |
| } |
| } |
| } |
| |
| /* |
| static void videoq_hdmi_video_sync(struct video_queue_dev *dev, |
| struct vframe_s *vf) |
| { |
| u64 audio_need_delay; |
| |
| audio_need_delay = get_tvin_delay_duration(); |
| |
| if (audio_need_delay == 0) |
| return; |
| |
| vframe_get_delay = (int)div_u64(((jiffies_64 - |
| vf->ready_jiffies64) * 1000), HZ); |
| vframe_get_delay -= vf->duration / 96; |
| if (dev->check_sync_count == 0) |
| vq_print(P_ERROR, "audio need =%lld, first delay=%lld\n", |
| audio_need_delay, vframe_get_delay); |
| dev->check_sync_count++; |
| if (audio_need_delay * 1000 > vframe_get_delay * 1000 + |
| 3 * vpp_vsync_us + vpp_vsync_us / 2) { |
| vq_print(P_SYNC, "delay: audio need =%lld, delay=%lld\n", |
| audio_need_delay, vframe_get_delay); |
| dev->sync_need_delay = true; |
| return; |
| } |
| } |
| */ |
| |
| static void videoq_hdmi_video_sync_2(struct video_queue_dev *dev, |
| struct vframe_s *vf) |
| { |
| u64 audio_need_delay; |
| u64 vframe_delay; |
| s64 vdin_vsync; |
| int need_drop = 0; |
| s64 diff = 0; |
| u64 actual_delay = 0; |
| u64 frc_delay = 0; |
| u32 disp_delay_count; |
| |
| if (dev->low_latency_mode) |
| disp_delay_count = 2; |
| else |
| disp_delay_count = 3; |
| audio_need_delay = get_tvin_delay_duration(); |
| if (audio_need_delay == 0) |
| return; |
| |
| vdin_vsync = vf->duration / 96; |
| vframe_delay = (int)div_u64(((jiffies_64 - |
| vf->ready_jiffies64) * 1000), HZ); |
| vframe_delay -= vdin_vsync; |
| actual_delay = vframe_delay * 1000 + disp_delay_count * vpp_vsync_us; |
| #ifdef CONFIG_AMLOGIC_MEDIA_FRC |
| frc_delay = frc_get_video_latency(); |
| frc_delay = frc_delay * 1000; |
| #endif |
| actual_delay += frc_delay; |
| diff = actual_delay - audio_need_delay * 1000; |
| if (diff > vdin_vsync * 1000 / 2) { |
| diff = div_u64(diff, 1000); |
| need_drop = div_u64(diff + vdin_vsync / 2, vdin_vsync); |
| if (need_drop > 0) |
| dev->sync_need_drop = true; |
| vq_print(P_AVSYNC, "drop=%d,delay=%lld,actual=%lld,need=%lld\n", |
| need_drop, vframe_delay, |
| actual_delay, audio_need_delay); |
| dev->sync_need_drop_count = need_drop; |
| } else if (diff + vdin_vsync * 1000 / 2 < 0) { |
| vq_print(P_SYNC, "delay: audio need =%lld, delay=%lld\n", |
| audio_need_delay, vframe_delay); |
| dev->sync_need_delay = true; |
| vq_print(P_AVSYNC, "need delay=%lld,actual=%lld,need=%lld\n", |
| vframe_delay, actual_delay, audio_need_delay); |
| } |
| } |
| |
| static enum vframe_disp_mode_e vf_disp_mode_get(struct video_queue_dev *dev, |
| struct vframe_s *vf) |
| { |
| struct provider_disp_mode_req_s req; |
| |
| req.vf = vf; |
| req.disp_mode = VFRAME_DISP_MODE_NULL; |
| req.req_mode = 0; |
| |
| if (dev->provider_name) |
| vf_notify_provider_by_name(dev->provider_name, |
| VFRAME_EVENT_RECEIVER_DISP_MODE, (void *)&req); |
| return req.disp_mode; |
| } |
| |
| void videoqueue_drop_vf(struct video_queue_dev *dev) |
| { |
| struct vframe_s *vf; |
| int ret = 0; |
| |
| vf = vf_get(dev->vf_receiver_name); |
| if (!vf) |
| return; |
| ret = vf_put(vf, dev->vf_receiver_name); |
| if (ret) |
| vq_print(P_ERROR, "put: FAIL\n"); |
| dev->frame_num++; |
| } |
| |
| static void do_file_thread(struct video_queue_dev *dev) |
| { |
| struct vframe_s *vf; |
| int ret = 0; |
| struct file *ready_file; |
| struct file_private_data *private_data = NULL; |
| struct file *free_file, *fence_file; |
| int i = 0; |
| int dq_count = 0; |
| struct sync_file *sync_file = NULL; |
| u64 disp_time = 0; |
| u64 pts = 0; |
| u64 pts_tmp = 0; |
| u32 display_vsync_no = 0; |
| u32 vsync_diff = 0; |
| struct vframe_states states; |
| char *provider_name = NULL; |
| u32 vframe_disp_mode = VFRAME_DISP_MODE_NULL; |
| u32 vframe_walk_delay = 0; |
| u32 tvin_delay_duration = 0; |
| u64 avsync_diff = 0; |
| u32 frc_delay = 0; |
| bool vlock_locked = false; |
| bool need_resync = false; |
| u64 vdin_vsync = 0; |
| |
| if (!dev->provider_name) { |
| provider_name = vf_get_provider_name(dev->vf_receiver_name); |
| while (provider_name) { |
| if (!vf_get_provider_name(provider_name)) |
| break; |
| provider_name = |
| vf_get_provider_name(provider_name); |
| } |
| dev->provider_name = provider_name; |
| vq_print(P_SYNC, "videoqueue: provider name: %s\n", |
| dev->provider_name ? dev->provider_name : "NULL"); |
| } |
| |
| if (!kfifo_peek(&dev->file_q, &ready_file)) |
| return; |
| vf = vf_peek(dev->vf_receiver_name); |
| if (!vf && !dev->game_mode) { |
| if (dev->low_latency_mode) { |
| /*if do ai_sr, 6ms for ai_sr, 3ms software scheduling*/ |
| if (dev->need_aisr || !sync_start) |
| usleep_range(6000, 7000); |
| else |
| usleep_range(10000, 11000); |
| } else { |
| usleep_range(10000, 11000); |
| } |
| vf = vf_peek(dev->vf_receiver_name); |
| if (vf) |
| vq_print(P_SYNC, "peek 2 time ok, need_aisr=%d\n", |
| dev->need_aisr); |
| } |
| |
| if (!vf) { |
| vq_print(P_SYNC, "peek err\n"); |
| return; |
| } |
| |
| if (vf->width == 0 || vf->height == 0) { |
| vq_print(P_ERROR, "vframe param invalid.\n"); |
| vf_get(dev->vf_receiver_name); |
| vf_put(vf, dev->vf_receiver_name); |
| vf = vf_peek(dev->vf_receiver_name); |
| if (!vf) |
| return; |
| } |
| |
| if (dev->provider_name && !(strcmp(dev->provider_name, "dv_vdin"))) { |
| if (vf->dv_crc_sts) { |
| dev->vdin_err_crc_count = 0; |
| } else { |
| vq_print(P_ERROR, "invalid vframe.\n"); |
| vf_get(dev->vf_receiver_name); |
| dev->frame_num++; |
| vf_put(vf, dev->vf_receiver_name); |
| dev->vdin_err_crc_count++; |
| vq_print(P_ERROR, |
| "vdin_err_crc_count is %d.\n", |
| dev->vdin_err_crc_count); |
| if (dev->vdin_err_crc_count >= 6) { |
| ret = vt_send_cmd(dev->dev_session, |
| dev->tunnel_id, |
| VT_VIDEO_SET_STATUS, |
| 1); |
| if (ret < 0) |
| vq_print(P_ERROR, |
| "crc black screen err\n"); |
| } |
| |
| return; |
| } |
| } |
| |
| if ((vf->flag & VFRAME_FLAG_GAME_MODE || force_game_mode) && |
| !dev->game_mode) { |
| dev->game_mode = true; |
| game_mode = 1; |
| ret = vt_send_cmd(dev->dev_session, dev->tunnel_id, |
| VT_VIDEO_SET_GAME_MODE, 1); |
| vq_print(P_ERROR, "set game mode true\n"); |
| if (ret < 0) |
| vq_print(P_ERROR, "set game mode true err\n"); |
| } else if (dev->low_latency_mode && !sync_start) { |
| ret = vt_send_cmd(dev->dev_session, dev->tunnel_id, |
| VT_VIDEO_SET_GAME_MODE, 1); |
| if (ret < 0) |
| vq_print(P_ERROR, "set game mode true err\n"); |
| } |
| |
| dev->sync_need_delay = false; |
| dev->sync_need_drop = false; |
| dev->sync_need_drop_count = 0; |
| |
| if (dev->frame_num == HDMI_DELAY_FIRST_CHECK_COUNT || |
| dev->frame_num == 1) |
| dev->need_check_delay_count = CHECK_DELAYE_COUNT; |
| if (get_tvin_delay_start()) { |
| vq_print(P_ERROR, "tvin delay start\n"); |
| dev->need_check_delay_count = CHECK_DELAYE_COUNT; |
| set_tvin_delay_start(0); |
| } |
| |
| vlock_locked = vlock_get_phlock_flag() && vlock_get_vlock_flag(); |
| |
| if (vlock_locked && !dev->vlock_locked) { |
| need_resync = true; |
| vq_print(P_OTHER, "vlock locked\n"); |
| } else if (resync_open) { |
| need_resync = true; |
| } |
| |
| if (dev->frame_num > HDMI_DELAY_CHECK_INDEX && |
| dev->need_check_delay_count == 0 && |
| need_resync) { |
| tvin_delay_duration = get_tvin_delay_duration(); |
| vframe_walk_delay = get_tvin_delay(); |
| avsync_diff = abs(tvin_delay_duration - vframe_walk_delay); |
| |
| vdin_vsync = vf->duration; |
| vdin_vsync = vdin_vsync * 1000; |
| vdin_vsync = div64_u64(vdin_vsync, 96); |
| |
| if (avsync_diff * 1000 > vdin_vsync * 3 / 4) { |
| #ifdef CONFIG_AMLOGIC_MEDIA_VDIN |
| frc_delay = get_vdin_add_delay_num(); |
| #endif |
| vq_print(P_OTHER, "resync: frc1:%d,frc2:%d\n", |
| dev->frc_delay_first_frame, frc_delay); |
| |
| if (frc_delay == dev->frc_delay_first_frame) { |
| vq_print(P_ERROR, |
| "resync:need=%d,actual=%d,diff=%lld\n", |
| tvin_delay_duration, vframe_walk_delay, |
| avsync_diff); |
| dev->need_check_delay_count = |
| CHECK_DELAYE_COUNT; |
| } |
| } |
| } |
| dev->vlock_locked = vlock_locked; |
| /*step 1: audio required*/ |
| if (!sync_start) { |
| vq_print(P_ERROR, "queue first frame frc=%d\n", |
| dev->frc_delay_first_frame); |
| } else if (dev->need_check_delay_count > 0) { |
| /*step 2: recheck video sync after hdmi-in start*/ |
| videoq_hdmi_video_sync_2(dev, vf); |
| dev->need_check_delay_count--; |
| } |
| |
| if (dev->sync_need_delay) |
| return; |
| |
| vf = vf_peek(dev->vf_receiver_name); |
| if (!vf) |
| return; |
| |
| vframe_disp_mode = vf_disp_mode_get(dev, vf); |
| if (vframe_disp_mode != VFRAME_DISP_MODE_OK && |
| vframe_disp_mode != VFRAME_DISP_MODE_NULL) |
| vq_print(P_OTHER, "disp_mode =%d\n", vframe_disp_mode); |
| |
| if (vframe_disp_mode == VFRAME_DISP_MODE_SKIP) { |
| vq_print(P_ERROR, "SKIP\n"); |
| videoqueue_drop_vf(dev); |
| return; |
| } |
| |
| if (!dev->game_mode && !sync_start) { |
| if (dev->delay_vsync_count > 0) { |
| dev->delay_vsync_count--; |
| vq_print(P_SYNC, "delay one vsync\n"); |
| return; |
| } |
| } |
| |
| if (!sync_start) { |
| pcr_time = pts; |
| sync_start = true; |
| #ifdef CONFIG_AMLOGIC_MEDIA_VDIN |
| dev->frc_delay_first_frame = get_vdin_add_delay_num(); |
| #endif |
| } |
| |
| pts = vf->pts; |
| if (pts == 0 && dev->frame_num != 0) |
| pts = dev->pts_last + DUR2PTS(vf->duration); |
| |
| disp_time = 0; |
| display_vsync_no = 0; |
| |
| if (dev->low_latency_mode && !dev->game_mode) { |
| if (pts + pcr_margin <= pcr_time) { |
| vq_print(P_ERROR, |
| "delay pts= %lld, pcr=%lld,omx_index=%d\n", |
| pts, pcr_time, dev->frame_num); |
| pcr_time = pts; |
| } |
| pts_tmp = pts + pcr_margin; |
| if (pts_tmp > pcr_time && |
| (pts_tmp < pcr_time + vsync_pts_inc)) { |
| display_vsync_no = vsync_no; |
| vq_print(P_SYNC, |
| "vsync=%d, pts=%lld, omx_index=%d\n", |
| display_vsync_no, |
| pts, dev->frame_num); |
| } else if (pts_tmp >= pcr_time + vsync_pts_inc) { |
| vq_print(P_SYNC, "early continue\n"); |
| return; |
| } |
| } |
| |
| if (!kfifo_get(&dev->file_q, &ready_file)) { |
| pr_info("task: get failed\n"); |
| return; |
| } |
| private_data = v4lvideo_get_file_private_data(ready_file, true); |
| vf = vf_get(dev->vf_receiver_name); |
| if (!vf) |
| return; |
| if (vf->hf_info) |
| dev->need_aisr = true; |
| else |
| dev->need_aisr = false; |
| |
| vframe_get_delay = (int)div_u64(((jiffies_64 - |
| vf->ready_jiffies64) * 1000), HZ); |
| vframe_get_delay -= vf->duration / 96; |
| dev->pts_last = pts; |
| vf_get_states_by_name(dev->vf_receiver_name, &states); |
| |
| total_get_count++; |
| if (vf->type & VIDTYPE_DI_PW) |
| di_get_count++; |
| |
| vf->omx_index = dev->frame_num++; |
| |
| if (dump_index != dump_index_last) { |
| dump_index_last = dump_index; |
| dump_vf(vf); |
| dump_hf(vf); |
| } |
| vq_print(P_OTHER, "get: omx_index=%d, buf_avail_num=%d,delay=%lld", |
| vf->omx_index, states.buf_avail_num, vframe_get_delay); |
| private_data->vf = *vf; |
| private_data->vf_p = vf; |
| if (dev->game_mode) { |
| private_data->vf.timestamp = ktime_to_us(ktime_get()); |
| private_data->vf.disp_pts_us64 = dev->ready_time; |
| } |
| |
| #ifdef COPY_META_DATA |
| v4lvideo_import_sei_data(vf, &private_data->vf, dev->provider_name); |
| #endif |
| |
| vsync_diff = display_vsync_no - vf->omx_index; |
| vq_print(P_SYNC, "dis_vsync=%d, omx_index=%d, diff=%d\n", |
| display_vsync_no, vf->omx_index, |
| vsync_diff); |
| if (dev->last_vsync_diff != vsync_diff) { |
| vq_print(P_SYNC, "omx_index=%d display_vsync=%d diff=%d\n", |
| vf->omx_index, display_vsync_no, vsync_diff); |
| dev->last_vsync_diff = vsync_diff; |
| } |
| |
| ret = vt_queue_buffer(dev->dev_session, |
| dev->tunnel_id, ready_file, -1, disp_time); |
| if (ret < 0) { |
| if (ret != -ENOTCONN) |
| pr_err("vt queue buffer error\n"); |
| else |
| vq_print(P_OTHER, "no consumer\n"); |
| total_put_count++; |
| if (vf->type & VIDTYPE_DI_PW) |
| di_put_count++; |
| vq_print(P_OTHER, "put: omx_index=%d\n", vf->omx_index); |
| ret = vf_put(vf, dev->vf_receiver_name); |
| if (ret) { |
| vq_print(P_ERROR, "put: FAIL\n"); |
| if (vf->type & VIDTYPE_DI_PW) |
| dim_post_keep_cmd_release2(vf); |
| } |
| if (!kfifo_put(&dev->file_q, ready_file)) |
| pr_err("queue error but file_q is full\n"); |
| return; |
| } |
| |
| if (!kfifo_put(&dev->display_q, ready_file)) |
| pr_err("queue error but display_q is full\n"); |
| dev->queue_count++; |
| |
| vq_print(P_OTHER, "q buf: omx_index=%d, queue_count=%d\n", |
| vf->omx_index, dev->queue_count); |
| |
| dq_count = 0; |
| while (1) { |
| if (dev->queue_count <= FILE_CNT - UNDEQUEU_COUNT) |
| break; |
| |
| if (kthread_should_stop()) |
| break; |
| |
| ret = vt_dequeue_buffer(dev->dev_session, dev->tunnel_id, |
| &free_file, &fence_file); |
| if (ret != 0) { |
| vq_print(P_OTHER, "dequeue fail\n"); |
| usleep_range(3000, 4000); |
| dq_count++; |
| continue; |
| } |
| |
| if (!IS_ERR_OR_NULL(fence_file)) { |
| fence_dq_count++; |
| sync_file = |
| (struct sync_file *)fence_file->private_data; |
| } else { |
| fence_null_count++; |
| } |
| vq_print(P_FENCE, "dq: free_file=%px, fence_file=%px\n", |
| free_file, fence_file); |
| file_pop_display_q(dev, free_file); |
| dev->dq_count++; |
| private_data = v4lvideo_get_file_private_data(free_file, true); |
| vf = private_data->vf_p; |
| vq_print(P_OTHER, "dq: omx_index=%d,q_count=%d,dq_count=%d\n", |
| vf->omx_index, dev->queue_count, dev->dq_count); |
| for (i = 0; i < FILE_CNT; i++) { |
| if (!dev->dq_info[i].used) |
| break; |
| } |
| if (i == FILE_CNT) { |
| vq_print(P_ERROR, "dq_info q is empty!\n"); |
| i = 0; |
| } |
| |
| dev->dq_info[i].free_file = free_file; |
| dev->dq_info[i].fence_file = fence_file; |
| dev->dq_info[i].used = true; |
| if (!kfifo_put(&dev->dq_info_q, &dev->dq_info[i])) |
| vq_print(P_ERROR, "queue error, fence_q is full\n"); |
| wake_up_interruptible(&dev->fence_wq); |
| break; |
| } |
| if (dq_count) |
| vq_print(P_OTHER, "dequeue count = %d\n", dq_count); |
| |
| while (dev->sync_need_drop && dev->sync_need_drop_count) { |
| videoqueue_drop_vf(dev); |
| vq_print(P_AVSYNC, "drop omx_index=%d\n", dev->frame_num); |
| dev->sync_need_drop_count--; |
| } |
| } |
| |
| static void do_fence_thread(struct video_queue_dev *dev) |
| { |
| int ret; |
| struct sync_file *sync_file = NULL; |
| struct file *free_file, *fence_file; |
| struct dma_fence *fence_obj = NULL; |
| struct file_private_data *private_data = NULL; |
| struct dequeu_info *dq_info = NULL; |
| struct vframe_s *vf; |
| |
| if (!kfifo_get(&dev->dq_info_q, &dq_info)) { |
| vq_print(P_OTHER, "get fence fail\n"); |
| return; |
| } |
| free_file = dq_info->free_file; |
| fence_file = dq_info->fence_file; |
| dq_info->used = false; |
| |
| sync_file = NULL; |
| fence_obj = NULL; |
| if (!IS_ERR_OR_NULL(fence_file)) |
| sync_file = (struct sync_file *)fence_file->private_data; |
| else |
| vq_print(P_ERROR, "fence_file is NULL\n"); |
| |
| if (!IS_ERR_OR_NULL(sync_file)) |
| fence_obj = sync_file->fence; |
| else |
| vq_print(P_ERROR, "sync_file is NULL\n"); |
| |
| if (fence_obj) { |
| vq_print(P_FENCE, "sync_file=%px, seqno=%lld\n", |
| sync_file, fence_obj->seqno); |
| ret = dma_fence_wait_timeout(fence_obj, |
| false, 1000); |
| if (ret == 0) |
| vq_print(P_ERROR, "fence timeout\n"); |
| } |
| |
| if (!IS_ERR_OR_NULL(fence_file)) { |
| fput(fence_file); |
| fence_put_count++; |
| } |
| |
| private_data = v4lvideo_get_file_private_data(free_file, true); |
| if (private_data) { |
| #ifdef COPY_META_DATA |
| v4lvideo_release_sei_data(&private_data->vf); |
| #endif |
| vf = private_data->vf_p; |
| if (vf) { |
| vq_print(P_OTHER, "put: omx_index=%d\n", |
| vf->omx_index); |
| total_put_count++; |
| if (vf->type & VIDTYPE_DI_PW) |
| di_put_count++; |
| ret = vf_put(vf, dev->vf_receiver_name); |
| if (ret) { |
| vq_print(P_ERROR, "put: FAIL\n"); |
| if (vf->type & VIDTYPE_DI_PW) |
| dim_post_keep_cmd_release2(vf); |
| } |
| } else { |
| pr_err("private_data vf null\n"); |
| } |
| init_file_private_data(private_data); |
| } else { |
| pr_err("private_data null"); |
| } |
| if (!kfifo_put(&dev->file_q, free_file)) |
| pr_err("queue error but file_q is full\n"); |
| } |
| |
| static int vq_file_thread(void *data) |
| { |
| struct video_queue_dev *dev = data; |
| struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1}; |
| |
| sched_setscheduler(current, SCHED_FIFO, ¶m); |
| dev->delay_vsync_count = delay_vsync; |
| |
| while (1) { |
| if (kthread_should_stop()) |
| break; |
| if (dev->thread_need_stop) { |
| usleep_range(1000, 2000); |
| continue; |
| } |
| wait_event_interruptible_timeout(file_wq, |
| wakeup | dev->thread_need_stop, |
| msecs_to_jiffies(50)); |
| wakeup = 0; |
| do_file_thread(dev); |
| } |
| |
| complete(&dev->file_thread_done); |
| return 0; |
| } |
| |
| static int vq_fence_thread(void *data) |
| { |
| struct video_queue_dev *dev = data; |
| struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1}; |
| |
| sched_setscheduler(current, SCHED_FIFO, ¶m); |
| |
| while (1) { |
| if (kthread_should_stop()) |
| break; |
| if (dev->thread_need_stop) { |
| usleep_range(1000, 2000); |
| continue; |
| } |
| wait_event_interruptible_timeout(dev->fence_wq, |
| kfifo_len(&dev->dq_info_q) > 0 || |
| dev->thread_need_stop, |
| msecs_to_jiffies(200)); |
| do_fence_thread(dev); |
| } |
| |
| complete(&dev->fence_thread_done); |
| return 0; |
| } |
| |
| static int init_vt_config(struct video_queue_dev *dev) |
| { |
| int ret = -1; |
| |
| vq_print(P_OTHER, "init vt %s id=%d\n", |
| dev->vf_receiver_name, dev->tunnel_id); |
| |
| dev->dev_session = vt_session_create(dev->vf_receiver_name); |
| if (IS_ERR_OR_NULL(dev->dev_session)) { |
| pr_err("%s create session fail\n", dev->vf_receiver_name); |
| return ret; |
| } |
| |
| ret = vt_producer_connect(dev->dev_session, dev->tunnel_id); |
| if (ret < 0) |
| pr_err("%s connect producer fail\n", dev->vf_receiver_name); |
| return ret; |
| } |
| |
| static int destroy_vt_config(struct video_queue_dev *dev) |
| { |
| int ret = -1; |
| |
| ret = vt_producer_disconnect(dev->dev_session, dev->tunnel_id); |
| if (ret < 0) { |
| pr_err("%s disconnect producer fail\n", dev->vf_receiver_name); |
| return ret; |
| } |
| |
| vt_session_destroy(dev->dev_session); |
| return ret; |
| } |
| |
| static void videoq_notify_to_amvideo(bool reg) |
| { |
| u32 para[2]; |
| |
| para[0] = 0; |
| para[1] = reg; |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| amvideo_notifier_call_chain(AMVIDEO_UPDATE_VT_REG, |
| (void *)¶[0]); |
| #endif |
| } |
| |
| static int videoqueue_reg_provider(struct video_queue_dev *dev) |
| { |
| int ret; |
| int i; |
| struct dma_buf *dmabuf; |
| //struct file *file = NULL; |
| |
| sync_start = false; |
| dev->pts_last = 0; |
| |
| dev->queue_count = 0; |
| dev->dq_count = 0; |
| dev->frame_num = 0; |
| dev->tunnel_id = 0; |
| dev->check_sync_count = 0; |
| dev->provider_name = NULL; |
| dev->need_check_delay_count = 0; |
| dev->frc_delay_first_frame = 0; |
| dev->vlock_locked = false; |
| vframe_get_delay = 0; |
| dev->game_mode = false; |
| game_mode = 0; |
| dev->low_latency_mode = low_latency_mode; |
| dev->vdin_err_crc_count = 0; |
| |
| INIT_KFIFO(dev->file_q); |
| INIT_KFIFO(dev->display_q); |
| INIT_KFIFO(dev->dq_info_q); |
| kfifo_reset(&dev->file_q); |
| kfifo_reset(&dev->display_q); |
| kfifo_reset(&dev->dq_info_q); |
| init_completion(&dev->file_thread_done); |
| init_completion(&dev->fence_thread_done); |
| |
| for (i = 0; i < FILE_CNT; i++) { |
| dev->dq_info[i].index = i; |
| dev->dq_info[i].used = false; |
| dev->dq_info[i].free_file = NULL; |
| dev->dq_info[i].fence_file = NULL; |
| } |
| |
| ret = init_vt_config(dev); |
| if (ret < 0) |
| return ret; |
| for (i = 0; i < FILE_CNT; i++) { |
| dmabuf = uvm_alloc_dmabuf(SZ_4K, 0, 0); |
| if (!dmabuf_is_uvm(dmabuf)) |
| vq_print(P_ERROR, "dmabuf is not uvm\n"); |
| vq_print(P_OTHER, "dmabuf is uvm:dmabuf=%px\n", dmabuf); |
| |
| dev->dev_file[i] = dmabuf->file; |
| dev->dmabuf[i] = dmabuf; |
| if (!kfifo_put(&dev->file_q, dev->dev_file[i])) |
| pr_info("%s file_q is full\n", dev->vf_receiver_name); |
| } |
| |
| init_waitqueue_head(&dev->fence_wq); |
| |
| dev->thread_need_stop = false; |
| |
| dev->file_thread = kthread_create(vq_file_thread, |
| dev, dev->vf_receiver_name); |
| dev->fence_thread = kthread_create(vq_fence_thread, |
| dev, dev->vf_receiver_name); |
| videoq_notify_to_amvideo(true); |
| |
| return ret; |
| } |
| |
| static int videoqueue_unreg_provider(struct video_queue_dev *dev) |
| { |
| int ret = -1; |
| int i; |
| struct file *disp_file = NULL; |
| struct dequeu_info *dq_info = NULL; |
| struct file *free_file, *fence_file; |
| int time_left = 0; |
| |
| vq_print(P_ERROR, "unreg: in\n"); |
| |
| dev->thread_need_stop = true; |
| |
| wake_up_interruptible(&dev->fence_wq); |
| wakeup = 1; |
| wake_up_interruptible(&file_wq); |
| |
| videoq_notify_to_amvideo(false); |
| |
| if (dev->file_thread) |
| kthread_stop(dev->file_thread); |
| |
| if (dev->fence_thread) |
| kthread_stop(dev->fence_thread); |
| |
| time_left = wait_for_completion_timeout(&dev->file_thread_done, |
| msecs_to_jiffies(500)); |
| if (!time_left) |
| vq_print(P_ERROR, "unreg:wait file_thread timeout\n"); |
| else if (time_left < 100) |
| vq_print(P_ERROR, |
| "unreg:wait file_thread time %d\n", time_left); |
| dev->file_thread = NULL; |
| |
| time_left = wait_for_completion_timeout(&dev->fence_thread_done, |
| msecs_to_jiffies(500)); |
| if (!time_left) |
| vq_print(P_ERROR, "unreg:wait fence_thread timeout\n"); |
| else if (time_left < 100) |
| vq_print(P_ERROR, |
| "unreg:wait fence_thread time %d\n", time_left); |
| dev->fence_thread = NULL; |
| |
| if (dev->game_mode || dev->low_latency_mode) { |
| ret = vt_send_cmd(dev->dev_session, dev->tunnel_id, |
| VT_VIDEO_SET_GAME_MODE, 0); |
| vq_print(P_ERROR, "set no game mode\n"); |
| dev->game_mode = false; |
| if (ret < 0) |
| vq_print(P_ERROR, "set game mode false err\n"); |
| } |
| |
| ret = destroy_vt_config(dev); |
| if (ret < 0) |
| return ret; |
| |
| while (kfifo_len(&dev->display_q) > 0) { |
| if (kfifo_get(&dev->display_q, &disp_file)) { |
| vq_print(P_OTHER, "unreg: disp_list keep vf\n"); |
| if (disp_file) |
| v4lvideo_keep_vf(disp_file); |
| } |
| } |
| |
| while (kfifo_len(&dev->dq_info_q) > 0) { |
| if (kfifo_get(&dev->dq_info_q, &dq_info)) { |
| free_file = dq_info->free_file; |
| fence_file = dq_info->fence_file; |
| vq_print(P_ERROR, "unreg: dq_info_q keep vf\n"); |
| if (free_file) |
| v4lvideo_keep_vf(free_file); |
| if (!IS_ERR_OR_NULL(fence_file)) { |
| fput(fence_file); |
| fence_put_count++; |
| } |
| } |
| } |
| |
| for (i = 0; i < FILE_CNT; i++) { |
| if (dev->dmabuf[i]) |
| dma_buf_put(dev->dmabuf[i]); |
| else |
| vq_print(P_ERROR, "unreg: dmabuf is NULL\n"); |
| dev->dmabuf[i] = NULL; |
| } |
| |
| vq_print(P_ERROR, "total get=%d put=%d, di_get=%d, di_put=%d\n", |
| total_get_count, total_put_count, |
| di_get_count, di_put_count); |
| vq_print(P_ERROR, "unreg: out fence_dq=%d, fence_put=%d\n", |
| fence_dq_count, fence_put_count); |
| |
| sync_start = false; |
| game_mode = 0; |
| dev->game_mode = false; |
| |
| return ret; |
| } |
| |
| static void videoqueue_start(struct video_queue_dev *dev) |
| { |
| wake_up_process(dev->file_thread); |
| wake_up_process(dev->fence_thread); |
| } |
| |
| static int video_receiver_event_fun(int type, void *data, |
| void *private_data) |
| { |
| struct video_queue_dev *dev = (struct video_queue_dev *)private_data; |
| //struct file_private_data *file_private_data = NULL; |
| int ret = 0; |
| |
| switch (type) { |
| case VFRAME_EVENT_PROVIDER_UNREG: |
| case VFRAME_EVENT_PROVIDER_LIGHT_UNREG: |
| videoqueue_unreg_provider(dev); |
| pr_info("%s unreg end!!\n", dev->vf_receiver_name); |
| break; |
| case VFRAME_EVENT_PROVIDER_REG: |
| videoqueue_reg_provider(dev); |
| pr_info("%s reg end!!\n", dev->vf_receiver_name); |
| break; |
| case VFRAME_EVENT_PROVIDER_QUREY_STATE: |
| ret = RECEIVER_ACTIVE; |
| break; |
| case VFRAME_EVENT_PROVIDER_START: |
| videoqueue_start(dev); |
| break; |
| case VFRAME_EVENT_PROVIDER_VFRAME_READY: |
| if (dev->game_mode) { |
| dev->ready_time = ktime_to_us(ktime_get()); |
| vq_print(P_SYNC, "VFRAME_READY\n"); |
| wakeup = 1; |
| wake_up_interruptible(&file_wq); |
| vq_print(P_SYNC, "VFRAME_READY_1\n"); |
| } |
| break; |
| case VFRAME_EVENT_PROVIDER_RESET: |
| videoqueue_unreg_provider(dev); |
| videoqueue_reg_provider(dev); |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static const struct vframe_receiver_op_s video_vf_receiver = { |
| .event_cb = video_receiver_event_fun |
| }; |
| |
| static ssize_t print_close_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current print_close is %d\n", |
| print_close); |
| } |
| |
| static ssize_t print_close_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| print_close = tmp; |
| return count; |
| } |
| |
| static ssize_t print_flag_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current print_flag is %d\n", |
| print_flag); |
| } |
| |
| static ssize_t print_flag_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| print_flag = tmp; |
| return count; |
| } |
| |
| static ssize_t buf_count_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "total_get=%d total_pu=%d, di_get=%d, di_pu=%d\n", |
| total_get_count, total_put_count, |
| di_get_count, di_put_count); |
| } |
| |
| static ssize_t dump_index_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current dump_index is %d\n", |
| dump_index); |
| } |
| |
| static ssize_t dump_index_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| dump_index = tmp; |
| return count; |
| } |
| |
| static ssize_t delay_vsync_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current delay_vsync is %d\n", |
| delay_vsync); |
| } |
| |
| static ssize_t delay_vsync_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| delay_vsync = tmp; |
| return count; |
| } |
| |
| static ssize_t vframe_get_delay_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current vframe_get_delay is %lld\n", |
| vframe_get_delay); |
| } |
| |
| static ssize_t force_delay_ms_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current force_delay_ms is %d\n", |
| force_delay_ms); |
| } |
| |
| static ssize_t force_delay_ms_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| force_delay_ms = tmp; |
| return count; |
| } |
| |
| static ssize_t game_mode_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current game_mode is %d\n", |
| game_mode); |
| } |
| |
| static ssize_t game_mode_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| game_mode = tmp; |
| return count; |
| } |
| |
| static ssize_t force_game_mode_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current force_game_mode is %d\n", |
| force_game_mode); |
| } |
| |
| static ssize_t force_game_mode_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| force_game_mode = tmp; |
| return count; |
| } |
| |
| static ssize_t fence_dq_count_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current fence_dq_count is %d\n", |
| fence_dq_count); |
| } |
| |
| static ssize_t fence_put_count_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current fence_put_count is %d\n", |
| fence_put_count); |
| } |
| |
| static ssize_t fence_null_count_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current fence_null_count is %d\n", |
| fence_null_count); |
| } |
| |
| static ssize_t resync_open_show(struct class *cla, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 80, |
| "current resync_open is %d\n", |
| resync_open); |
| } |
| |
| static ssize_t resync_open_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long tmp; |
| int ret; |
| |
| ret = kstrtol(buf, 0, &tmp); |
| if (ret != 0) { |
| pr_info("ERROR converting %s to long int!\n", buf); |
| return ret; |
| } |
| resync_open = tmp; |
| return count; |
| } |
| |
| static CLASS_ATTR_RW(print_close); |
| static CLASS_ATTR_RW(print_flag); |
| static CLASS_ATTR_RO(buf_count); |
| static CLASS_ATTR_RW(dump_index); |
| static CLASS_ATTR_RW(delay_vsync); |
| static CLASS_ATTR_RO(vframe_get_delay); |
| static CLASS_ATTR_RW(force_delay_ms); |
| static CLASS_ATTR_RW(game_mode); |
| static CLASS_ATTR_RW(force_game_mode); |
| static CLASS_ATTR_RO(fence_dq_count); |
| static CLASS_ATTR_RO(fence_put_count); |
| static CLASS_ATTR_RO(fence_null_count); |
| static CLASS_ATTR_RW(resync_open); |
| |
| static struct attribute *videoqueue_class_attrs[] = { |
| &class_attr_print_close.attr, |
| &class_attr_print_flag.attr, |
| &class_attr_buf_count.attr, |
| &class_attr_dump_index.attr, |
| &class_attr_delay_vsync.attr, |
| &class_attr_vframe_get_delay.attr, |
| &class_attr_force_delay_ms.attr, |
| &class_attr_game_mode.attr, |
| &class_attr_force_game_mode.attr, |
| &class_attr_fence_dq_count.attr, |
| &class_attr_fence_put_count.attr, |
| &class_attr_fence_null_count.attr, |
| &class_attr_resync_open.attr, |
| NULL |
| }; |
| |
| ATTRIBUTE_GROUPS(videoqueue_class); |
| |
| static struct class videoqueue_class = { |
| .name = "videoqueue", |
| .class_groups = videoqueue_class_groups, |
| }; |
| |
| static int videoqueue_open(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| static int videoqueue_release(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| int videoqueue_assign_map(char **receiver_name, int *inst) |
| { |
| unsigned long flags; |
| struct video_queue_dev *dev = NULL; |
| struct list_head *p; |
| |
| flags = videoqueue_devlist_lock(); |
| |
| list_for_each(p, &videoqueue_devlist) { |
| dev = list_entry(p, struct video_queue_dev, |
| videoqueue_devlist); |
| |
| if (dev->inst == *inst) { |
| *receiver_name = dev->vf_receiver_name; |
| videoqueue_devlist_unlock(flags); |
| return 0; |
| } |
| } |
| |
| videoqueue_devlist_unlock(flags); |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL(videoqueue_assign_map); |
| |
| int videoqueue_alloc_map(int *inst) |
| { |
| unsigned long flags; |
| struct video_queue_dev *dev = NULL; |
| struct list_head *p; |
| |
| flags = videoqueue_devlist_lock(); |
| |
| list_for_each(p, &videoqueue_devlist) { |
| dev = list_entry(p, struct video_queue_dev, |
| videoqueue_devlist); |
| |
| if (dev->inst >= 0 && !dev->mapped) { |
| dev->mapped = true; |
| *inst = dev->inst; |
| videoqueue_devlist_unlock(flags); |
| return 0; |
| } |
| } |
| |
| videoqueue_devlist_unlock(flags); |
| return -ENODEV; |
| } |
| |
| void videoqueue_release_map(int inst) |
| { |
| unsigned long flags; |
| struct video_queue_dev *dev = NULL; |
| struct list_head *p; |
| |
| flags = videoqueue_devlist_lock(); |
| |
| list_for_each(p, &videoqueue_devlist) { |
| dev = list_entry(p, struct video_queue_dev, |
| videoqueue_devlist); |
| if (dev->inst == inst && dev->mapped) { |
| dev->mapped = false; |
| pr_info("%s %d OK\n", __func__, dev->inst); |
| break; |
| } |
| } |
| videoqueue_devlist_unlock(flags); |
| } |
| |
| static long videoqueue_ioctl(struct file *file, |
| unsigned int cmd, ulong arg) |
| { |
| long ret = 0; |
| u32 videoque_id = 0; |
| void __user *argp = (void __user *)arg; |
| |
| switch (cmd) { |
| case videoqueue_IOCTL_ALLOC_ID:{ |
| ret = videoqueue_alloc_map(&videoque_id); |
| if (ret != 0) |
| break; |
| put_user(videoque_id, (u32 __user *)argp); |
| } |
| break; |
| case videoqueue_IOCTL_FREE_ID:{ |
| get_user(videoque_id, (u32 __user *)argp); |
| videoqueue_release_map(videoque_id); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long videoqueue_compat_ioctl(struct file *file, |
| unsigned int cmd, |
| ulong arg) |
| { |
| long ret = 0; |
| |
| ret = videoqueue_ioctl(file, cmd, (ulong)compat_ptr(arg)); |
| return ret; |
| } |
| #endif |
| static const struct file_operations videoqueue_fops = { |
| .owner = THIS_MODULE, |
| .open = videoqueue_open, |
| .release = videoqueue_release, |
| .unlocked_ioctl = videoqueue_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = videoqueue_compat_ioctl, |
| #endif |
| .poll = NULL, |
| }; |
| |
| static int videoqueue_create_instance(int inst) |
| { |
| struct video_queue_dev *dev; |
| unsigned long flags; |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| dev->inst = inst; |
| snprintf(dev->vf_receiver_name, RECEIVER_NAME_SIZE, |
| RECEIVER_NAME ".%x", inst & 0xff); |
| |
| vf_receiver_init(&dev->video_vf_receiver, |
| dev->vf_receiver_name, &video_vf_receiver, dev); |
| vf_reg_receiver(&dev->video_vf_receiver); |
| |
| /* add to device list */ |
| flags = videoqueue_devlist_lock(); |
| list_add_tail(&dev->videoqueue_devlist, &videoqueue_devlist); |
| videoqueue_devlist_unlock(flags); |
| |
| mutex_init(&dev->mutex_input); |
| mutex_init(&dev->mutex_output); |
| |
| return 0; |
| } |
| |
| static int video_queue_release(void) |
| { |
| struct video_queue_dev *dev; |
| struct list_head *list; |
| unsigned long flags; |
| |
| flags = videoqueue_devlist_lock(); |
| |
| while (!list_empty(&videoqueue_devlist)) { |
| list = videoqueue_devlist.next; |
| list_del(list); |
| videoqueue_devlist_unlock(flags); |
| dev = list_entry(list, struct video_queue_dev, |
| videoqueue_devlist); |
| kfree(dev); |
| flags = videoqueue_devlist_lock(); |
| } |
| videoqueue_devlist_unlock(flags); |
| return 0; |
| } |
| |
| static int video_queue_probe(struct platform_device *pdev) |
| { |
| int ret = 0, i = 0; |
| struct device *devp; |
| |
| ret = class_register(&videoqueue_class); |
| if (ret < 0) |
| return ret; |
| ret = register_chrdev(VIDEOQUEUE_MAJOR, |
| "videoqueue", &videoqueue_fops); |
| if (ret < 0) { |
| pr_err("Can't allocate major for videoqueue device\n"); |
| goto error1; |
| } |
| devp = device_create(&videoqueue_class, |
| NULL, |
| MKDEV(VIDEOQUEUE_MAJOR, 0), |
| NULL, |
| videoqueue_DEVICE_NAME); |
| if (IS_ERR(devp)) { |
| pr_err("failed to create videoqueue device node\n"); |
| ret = PTR_ERR(devp); |
| return ret; |
| } |
| |
| if (n_devs <= 0) |
| n_devs = 1; |
| for (i = 0; i < n_devs; i++) { |
| ret = videoqueue_create_instance(i); |
| if (ret) { |
| /* If some instantiations succeeded, keep driver */ |
| if (i) |
| ret = 0; |
| break; |
| } |
| } |
| |
| if (ret < 0) { |
| pr_err("videoqueue: error %d while loading driver\n", ret); |
| goto error1; |
| } |
| |
| return 0; |
| error1: |
| unregister_chrdev(VIDEOQUEUE_MAJOR, videoqueue_DEVICE_NAME); |
| class_unregister(&videoqueue_class); |
| return ret; |
| } |
| |
| static int video_queue_remove(struct platform_device *pdev) |
| { |
| video_queue_release(); |
| device_destroy(&videoqueue_class, MKDEV(VIDEOQUEUE_MAJOR, 0)); |
| unregister_chrdev(VIDEOQUEUE_MAJOR, videoqueue_DEVICE_NAME); |
| class_unregister(&videoqueue_class); |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int video_queue_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| return 0; |
| } |
| |
| static int video_queue_resume(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| #endif |
| |
| static const struct of_device_id video_queue_dt_match[] = { |
| { |
| .compatible = "amlogic, video_queue", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver videoqueue_driver = { |
| .probe = video_queue_probe, |
| .remove = video_queue_remove, |
| #ifdef CONFIG_PM |
| .suspend = video_queue_suspend, |
| .resume = video_queue_resume, |
| #endif |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "videoqueue", |
| .of_match_table = video_queue_dt_match, |
| } |
| }; |
| |
| int __init videoqueue_init(void) |
| { |
| pr_info("%s___1\n", __func__); |
| init_waitqueue_head(&file_wq); |
| videoq_start = true; |
| if (platform_driver_register(&videoqueue_driver)) { |
| pr_err("failed to register videoqueue module\n"); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| void __exit videoqueue_exit(void) |
| { |
| videoq_start = false; |
| platform_driver_unregister(&videoqueue_driver); |
| } |
| |