From: Nagaraju Siddineni <[email protected]> - Implement runtime rate‑calculation (timestamp handling, framerate/BPS estimation, QoS adjustments, performance checks) with new helper APIs. - Add utility layer for format validation, resolution/stride/size computation, DPB size, address mapping, and view‑buffer bookkeeping. - Export functions used by decoder/encoder paths for dynamic QoS and memory handling.
Signed-off-by: Nagaraju Siddineni <[email protected]> Signed-off-by: Himanshu Dewangan <[email protected]> --- .../platform/samsung/exynos-mfc/Makefile | 2 +- .../samsung/exynos-mfc/base/mfc_mem.c | 187 ++++++ .../samsung/exynos-mfc/base/mfc_mem.h | 44 ++ .../exynos-mfc/base/mfc_rate_calculate.c | 612 ++++++++++++++++++ .../exynos-mfc/base/mfc_rate_calculate.h | 100 +++ .../samsung/exynos-mfc/base/mfc_utils.c | 284 ++++++++ .../samsung/exynos-mfc/base/mfc_utils.h | 214 +++++- 7 files changed, 1441 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.c create mode 100644 drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.h create mode 100644 drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.c diff --git a/drivers/media/platform/samsung/exynos-mfc/Makefile b/drivers/media/platform/samsung/exynos-mfc/Makefile index 5353289fa810..bd5f80953bab 100644 --- a/drivers/media/platform/samsung/exynos-mfc/Makefile +++ b/drivers/media/platform/samsung/exynos-mfc/Makefile @@ -19,7 +19,7 @@ exynos_mfc-y += mfc_core_hw_reg_api.o mfc_core_reg_api.o #Plugin control layer #Plugin HW access layer #Common base layer -exynos_mfc-y += base/mfc_queue.o +exynos_mfc-y += base/mfc_rate_calculate.o base/mfc_queue.o base/mfc_utils.o exynos_mfc-y += base/mfc_buf.o base/mfc_mem.o #Tracing # exynos_mfc-y += trace/mfc_trace_points.o diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.c b/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.c index 17cc1d793cbc..c99c1c081b0e 100644 --- a/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.c +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.c @@ -18,6 +18,70 @@ #include "mfc_mem.h" +struct vb2_mem_ops *mfc_mem_ops(void) +{ + return (struct vb2_mem_ops *)&vb2_dma_sg_memops; +} + +int mfc_mem_get_user_shared_handle(struct mfc_ctx *ctx, + struct mfc_user_shared_handle *handle, char *name) +{ + struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); + int ret = 0; + + handle->dma_buf = dma_buf_get(handle->fd); + if (IS_ERR(handle->dma_buf)) { + mfc_ctx_err("[MEMINFO][SH][%s] Failed to import fd\n", name); + ret = PTR_ERR(handle->dma_buf); + goto import_dma_fail; + } + + if (handle->dma_buf->size < handle->data_size) { + mfc_ctx_err("[MEMINFO][SH][%s] User-provided dma_buf size(%ld) is smaller than required size(%ld)\n", + name, handle->dma_buf->size, handle->data_size); + ret = -EINVAL; + goto dma_buf_size_fail; + } + ret = dma_buf_vmap_unlocked(handle->dma_buf, &map); + if (ret) { + mfc_ctx_err("[MEMINFO][SH][%s] Failed to get kernel virtual address\n", name); + ret = -EINVAL; + goto map_kernel_fail; + } + + handle->vaddr = map.vaddr; + mfc_ctx_debug(2, "[MEMINFO][SH][%s] shared handle fd: %d, vaddr: 0x%p, buf size: %zu, data size: %zu\n", + name, handle->fd, handle->vaddr, + handle->dma_buf->size, handle->data_size); + + return 0; + +map_kernel_fail: + handle->vaddr = NULL; +dma_buf_size_fail: + dma_buf_put(handle->dma_buf); +import_dma_fail: + handle->dma_buf = NULL; + handle->fd = -1; + return ret; +} + +void mfc_mem_cleanup_user_shared_handle(struct mfc_ctx *ctx, + struct mfc_user_shared_handle *handle) +{ + struct iosys_map map = IOSYS_MAP_INIT_VADDR(handle->vaddr); + + if (handle->vaddr) + dma_buf_vunmap_unlocked(handle->dma_buf, &map); + if (handle->dma_buf) + dma_buf_put(handle->dma_buf); + + handle->data_size = 0; + handle->dma_buf = NULL; + handle->vaddr = NULL; + handle->fd = -1; +} + static int mfc_mem_fw_alloc(struct mfc_dev *dev, struct mfc_special_buf *special_buf) { #if (IS_ENABLED(CONFIG_SAMSUNG_IOMMU)) @@ -334,6 +398,26 @@ void mfc_mem_special_buf_free(struct mfc_dev *dev, struct mfc_special_buf *speci } } +void mfc_bufcon_put_daddr(struct mfc_ctx *ctx, struct mfc_buf *mfc_buf, int plane) +{ + int i; + + for (i = 0; i < mfc_buf->num_valid_bufs; i++) { + if (mfc_buf->addr[i][plane]) { + mfc_ctx_debug(4, "[BUFCON] put batch buf addr[%d][%d]: 0x%08llx\n", + i, plane, mfc_buf->addr[i][plane]); + } + if (mfc_buf->attachments[i][plane]) + dma_buf_detach(mfc_buf->dmabufs[i][plane], mfc_buf->attachments[i][plane]); + if (mfc_buf->dmabufs[i][plane]) + dma_buf_put(mfc_buf->dmabufs[i][plane]); + + mfc_buf->addr[i][plane] = 0; + mfc_buf->attachments[i][plane] = NULL; + mfc_buf->dmabufs[i][plane] = NULL; + } +} + void mfc_put_iovmm(struct mfc_ctx *ctx, struct dpb_table *dpb, int num_planes, int index) { int i; @@ -524,6 +608,81 @@ void mfc_get_iovmm(struct mfc_ctx *ctx, struct vb2_buffer *vb, struct dpb_table mfc_put_iovmm(ctx, dpb, mem_get_count, sub_view_index); } +void mfc_init_dpb_table(struct mfc_ctx *ctx) +{ + struct mfc_dec *dec = ctx->dec_priv; + int index, plane; + + mutex_lock(&dec->dpb_mutex); + for (index = 0; index < MFC_MAX_DPBS; index++) { + for (plane = 0; plane < MFC_MAX_PLANES; plane++) { + dec->dpb[index].fd[plane] = -1; + dec->dpb[index].addr[plane] = 0; + dec->dpb[index].attach[plane] = NULL; + dec->dpb[index].dmabufs[plane] = NULL; + } + dec->dpb[index].new_fd = -1; + dec->dpb[index].mapcnt = 0; + dec->dpb[index].queued = 0; + } + mutex_unlock(&dec->dpb_mutex); +} + +void mfc_cleanup_iovmm(struct mfc_ctx *ctx) +{ + struct mfc_dec *dec = ctx->dec_priv; + int i; + + mutex_lock(&dec->dpb_mutex); + + for (i = 0; i < MFC_MAX_DPBS; i++) { + dec->dpb[i].paddr = 0; + dec->dpb[i].ref = 0; + if (dec->dpb[i].mapcnt == 0) { + continue; + } else if (dec->dpb[i].mapcnt == 1) { + mfc_put_iovmm(ctx, dec->dpb, ctx->dst_fmt->mem_planes, i); + } else { + mfc_ctx_err("[IOVMM] DPB[%d] %#llx invalid mapcnt %d\n", + i, dec->dpb[i].addr[0], dec->dpb[i].mapcnt); + MFC_TRACE_CTX("DPB[%d] %#llx invalid mapcnt %d\n", + i, dec->dpb[i].addr[0], dec->dpb[i].mapcnt); + } + } + + mutex_unlock(&dec->dpb_mutex); +} + +void mfc_cleanup_iovmm_except_used(struct mfc_ctx *ctx) +{ + struct mfc_dec *dec = ctx->dec_priv; + int i; + + mutex_lock(&dec->dpb_mutex); + + for (i = 0; i < MFC_MAX_DPBS; i++) { + if (dec->dynamic_used & (1UL << i)) { + continue; + } else { + dec->dpb[i].paddr = 0; + dec->dpb[i].ref = 0; + if (dec->dpb[i].mapcnt == 0) { + continue; + } else if (dec->dpb[i].mapcnt == 1) { + dec->dpb_table_used &= ~(1UL << i); + mfc_put_iovmm(ctx, dec->dpb, ctx->dst_fmt->mem_planes, i); + } else { + mfc_ctx_err("[IOVMM] DPB[%d] %#llx invalid mapcnt %d\n", + i, dec->dpb[i].addr[0], dec->dpb[i].mapcnt); + MFC_TRACE_CTX("DPB[%d] %#llx invalid mapcnt %d\n", + i, dec->dpb[i].addr[0], dec->dpb[i].mapcnt); + } + } + } + + mutex_unlock(&dec->dpb_mutex); +} + void mfc_iova_pool_free(struct mfc_dev *dev, struct mfc_special_buf *buf) { /* Project that do not support iova reservation */ @@ -712,6 +871,34 @@ int mfc_iommu_map_firmware(struct mfc_core *core, struct mfc_special_buf *fw_buf return 0; } +void mfc_check_iova(struct mfc_dev *dev) +{ + struct mfc_platdata *pdata = dev->pdata; + struct mfc_ctx *ctx; + unsigned long total_iova = 0; + + if (!pdata->iova_threshold) + return; + + /* + * The number of extra dpb is 8 + * OMX: extra buffer 5, platform buffer 3 + * Codec2: platform buffer 8 + */ + list_for_each_entry(ctx, &dev->ctx_list, list) + total_iova += (ctx->raw_buf.total_plane_size * + (ctx->dpb_count + MFC_EXTRA_DPB + 3)) / SZ_1K; + + if (total_iova > (pdata->iova_threshold * SZ_1K)) + dev->skip_lazy_unmap = 1; + else + dev->skip_lazy_unmap = 0; + + mfc_dev_debug(2, "[LAZY_UNMAP] Now the IOVA for DPB is %lu/%uMB, LAZY_UNMAP %s\n", + total_iova / SZ_1K, pdata->iova_threshold, + dev->skip_lazy_unmap ? "disable" : "enable"); +} + /* DMA memory related helper functions */ static void mfc_memdev_release(struct device *dev) { diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.h b/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.h index 3deeb0d611a0..3bd40dd0a0ed 100644 --- a/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.h +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_mem.h @@ -66,6 +66,35 @@ static inline size_t mfc_mem_get_sg_length(struct mfc_dev *dev, struct sg_table return size; } +static inline void mfc_mem_buf_prepare(struct vb2_buffer *vb, int stream) +{ + int i; + enum dma_data_direction dir; + struct dma_buf *dbuf; + + dir = V4L2_TYPE_IS_OUTPUT(vb->type) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE; + + for (i = 0; i < vb->num_planes; i++) { + dbuf = vb->planes[i].dbuf; + dma_buf_end_cpu_access(dbuf, dir); + } +} + +static inline void mfc_mem_buf_finish(struct vb2_buffer *vb, int stream) +{ + int i; + struct dma_buf *dbuf; + + if (V4L2_TYPE_IS_OUTPUT(vb->type)) + return; + + for (i = 0; i < vb->num_planes; i++) { + dbuf = vb->planes[i].dbuf; + dma_buf_begin_cpu_access(dbuf, DMA_FROM_DEVICE); + } +} + static inline void mfc_print_dpb_table(struct mfc_ctx *ctx) { struct mfc_dec *dec = ctx->dec_priv; @@ -95,16 +124,31 @@ static inline void mfc_print_dpb_table(struct mfc_ctx *ctx) dec->dpb[i].queued ? "Q" : "DQ"); } } + +struct vb2_mem_ops *mfc_mem_ops(void); + +int mfc_mem_get_user_shared_handle(struct mfc_ctx *ctx, + struct mfc_user_shared_handle *handle, char *name); +void mfc_mem_cleanup_user_shared_handle(struct mfc_ctx *ctx, + struct mfc_user_shared_handle *handle); + int mfc_mem_special_buf_alloc(struct mfc_dev *dev, struct mfc_special_buf *special_buf); void mfc_mem_special_buf_free(struct mfc_dev *dev, struct mfc_special_buf *special_buf); +void mfc_bufcon_put_daddr(struct mfc_ctx *ctx, struct mfc_buf *mfc_buf, int plane); + void mfc_put_iovmm(struct mfc_ctx *ctx, struct dpb_table *dpb, int num_planes, int index); void mfc_get_iovmm(struct mfc_ctx *ctx, struct vb2_buffer *vb, struct dpb_table *dpb); +void mfc_init_dpb_table(struct mfc_ctx *ctx); +void mfc_cleanup_iovmm(struct mfc_ctx *ctx); +void mfc_cleanup_iovmm_except_used(struct mfc_ctx *ctx); + void mfc_iova_pool_free(struct mfc_dev *dev, struct mfc_special_buf *buf); int mfc_iova_pool_alloc(struct mfc_dev *dev, struct mfc_special_buf *buf); int mfc_iova_pool_init(struct mfc_dev *dev); int mfc_iommu_map_firmware(struct mfc_core *core, struct mfc_special_buf *fw_buf); +void mfc_check_iova(struct mfc_dev *dev); int mfc_configure_dma_memory(struct mfc_dev *mfc_dev); void mfc_unconfigure_dma_memory(struct mfc_dev *mfc_dev); diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.c b/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.c new file mode 100644 index 000000000000..94a555c900d7 --- /dev/null +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * mfc_rate_calculate.c file + * + * Nagaraju Siddineni, <[email protected]> + * Himanshu Dewangan, <[email protected]> + */ + +#include <linux/err.h> +#include <linux/sort.h> + +#include "mfc_utils.h" + +#define COL_FRAME_RATE 0 +#define COL_FRAME_INTERVAL 1 + +#define MFC_MAX_INTERVAL (2 * USEC_PER_SEC) + +/* + * A framerate table determines framerate by the interval(us) of each frame. + * Framerate is not accurate, just rough value to separate overload section. + * Base line of each section are selected from middle value. + * 25fps(40000us), 40fps(25000us), 80fps(12500us) + * 144fps(6940us), 205fps(4860us), 320fps(3125us) + * + * interval(us) | 0 3125 4860 6940 12500 25000 40000 + * framerate | 480fps | 240fps | 180fps | 120fps | 60fps | 30fps | + * 24fps + */ +static unsigned long framerate_table[][2] = { + { 24000, 40000 }, + { 30000, 25000 }, + { 60000, 12500 }, + { 120000, 6940 }, + { 180000, 4860 }, + { 240000, 3125 }, + { 480000, 0 }, +}; + +/* + * display_framerate_table determines framerate by the queued interval. + * It supports 30fps, 60fps, 120fps as display framerate. + * Base line of each section is selected from middle value. + * 25fps(40000us), 40fps(25000us), 80fps(12500us) + * + * interval(us) | 12500 25000 40000 + * disp framerate | 120fps | 60fps | 30fps | 24fps + */ +static unsigned long display_framerate_table[][2] = { + { 24000, 40000 }, + { 30000, 25000 }, + { 60000, 12500 }, + { 120000, 0 }, +}; + +inline unsigned long mfc_rate_timespec64_diff(struct timespec64 *to, + struct timespec64 *from) +{ + unsigned long interval_nsec = (to->tv_sec * NSEC_PER_SEC + to->tv_nsec) + - (from->tv_sec * NSEC_PER_SEC + from->tv_nsec); + + if (interval_nsec <= 0) + interval_nsec = MFC_MAX_INTERVAL * 1000; + + return interval_nsec / 1000; +} + +static int __mfc_rate_ts_sort(const void *p0, const void *p1) +{ + const int *t0 = p0, *t1 = p1; + + /* ascending sort */ + if (*t0 < *t1) + return -1; + else if (*t0 > *t1) + return 1; + + return 0; +} + +static int __mfc_rate_get_ts_min_interval(struct mfc_ctx *ctx, struct mfc_ts_control *ts) +{ + int tmp[MAX_TIME_INDEX]; + + if (!ts->ts_is_full) + return 0; + + memcpy(&tmp[0], &ts->ts_interval_array[0], MAX_TIME_INDEX * sizeof(int)); + sort(tmp, MAX_TIME_INDEX, sizeof(int), __mfc_rate_ts_sort, NULL); + + return tmp[0]; +} + +static int __mfc_rate_get_ts_interval(struct mfc_ctx *ctx, struct mfc_ts_control *ts, int type) +{ + int tmp[MAX_TIME_INDEX]; + int n, i, val = 0, sum = 0; + + n = ts->ts_is_full ? MAX_TIME_INDEX : ts->ts_count; + + memcpy(&tmp[0], &ts->ts_interval_array[0], n * sizeof(int)); + sort(tmp, n, sizeof(int), __mfc_rate_ts_sort, NULL); + + if (type == MFC_TS_SRC) { + /* apply median filter for selecting ts interval */ + val = (n <= 2) ? tmp[0] : tmp[n / 2]; + } else if ((type == MFC_TS_DST_Q) || (type == MFC_TS_SRC_Q) || (type == MFC_TS_DST_DQ)) { + /* apply average for selecting ts interval except min,max */ + if (n < 3) + return 0; + for (i = 1; i < (n - 1); i++) + sum += tmp[i]; + val = sum / (n - 2); + } else { + mfc_ctx_err("[TS] Wrong timestamp type %d\n", type); + } + + if (ctx->dev->debugfs.debug_ts == 1) { + mfc_ctx_info("==============[%s%s%s][TS] interval (sort)==============\n", + (type & 0x1) ? "SRC" : "DST", (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : ""); + for (i = 0; i < n; i++) + mfc_ctx_info("[%s%s%s][TS] interval [%d] = %d\n", + (type & 0x1) ? "SRC" : "DST", + (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : "", i, tmp[i]); + mfc_ctx_info("[%s%s%s][TS] get interval %d\n", + (type & 0x1) ? "SRC" : "DST", + (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : "", val); + } + + return val; +} + +static unsigned long __mfc_rate_get_framerate_by_interval(int interval, int type) +{ + unsigned long (*table)[2]; + unsigned long i; + int size; + + /* if the interval is too big (2sec), framerate set to 0 */ + if (interval > MFC_MAX_INTERVAL || interval <= 0) + return 0; + + if (type == MFC_TS_SRC || type == MFC_TS_SRC_Q || type == MFC_TS_DST_DQ) { + table = framerate_table; + size = ARRAY_SIZE(framerate_table); + } else if (type == MFC_TS_DST_Q) { + table = display_framerate_table; + size = ARRAY_SIZE(display_framerate_table); + } else { + return 0; + } + + for (i = 0; i < size; i++) { + if (interval > table[i][COL_FRAME_INTERVAL]) + return table[i][COL_FRAME_RATE]; + } + + return 0; +} + +int mfc_rate_get_bps_section_by_bps(struct mfc_dev *dev, int kbps, int max_kbps) +{ + struct mfc_platdata *pdata = dev->pdata; + int i, max_freq_idx; + int freq_ratio, bps_interval, prev_interval = 0; + + max_freq_idx = pdata->num_mfc_freq - 1; + if (kbps > max_kbps) { + mfc_dev_debug(4, "[BPS] overspec bps %d > %d\n", kbps, max_kbps); + return max_freq_idx; + } + + for (i = 0; i < pdata->num_mfc_freq; i++) { + freq_ratio = pdata->mfc_freqs[i] * 100 / pdata->mfc_freqs[max_freq_idx]; + bps_interval = max_kbps * freq_ratio / 100; + mfc_dev_debug(4, "[BPS] MFC freq lv%d, %uKHz covered: %d ~ %dkbps (now: %dkbps)\n", + i, pdata->mfc_freqs[i], prev_interval, bps_interval, kbps); + if (kbps <= bps_interval) { + mfc_dev_debug(3, "[BPS] MFC freq lv%d, %uKHz is needed: %d ~ %dkbps\n", + i, pdata->mfc_freqs[i], + prev_interval, bps_interval); + return i; + } + prev_interval = bps_interval; + } + + /* Not changed the MFC freq according to BPS */ + return 0; +} + +/* Return the minimum interval between previous and next entry */ +static int __mfc_rate_get_interval(struct list_head *head, struct list_head *entry) +{ + unsigned long prev_interval = MFC_MAX_INTERVAL, next_interval = MFC_MAX_INTERVAL; + struct mfc_timestamp *prev_ts, *next_ts, *curr_ts; + int ret = 0; + + curr_ts = list_entry(entry, struct mfc_timestamp, list); + + if (entry->prev != head) { + prev_ts = list_entry(entry->prev, struct mfc_timestamp, list); + prev_interval = mfc_rate_timespec64_diff(&curr_ts->timestamp, + &prev_ts->timestamp); + if (prev_interval > MFC_MAX_INTERVAL) + prev_interval = MFC_MAX_INTERVAL; + } + + if (entry->next != head) { + next_ts = list_entry(entry->next, struct mfc_timestamp, list); + next_interval = mfc_rate_timespec64_diff(&next_ts->timestamp, + &curr_ts->timestamp); + if (next_interval > MFC_MAX_INTERVAL) + next_interval = MFC_MAX_INTERVAL; + } + + ret = (prev_interval < next_interval) ? prev_interval : next_interval; + return ret; +} + +static int __mfc_rate_add_timestamp(struct mfc_ctx *ctx, struct mfc_ts_control *ts, + struct timespec64 *time, struct list_head *head) +{ + int replace_entry = 0; + struct mfc_timestamp *curr_ts = &ts->ts_array[ts->ts_count]; + struct mfc_timestamp *adj_ts = NULL; + + if (ts->ts_is_full) { + /* Replace the entry if list of array[ts_count] is same as entry */ + if (&curr_ts->list == head) + replace_entry = 1; + else + list_del(&curr_ts->list); + } + + memcpy(&curr_ts->timestamp, time, sizeof(*time)); + if (!replace_entry) + list_add(&curr_ts->list, head); + curr_ts->interval = __mfc_rate_get_interval(&ts->ts_list, &curr_ts->list); + curr_ts->index = ts->ts_count; + + ts->ts_interval_array[ts->ts_count] = curr_ts->interval; + ts->ts_count++; + + if (ts->ts_count == MAX_TIME_INDEX) { + ts->ts_is_full = 1; + ts->ts_count %= MAX_TIME_INDEX; + } + + /* + * When timestamp is updated, the interval of adjacent timestamp can be changed. + * So, update this value of prev and next in list. + */ + if (curr_ts->list.next != &ts->ts_list) { + adj_ts = list_entry(curr_ts->list.next, struct mfc_timestamp, list); + adj_ts->interval = __mfc_rate_get_interval(&ts->ts_list, &adj_ts->list); + ts->ts_interval_array[adj_ts->index] = adj_ts->interval; + } + if (curr_ts->list.prev != &ts->ts_list) { + adj_ts = list_entry(curr_ts->list.prev, struct mfc_timestamp, list); + adj_ts->interval = __mfc_rate_get_interval(&ts->ts_list, &adj_ts->list); + ts->ts_interval_array[adj_ts->index] = adj_ts->interval; + } + + return 0; +} + +void mfc_rate_reset_ts_list(struct mfc_ts_control *ts) +{ + struct mfc_timestamp *temp_ts = NULL; + unsigned long flags; + + spin_lock_irqsave(&ts->ts_lock, flags); + + /* empty the timestamp queue */ + while (!list_empty(&ts->ts_list)) { + temp_ts = list_entry((&ts->ts_list)->next, struct mfc_timestamp, list); + list_del(&temp_ts->list); + } + + ts->ts_count = 0; + ts->ts_is_full = 0; + + spin_unlock_irqrestore(&ts->ts_lock, flags); +} + +static unsigned long __mfc_rate_get_fps_by_timestamp(struct mfc_ctx *ctx, + struct mfc_ts_control *ts, + struct timespec64 *time, int type) +{ + struct mfc_timestamp *temp_ts; + int found; + int index = 0; + int ts_is_full = ts->ts_is_full; + int interval = MFC_MAX_INTERVAL; + int min_interval = 0; + int time_diff; + unsigned long framerate; + unsigned long flags; + u64 current_time; + + spin_lock_irqsave(&ts->ts_lock, flags); + if (list_empty(&ts->ts_list)) { + __mfc_rate_add_timestamp(ctx, ts, time, &ts->ts_list); + spin_unlock_irqrestore(&ts->ts_lock, flags); + return __mfc_rate_get_framerate_by_interval(0, type); + } + found = 0; + list_for_each_entry_reverse(temp_ts, &ts->ts_list, list) { + time_diff = __mfc_timespec64_compare(time, &temp_ts->timestamp); + if (time_diff == 0) { + /* Do not add if same timestamp already exists */ + found = 1; + break; + } else if (time_diff > 0) { + /* Add this after temp_ts */ + __mfc_rate_add_timestamp(ctx, ts, time, &temp_ts->list); + found = 1; + break; + } + } + + if (!found) /* Add this at first entry */ + __mfc_rate_add_timestamp(ctx, ts, time, &ts->ts_list); + spin_unlock_irqrestore(&ts->ts_lock, flags); + + interval = __mfc_rate_get_ts_interval(ctx, ts, type); + framerate = __mfc_rate_get_framerate_by_interval(interval, type); + + if (ctx->dev->debugfs.debug_ts == 1) { + spin_lock_irqsave(&ts->ts_lock, flags); + /* Debug info */ + mfc_ctx_info("=================[%s%s%s][TS]===================\n", + (type & 0x1) ? "SRC" : "DST", (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : ""); + mfc_ctx_info("[%s%s%s][TS] New timestamp = %lld.%06ld, count = %d\n", + (type & 0x1) ? "SRC" : "DST", + (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : "", + time->tv_sec, time->tv_nsec, ts->ts_count); + + index = 0; + list_for_each_entry(temp_ts, &ts->ts_list, list) { + mfc_ctx_info("[%s%s][TS] [%d] timestamp [i:%d]: %lld.%06ld\n", + (type & 0x1) ? "SRC" : "DST", + (type & 0x2) ? "_Q" : "", + index, temp_ts->index, + temp_ts->timestamp.tv_sec, + temp_ts->timestamp.tv_nsec); + index++; + } + mfc_ctx_info("[%s%s%s][TS] Min interval = %d, It is %ld fps\n", + (type & 0x1) ? "SRC" : "DST", + (type & 0x2) ? "_Q" : "", + (type & 0x4) ? "_DQ" : "", + interval, framerate); + spin_unlock_irqrestore(&ts->ts_lock, flags); + } + + if (!ts->ts_is_full) { + if (ctx->dev->debugfs.debug_ts == 1) + mfc_ctx_info("[TS] ts doesn't full, keep %ld fps\n", ctx->framerate); + return ctx->framerate; + } + + if (!ts_is_full && type == MFC_TS_SRC) { + min_interval = __mfc_rate_get_ts_min_interval(ctx, ts); + if (min_interval) + ctx->max_framerate = + __mfc_rate_get_framerate_by_interval(min_interval, type); + else + ctx->max_framerate = 0; + + current_time = ktime_get_ns() / NSEC_PER_SEC; + if (current_time == time->tv_sec) + ctx->ktime_used = TRUE; + } + + return framerate; +} + +static int __mfc_rate_get_bps_section(struct mfc_ctx *ctx, u32 bytesused) +{ + struct mfc_dev *dev = ctx->dev; + struct list_head *head = &ctx->bitrate_list; + struct mfc_bitrate *temp_bitrate; + struct mfc_bitrate *new_bitrate = &ctx->bitrate_array[ctx->bitrate_index]; + int max_kbps; + unsigned long sum_size = 0, avg_kbits, fps; + int count = 0; + + if (ctx->bitrate_is_full) { + temp_bitrate = list_entry(head->next, struct mfc_bitrate, list); + list_del(&temp_bitrate->list); + } + + new_bitrate->bytesused = bytesused; + list_add_tail(&new_bitrate->list, head); + + list_for_each_entry(temp_bitrate, head, list) { + mfc_ctx_debug(4, "[BPS][%d] strm_size %d\n", count, temp_bitrate->bytesused); + sum_size += temp_bitrate->bytesused; + count++; + } + + if (count == 0) { + mfc_ctx_err("[BPS] There is no list for bps\n"); + return ctx->last_bps_section; + } + + ctx->bitrate_index++; + if (ctx->bitrate_index == MAX_TIME_INDEX) { + ctx->bitrate_is_full = 1; + ctx->bitrate_index %= MAX_TIME_INDEX; + } + + /* + * When there is a value of ts_is_full, + * we can trust fps(trusted fps calculated by timestamp diff). + * When fps information becomes reliable, + * we will start QoS handling by obtaining bps section. + */ + if (!ctx->src_ts.ts_is_full) + return 0; + + if (IS_MULTI_MODE(ctx)) + fps = ctx->last_framerate / 1000 / dev->num_core; + else + fps = ctx->last_framerate / 1000; + avg_kbits = ((sum_size * BITS_PER_BYTE) / count) / SZ_1K; + ctx->kbps = (int)(avg_kbits * fps); + max_kbps = dev->pdata->mfc_resource[ctx->codec_mode].max_kbps; + mfc_ctx_debug(3, "[BPS] %d kbps, average %lu Kbits per frame\n", ctx->kbps, avg_kbits); + + return mfc_rate_get_bps_section_by_bps(dev, ctx->kbps, max_kbps); +} + +void mfc_rate_update_bitrate(struct mfc_ctx *ctx, u32 bytesused) +{ + int bps_section; + + /* bitrate is updated */ + bps_section = __mfc_rate_get_bps_section(ctx, bytesused); + if (ctx->last_bps_section != bps_section) { + mfc_ctx_debug(2, "[BPS] bps section changed: %d -> %d\n", + ctx->last_bps_section, bps_section); + ctx->last_bps_section = bps_section; + ctx->update_bitrate = true; + } +} + +void mfc_rate_update_framerate(struct mfc_ctx *ctx) +{ + struct mfc_dev *dev = ctx->dev; + unsigned long framerate; + + /* 2) when src timestamp isn't full, only check operating framerate by user */ + if (!ctx->src_ts.ts_is_full) { + if (ctx->operating_framerate && ctx->operating_framerate > ctx->framerate) { + mfc_ctx_debug(2, "[QoS] operating fps changed: %ld\n", + ctx->operating_framerate); + mfc_rate_set_framerate(ctx, ctx->operating_framerate); + ctx->update_framerate = true; + return; + } + } else { + /* 3) get src framerate */ + framerate = ctx->last_framerate; + + /* 4) check display framerate */ + if (dev->pdata->display_framerate && + ctx->dst_q_ts.ts_is_full && ctx->dst_q_framerate > framerate) { + framerate = ctx->dst_q_framerate; + if (framerate != ctx->framerate) + mfc_ctx_debug(2, "[QoS] display fps %ld\n", framerate); + } + + /* 5) check operating framerate by user */ + if (ctx->operating_framerate && ctx->operating_framerate > framerate) { + framerate = ctx->operating_framerate; + if (framerate != ctx->framerate) + mfc_ctx_debug(2, "[QoS] operating fps %ld\n", framerate); + } + + /* 6) check non-real-time and undefined mode */ + if (ctx->rt == MFC_NON_RT || ctx->rt == MFC_RT_UNDEFINED) { + if (framerate < ctx->src_q_framerate) { + framerate = ctx->src_q_framerate; + if (framerate != ctx->framerate) + mfc_ctx_debug(2, + "[QoS][PRIO] (default) NRT src_q fps %ld\n", + framerate); + } + } + + if (ctx->operating_framerate == ctx->framerate && !ctx->check_src_ts_full) { + mfc_ctx_debug(2, "[QoS] src ts is full, update framerate for load balancing\n"); + ctx->check_src_ts_full = true; + ctx->update_framerate = true; + } + + if (framerate && framerate != ctx->framerate) { + mfc_ctx_debug(2, "[QoS] fps changed: %ld -> %ld, qos ratio: %d\n", + ctx->framerate, framerate, ctx->qos_ratio); + mfc_rate_set_framerate(ctx, framerate); + ctx->update_framerate = true; + } + } +} + +void mfc_rate_update_last_framerate(struct mfc_ctx *ctx, u64 timestamp) +{ + struct timespec64 time; + + time.tv_sec = timestamp / NSEC_PER_SEC; + time.tv_nsec = (timestamp - (time.tv_sec * NSEC_PER_SEC)); + + ctx->last_framerate = __mfc_rate_get_fps_by_timestamp(ctx, &ctx->src_ts, &time, MFC_TS_SRC); + if (ctx->last_framerate > MFC_MAX_FPS) + ctx->last_framerate = MFC_MAX_FPS; + + if (ctx->src_ts.ts_is_full) + ctx->last_framerate = (ctx->qos_ratio * ctx->last_framerate) / 100; +} + +void mfc_rate_update_bufq_framerate(struct mfc_ctx *ctx, int type) +{ + struct timespec64 time; + u64 timestamp; + + timestamp = ktime_get_ns(); + time.tv_sec = timestamp / NSEC_PER_SEC; + time.tv_nsec = timestamp - (time.tv_sec * NSEC_PER_SEC); + + if (type == MFC_TS_DST_Q) { + ctx->dst_q_framerate = __mfc_rate_get_fps_by_timestamp + (ctx, &ctx->dst_q_ts, &time, MFC_TS_DST_Q); + mfc_ctx_debug(5, "[QoS] dst_q_framerate = %lu\n", ctx->dst_q_framerate); + } + if (type == MFC_TS_DST_DQ) { + ctx->dst_dq_framerate = __mfc_rate_get_fps_by_timestamp + (ctx, &ctx->dst_dq_ts, &time, MFC_TS_DST_DQ); + mfc_ctx_debug(5, "[QoS] dst_dq_framerate = %lu\n", ctx->dst_dq_framerate); + } + if (type == MFC_TS_SRC_Q) { + ctx->src_q_framerate = __mfc_rate_get_fps_by_timestamp + (ctx, &ctx->src_q_ts, &time, MFC_TS_SRC_Q); + mfc_ctx_debug(5, "[QoS] src_q_framerate = %lu\n", ctx->src_q_framerate); + } +} + +void mfc_rate_reset_bufq_framerate(struct mfc_ctx *ctx) +{ + ctx->dst_q_framerate = 0; + ctx->dst_dq_framerate = 0; + ctx->src_q_framerate = 0; + + mfc_rate_reset_ts_list(&ctx->dst_q_ts); + mfc_rate_reset_ts_list(&ctx->src_q_ts); +} + +int mfc_rate_check_perf_ctx(struct mfc_ctx *ctx, int max_runtime) +{ + struct mfc_ts_control *ts; + struct timespec64 start_time, curr_time; + u64 timestamp, interval; + int op_fps, fps; + unsigned long flags; + + if (ctx->dev->debugfs.sched_perf_disable) + return 0; + + ts = &ctx->dst_dq_ts; + if (!ts->ts_is_full) + return 0; + + op_fps = ctx->operating_framerate; + if (op_fps == 0) { + op_fps = ctx->src_q_framerate; + mfc_ctx_debug(2, "[PRIO][rt %d] use fps: %d\n", ctx->rt, op_fps); + } + + /* Calculate interval from start to current time */ + timestamp = ktime_get_ns(); + curr_time.tv_sec = timestamp / NSEC_PER_SEC; + curr_time.tv_nsec = timestamp - (curr_time.tv_sec * NSEC_PER_SEC); + + spin_lock_irqsave(&ts->ts_lock, flags); + start_time = ts->ts_array[ts->ts_count].timestamp; + spin_unlock_irqrestore(&ts->ts_lock, flags); + + interval = mfc_rate_timespec64_diff(&curr_time, &start_time); + interval += max_runtime; + + fps = (((MAX_TIME_INDEX - 1) * 1000) / (interval / 1000)) * 1000; + mfc_ctx_debug(2, "[PRIO][rt %d] st %lld.%06ld, curr %lld.%06ld, interval %lld, fps %d\n", + ctx->rt, start_time.tv_sec, start_time.tv_nsec, + curr_time.tv_sec, curr_time.tv_nsec, interval, fps); + + if (fps > op_fps) { + mfc_ctx_debug(2, "[PRIO] PERF enough fps: %d, op_fps: %d\n", fps, op_fps); + return 1; + } + + mfc_ctx_debug(2, "[PRIO] PERF insufficient fps: %d, op_fps: %d\n", fps, op_fps); + return 0; +} diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.h b/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.h new file mode 100644 index 000000000000..2452e6ee56dd --- /dev/null +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_rate_calculate.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * mfc_rate_calculate.h + * + * Nagaraju Siddineni, <[email protected]> + * Himanshu Dewangan, <[email protected]> + */ + +#ifndef __MFC_RATE_CALCULATE_H +#define __MFC_RATE_CALCULATE_H __FILE__ + +#include "mfc_common.h" + +#define MFC_MIN_FPS (30000) +#define MFC_MAX_FPS (480000) +#define DEC_DEFAULT_FPS (480000) +#define ENC_DEFAULT_FPS (480000) +#define ENC_DEFAULT_CAM_CAPTURE_FPS (60000) + +inline unsigned long mfc_rate_timespec64_diff(struct timespec64 *to, + struct timespec64 *from); +void mfc_rate_reset_ts_list(struct mfc_ts_control *ts); +int mfc_rate_get_bps_section_by_bps(struct mfc_dev *dev, int kbps, int max_kbps); +void mfc_rate_update_bitrate(struct mfc_ctx *ctx, u32 bytesused); +void mfc_rate_update_framerate(struct mfc_ctx *ctx); +void mfc_rate_update_last_framerate(struct mfc_ctx *ctx, u64 timestamp); +void mfc_rate_update_bufq_framerate(struct mfc_ctx *ctx, int type); +void mfc_rate_reset_bufq_framerate(struct mfc_ctx *ctx); +int mfc_rate_check_perf_ctx(struct mfc_ctx *ctx, int max_runtime); + +static inline int __mfc_timespec64_compare(const struct timespec64 *lhs, + const struct timespec64 *rhs) +{ + if (lhs->tv_sec < rhs->tv_sec) + return -1; + if (lhs->tv_sec > rhs->tv_sec) + return 1; + return lhs->tv_nsec - rhs->tv_nsec; +} + +static inline void mfc_rate_reset_framerate(struct mfc_ctx *ctx) +{ + if (ctx->type == MFCINST_DECODER) + ctx->framerate = DEC_DEFAULT_FPS; + + mfc_ctx_debug(3, "[QoS] reset ctx->framrate: %lu\n", ctx->framerate); +} + +static inline void mfc_rate_reset_last_framerate(struct mfc_ctx *ctx) +{ + ctx->last_framerate = 0; +} + +static inline void mfc_rate_set_framerate(struct mfc_ctx *ctx, int rate) +{ + ctx->framerate = rate; + + mfc_ctx_debug(3, "[QoS] set ctx->framerate: %lu\n", ctx->framerate); +} + +static inline int mfc_rate_get_framerate(struct mfc_ctx *ctx) +{ + unsigned long framerate = ctx->last_framerate; + + if (!framerate) + framerate = ctx->framerate; + + if (ctx->operating_framerate > framerate) + return ctx->operating_framerate; + else + return framerate; +} + +static inline unsigned long mfc_rate_get_rt_framerate(struct mfc_ctx *ctx, enum mfc_real_time rt) +{ + unsigned long framerate; + + framerate = ctx->operating_framerate; + + if (rt == MFC_RT_UNDEFINED || rt == MFC_NON_RT) { + framerate = ctx->framerate; + } else { + if (ctx->src_ts.ts_is_full) + framerate = mfc_rate_get_framerate(ctx); + } + + if (framerate == 0) + framerate = MFC_MIN_FPS; + else if (framerate > MFC_MAX_FPS) + framerate = MFC_MAX_FPS; + + mfc_ctx_debug(3, "[QoS] rt framerate: %lu\n", framerate); + + return framerate; +} + +#endif /* __MFC_RATE_CALCULATE_H */ diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.c b/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.c new file mode 100644 index 000000000000..b0698b2bb0c0 --- /dev/null +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * mfc_utils.c file + * + * Nagaraju Siddineni, <[email protected]> + * Himanshu Dewangan, <[email protected]> + */ + +#include "mfc_utils.h" +#include "mfc_mem.h" +#include "mfc_queue.h" + +static struct mfc_resolution mfc_res[] = { + { + .width = 0, + .height = 0, + }, + { + .width = 1920, + .height = 1088, + }, + { + .width = 4096, + .height = 2176, + }, + { + .width = 8192, + .height = 4352, + }, +}; + +int mfc_check_vb_with_fmt(struct mfc_fmt *fmt, struct vb2_buffer *vb) +{ + struct mfc_ctx *ctx = vb->vb2_queue->drv_priv; + int mem_planes; + + if (!fmt) + return -EINVAL; + + if (fmt->type & MFC_FMT_FRAME) + mem_planes = ctx->num_fd_frame; + else + mem_planes = fmt->mem_planes; + + if (mem_planes != vb->num_planes) { + mfc_ctx_err("plane number is different (%d != %d)\n", + mem_planes, vb->num_planes); + return -EINVAL; + } + + return 0; +} + +int mfc_check_resolution(struct mfc_ctx *ctx) +{ + int check_res = ctx->dev->pdata->support_check_res; + int max_width, max_height; + + if (!check_res) + return 0; + + max_width = mfc_res[check_res].width; + max_height = mfc_res[check_res].height; + + if ((ctx->crop_width > max_width && ctx->crop_height > max_height) || + (ctx->crop_width > max_height && ctx->crop_height > max_width)) { + mfc_ctx_err("Resolution is too big (%dx%d > %dx%d or %dx%d)\n", + ctx->crop_width, ctx->crop_height, + max_width, max_height, max_height, max_width); + return -EINVAL; + } + + return 0; +} + +static void __mfc_set_dec_stride(struct mfc_ctx *ctx, + struct mfc_raw_info *raw, + struct mfc_fmt *fmt) +{ + int stride_align, y_stride; + + stride_align = ctx->dev->pdata->stride_align; + y_stride = ALIGN(ctx->img_width, stride_align); + + switch (fmt->fourcc) { + case V4L2_PIX_FMT_YUV420M: + case V4L2_PIX_FMT_YVU420M: + raw->stride[0] = y_stride; + raw->stride[1] = ALIGN(y_stride >> 1, stride_align); + raw->stride[2] = ALIGN(y_stride >> 1, stride_align); + break; + case V4L2_PIX_FMT_NV12MT_16X16: + case V4L2_PIX_FMT_NV12MT: + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_NV21M: + case V4L2_PIX_FMT_NV16M: + case V4L2_PIX_FMT_NV61M: + raw->stride[0] = y_stride; + raw->stride[1] = y_stride; + raw->stride[2] = 0; + break; + default: + mfc_ctx_err("Invalid pixelformat : %s\n", fmt->name); + break; + } +} + +void mfc_set_linear_stride_size(struct mfc_ctx *ctx, struct mfc_raw_info *raw, struct mfc_fmt *fmt) +{ + /* + * Decoder: Use stride alignment value defiend in DT. + * (Largest limitation among SoC IPs) + * Encoder: Use the stride value that the user set when s_fmt. + */ + if (ctx->type == MFCINST_DECODER) + __mfc_set_dec_stride(ctx, raw, fmt); +} + +void mfc_dec_calc_dpb_size(struct mfc_ctx *ctx, struct mfc_raw_info *raw, struct mfc_fmt *fmt) +{ + int i; + int extra = MFC_LINEAR_BUF_SIZE; + int check_min_dpb_size = 1; + + mfc_set_linear_stride_size(ctx, raw, fmt); + + raw->total_plane_size = 0; + + for (i = 0; i < raw->num_planes; i++) { + raw->plane_size[i] = 0; + raw->plane_size_2bits[i] = 0; + } + + switch (fmt->fourcc) { + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_NV21M: + raw->plane_size[0] = raw->stride[0] * ALIGN(ctx->img_height, 16) + extra; + raw->plane_size[1] = raw->stride[1] * ALIGN(ctx->img_height, 16) / 2 + extra; + break; + case V4L2_PIX_FMT_YUV420M: + case V4L2_PIX_FMT_YVU420M: + raw->plane_size[0] = raw->stride[0] * ALIGN(ctx->img_height, 16) + extra; + raw->plane_size[1] = raw->stride[1] * ALIGN(ctx->img_height, 16) / 2 + extra; + raw->plane_size[2] = raw->stride[2] * ALIGN(ctx->img_height, 16) / 2 + extra; + break; + case V4L2_PIX_FMT_NV16M: + case V4L2_PIX_FMT_NV61M: + raw->plane_size[0] = raw->stride[0] * ALIGN(ctx->img_height, 16) + extra; + raw->plane_size[1] = raw->stride[1] * ALIGN(ctx->img_height, 16) + extra; + break; + default: + mfc_ctx_err("Invalid pixelformat : %s\n", fmt->name); + break; + } + + /* + * The min DPB size returned by firmware may be larger than + * the DPB size calculated by the driver in the following situation. + * - Change 10bit mem_type at INIT_BUF. + * - Use single-fd format without extra bytes. + * In the above case, if the driver forcibly changes the DPB size, + * it fails due to buffer size error at V4L2 Qbuf. + * And when F/W really needs min DPB size in scenario like VP9 interframe DRC, + * if the driver does not force change the DPB size, + * No.57(INSUFFICIENT_DPB_SIZE) error occurs in F/W. + */ + if (fmt->mem_planes == 1) + check_min_dpb_size = 0; + + if (check_min_dpb_size) { + for (i = 0; i < raw->num_planes; i++) { + if (raw->plane_size[i] < ctx->min_dpb_size[i]) { + mfc_ctx_info("[FRAME] plane[%d] size %d / min size %d\n", + i, raw->plane_size[i], ctx->min_dpb_size[i]); + raw->plane_size[i] = ctx->min_dpb_size[i]; + } + } + } + + for (i = 0; i < raw->num_planes; i++) { + raw->total_plane_size += raw->plane_size[i]; + mfc_ctx_debug(2, "[FRAME] Plane[%d] size = %d, stride = %d\n", + i, raw->plane_size[i], raw->stride[i]); + } + mfc_ctx_debug(2, "[FRAME] total plane size: %d\n", raw->total_plane_size); + + if (IS_H264_DEC(ctx)) { + ctx->mv_size = DEC_MV_SIZE_MB(ctx->img_width, ctx->img_height); + ctx->mv_size = ALIGN(ctx->mv_size, 32); + } else { + ctx->mv_size = 0; + } +} + +void mfc_calc_base_addr(struct mfc_ctx *ctx, struct vb2_buffer *vb, + struct mfc_fmt *fmt) +{ + struct mfc_buf *buf = vb_to_mfc_buf(vb); + int i, idx, max_idx; + + if ((fmt->type & MFC_FMT_FRAME) && ctx->multi_view_enable) + max_idx = MFC_MV_BUF_IDX_MAX; + else + max_idx = 1; + + for (i = 0; i < max_idx; i++) { + /* It means there is no plane in the buffer. */ + if (ctx->view_buf_info[i].num_fd == 0) + continue; + + for (idx = 0; idx < ctx->view_buf_info[i].num_fd; idx++) + buf->addr[i][idx] = mfc_mem_get_daddr_vb + (vb, ctx->view_buf_info[i].offset + idx); + } + + for (i = 0; i < fmt->num_planes; i++) + mfc_ctx_debug(2, "[MEMINFO] plane[%d] addr %#llx\n", i, buf->addr[0][i]); +} + +void mfc_set_view_buf_info(struct mfc_ctx *ctx, + int mem_planes, + int num_fd_depth_map, + int num_fd_sub_view_meta) +{ + int offset = 0; + + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW0].offset = offset; + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW0].num_fd = mem_planes; + offset += mem_planes; + + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW0_DEPTH].offset = offset; + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW0_DEPTH].num_fd = num_fd_depth_map; + offset += num_fd_depth_map; + + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1].offset = offset; + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1].num_fd = mem_planes; + offset += mem_planes; + + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1_DEPTH].offset = offset; + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1_DEPTH].num_fd = num_fd_depth_map; + offset += num_fd_depth_map; + + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1_META].offset = offset; + ctx->view_buf_info[MFC_MV_BUF_IDX_VIEW1_META].num_fd = num_fd_sub_view_meta; + offset += num_fd_sub_view_meta; +} + +void mfc_core_idle_checker(struct timer_list *t) +{ + struct mfc_core *core = timer_container_of(core, t, mfc_idle_timer); + struct mfc_dev *dev = core->dev; + + mfc_core_debug(5, "[MFCIDLE] MFC HW idle checker is ticking!\n"); + + if (dev->move_ctx_cnt) { + MFC_TRACE_RM("[MFCIDLE] migration working\n"); + mfc_core_idle_checker_start_tick(core); + return; + } + + if (atomic_read(&core->qos_req_cur) == 0) { + mfc_core_debug(6, "[MFCIDLE] MFC QoS not started yet\n"); + mfc_core_idle_checker_start_tick(core); + return; + } + + if (core->sched->is_work(core)) { + MFC_TRACE_CORE("[MFCIDLE] there is work to do\n"); + mfc_core_debug(6, "[MFCIDLE] there is work to do\n"); + core->sched->queue_work(core); + mfc_core_idle_checker_start_tick(core); + return; + } + + if (!atomic_read(&core->hw_run_bits) && !atomic_read(&core->dev->queued_bits)) + mfc_core_change_idle_mode(core, MFC_IDLE_MODE_RUNNING); + +#ifdef CONFIG_MFC_USE_BUS_DEVFREQ + queue_work(core->mfc_idle_wq, &core->mfc_idle_work); +#endif +} diff --git a/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.h b/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.h index 320dc96a40ed..dedfb049e6fc 100644 --- a/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.h +++ b/drivers/media/platform/samsung/exynos-mfc/base/mfc_utils.h @@ -12,7 +12,84 @@ #ifndef __MFC_UTILS_H #define __MFC_UTILS_H __FILE__ -#include "mfc_common.h" +#include "mfc_rate_calculate.h" +#include "mfc_format.h" + +/* bit operation */ +#define mfc_clear_bits(reg, mask, shift) ((reg) &= ~((mask) << (shift))) +#define mfc_set_bits(reg, mask, shift, value) ((reg) |= ((value) & (mask)) << (shift)) +#define mfc_clear_set_bits(reg, mask, shift, value) \ + do { \ + typeof(shift) s = shift; \ + typeof(mask) m = mask; \ + (reg) &= ~(m << s); \ + (reg) |= ((value) & m) << s; \ + } while (0) + +#define mfc_get_upper(x) (((unsigned long)(x) >> 32) & U32_MAX) +#define mfc_get_lower(x) ((x) & U32_MAX) + +#define MFC_FPS(x) ((x) / 1000) + +static inline void mfc_set_bit(int num, struct mfc_bits *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + __set_bit(num, &data->bits); + spin_unlock_irqrestore(&data->lock, flags); +} + +static inline void mfc_clear_bit(int num, struct mfc_bits *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + __clear_bit(num, &data->bits); + spin_unlock_irqrestore(&data->lock, flags); +} + +static inline int mfc_is_all_bits_cleared(struct mfc_bits *data) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&data->lock, flags); + ret = ((data->bits) == 0) ? 1 : 0; + spin_unlock_irqrestore(&data->lock, flags); + return ret; +} + +static inline void mfc_clear_all_bits(struct mfc_bits *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + data->bits = 0; + spin_unlock_irqrestore(&data->lock, flags); +} + +static inline unsigned long mfc_get_bits(struct mfc_bits *data) +{ + unsigned long flags; + unsigned long ret; + + spin_lock_irqsave(&data->lock, flags); + ret = data->bits; + spin_unlock_irqrestore(&data->lock, flags); + return ret; +} + +static inline void mfc_create_bits(struct mfc_bits *data) +{ + spin_lock_init(&data->lock); + mfc_clear_all_bits(data); +} + +static inline void mfc_delete_bits(struct mfc_bits *data) +{ + mfc_clear_all_bits(data); +} static inline void mfc_core_clean_dev_int_flags(struct mfc_core *core) { @@ -65,6 +142,7 @@ static inline void mfc_core_change_fw_state(struct mfc_core *core, prev_stat, core->fw.status, set ? "set" : "clear", state); mfc_core_debug(2, "[F/W] normal status: %#x -> %#x (%s: %#x)\n", prev_stat, core->fw.status, set ? "set" : "clear", state); + } static inline enum mfc_node_type mfc_get_node_type(struct file *file) @@ -121,6 +199,27 @@ static inline void mfc_clear_mb_flag(struct mfc_buf *mfc_buf) mfc_buf->flag = 0; } +static inline void mfc_set_mb_flag(struct mfc_buf *mfc_buf, enum mfc_mb_flag f) +{ + mfc_buf->flag |= BIT(f); +} + +static inline int mfc_check_mb_flag(struct mfc_buf *mfc_buf, enum mfc_mb_flag f) +{ + if (mfc_buf->flag & BIT(f)) + return 1; + + return 0; +} + +int mfc_check_vb_with_fmt(struct mfc_fmt *fmt, struct vb2_buffer *vb); +int mfc_check_resolution(struct mfc_ctx *ctx); +void mfc_set_linear_stride_size(struct mfc_ctx *ctx, struct mfc_raw_info *raw, struct mfc_fmt *fmt); +void mfc_dec_calc_dpb_size(struct mfc_ctx *ctx, struct mfc_raw_info *raw, struct mfc_fmt *fmt); +void mfc_calc_base_addr(struct mfc_ctx *ctx, struct vb2_buffer *vb, struct mfc_fmt *fmt); +void mfc_set_view_buf_info(struct mfc_ctx *ctx, int mem_planes, + int num_fd_depth_map, int num_fd_sub_view_meta); + static inline u32 mfc_dec_get_strm_size(struct mfc_ctx *ctx, struct mfc_buf *src_mb) { struct vb2_plane *vb_plane; @@ -157,6 +256,41 @@ static inline u32 mfc_dec_get_strm_size(struct mfc_ctx *ctx, struct mfc_buf *src return strm_size; } + +static inline int mfc_dec_get_strm_offset(struct mfc_ctx *ctx, struct mfc_buf *src_mb) +{ + struct vb2_plane *vb_plane; + struct mfc_dec *dec = ctx->dec_priv; + unsigned int offset; + + vb_plane = &src_mb->vb.vb2_buf.planes[0]; + offset = vb_plane->data_offset; + if (dec->consumed) + offset += dec->consumed; + + mfc_ctx_debug(2, "[STREAM] offset: %d (bytesused %d, data_offset %d, consumed %d)\n", + offset, vb_plane->bytesused, vb_plane->data_offset, dec->consumed); + + return offset; +} + +static inline int mfc_dec_status_decoding(unsigned int dst_frame_status) +{ + if (dst_frame_status == MFC_REG_DEC_STATUS_DECODING_DISPLAY || + dst_frame_status == MFC_REG_DEC_STATUS_DECODING_ONLY) + return 1; + return 0; +} + +static inline int mfc_dec_status_display(unsigned int dst_frame_status) +{ + if (dst_frame_status == MFC_REG_DEC_STATUS_DISPLAY_ONLY || + dst_frame_status == MFC_REG_DEC_STATUS_DECODING_DISPLAY) + return 1; + + return 0; +} + /* Meerkat interval */ #define MEERKAT_TICK_INTERVAL 1000 /* After how many executions meerkat should assume lock up */ @@ -190,6 +324,20 @@ static inline void mfc_core_idle_update_hw_run(struct mfc_core *core, spin_unlock_irqrestore(&core->dev->idle_bits_lock, flags); } +static inline void mfc_idle_update_queued(struct mfc_dev *dev, + struct mfc_ctx *ctx) +{ + unsigned long flags; + int bits; + + spin_lock_irqsave(&dev->idle_bits_lock, flags); + + bits = atomic_read(&dev->queued_bits); + atomic_set(&dev->queued_bits, (bits | BIT(ctx->num))); + + spin_unlock_irqrestore(&dev->idle_bits_lock, flags); +} + static inline void mfc_core_change_idle_mode(struct mfc_core *core, enum mfc_idle_mode idle_mode) { @@ -206,4 +354,68 @@ static inline void mfc_ctx_change_idle_mode(struct mfc_ctx *ctx, MFC_TRACE_CTX("**[c:%d] idle mode : %d\n", ctx->num, idle_mode); ctx->idle_mode = idle_mode; } + +static inline void mfc_print_ctx_info(struct mfc_ctx *ctx) +{ + struct mfc_fmt *codec = NULL; + struct mfc_fmt *fmt = NULL; + + if (ctx->type == MFCINST_DECODER) { + codec = ctx->src_fmt; + fmt = ctx->dst_fmt; + } else { + codec = ctx->dst_fmt; + fmt = ctx->src_fmt; + } + + if (!codec) + codec = &mfc_formats[0]; + if (!fmt) + fmt = &mfc_formats[0]; + mfc_ctx_info("- %s%s, %s, %dx%d %lu fps(ts %lu fps, op %lu fps, rt %lu fps)", + codec->name, + ctx->multi_view_enable ? "(MV-HEVC)" : "", + fmt->name, + ctx->img_width, ctx->img_height, + MFC_FPS(ctx->framerate), + MFC_FPS(ctx->last_framerate), + MFC_FPS(ctx->operating_framerate), + MFC_FPS(mfc_rate_get_rt_framerate(ctx, ctx->rt))); + mfc_ctx_info("mb %lu(%d%%), main core %d, op_mode %d(stream %d), rt %d\n", + ctx->weighted_mb, ctx->load, + ctx->op_core_num[MFC_CORE_MAIN], + ctx->op_mode, ctx->stream_op_mode, ctx->rt); +} + +static inline void mfc_show_ctx_info(struct mfc_ctx *ctx) +{ + struct mfc_fmt *codec = NULL; + struct mfc_fmt *fmt = NULL; + + if (ctx->type == MFCINST_DECODER) { + codec = ctx->src_fmt; + fmt = ctx->dst_fmt; + } else { + codec = ctx->dst_fmt; + fmt = ctx->src_fmt; + } + + if (!codec) + codec = &mfc_formats[0]; + if (!fmt) + fmt = &mfc_formats[0]; + + mfc_ctx_debug(3, "- %s, %s, %dx%d %lu fps(ts %lu fps, op %lu fps, rt %lu fps)", + codec->name, + fmt->name, + ctx->img_width, ctx->img_height, + MFC_FPS(ctx->framerate), + MFC_FPS(ctx->last_framerate), + MFC_FPS(ctx->operating_framerate), + MFC_FPS(mfc_rate_get_rt_framerate(ctx, ctx->rt))); + mfc_ctx_debug(3, "mb %lu(%d%%), main core %d, op_mode %d(stream %d), rt %d\n", + ctx->weighted_mb, ctx->load, + ctx->op_core_num[MFC_CORE_MAIN], + ctx->op_mode, ctx->stream_op_mode, ctx->rt); +} #endif /* __MFC_UTILS_H */ -- 2.34.1
