23b65c3a24
git-subtree-dir: techpack/display git-subtree-mainline:2d46776923
git-subtree-split:64f31403b4
Change-Id: I7f4c42a3ba6b11a8db861cdd171a52d8f58f2e06
3529 lines
88 KiB
C
3529 lines
88 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2015-2020, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/file.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/dma-direction.h>
|
|
#include <linux/qcom_scm.h>
|
|
#include <soc/qcom/secure_buffer.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <uapi/linux/sched/types.h>
|
|
#include <linux/qtee_shmbridge.h>
|
|
|
|
#include "sde_rotator_base.h"
|
|
#include "sde_rotator_core.h"
|
|
#include "sde_rotator_dev.h"
|
|
#include "sde_rotator_util.h"
|
|
#include "sde_rotator_io_util.h"
|
|
#include "sde_rotator_smmu.h"
|
|
#include "sde_rotator_r1.h"
|
|
#include "sde_rotator_r3.h"
|
|
#include "sde_rotator_trace.h"
|
|
#include "sde_rotator_debug.h"
|
|
|
|
|
|
/* Rotator device id to be used in SCM call */
|
|
#define SDE_ROTATOR_DEVICE 21
|
|
|
|
/*
|
|
* SCM call function id to be used for switching between secure and non
|
|
* secure context
|
|
*/
|
|
#define MEM_PROTECT_SD_CTRL_SWITCH 0x18
|
|
|
|
/* waiting for hw time out, 3 vsync for 30fps*/
|
|
#define ROT_HW_ACQUIRE_TIMEOUT_IN_MS 100
|
|
|
|
/* waiting for inline hw start */
|
|
#define ROT_INLINE_START_TIMEOUT_IN_MS (10000 + 500)
|
|
|
|
/* default pixel per clock ratio */
|
|
#define ROT_PIXEL_PER_CLK_NUMERATOR 36
|
|
#define ROT_PIXEL_PER_CLK_DENOMINATOR 10
|
|
#define ROT_FUDGE_FACTOR_NUMERATOR 105
|
|
#define ROT_FUDGE_FACTOR_DENOMINATOR 100
|
|
#define ROT_OVERHEAD_NUMERATOR 27
|
|
#define ROT_OVERHEAD_DENOMINATOR 10000
|
|
|
|
/* Minimum Rotator Clock value */
|
|
#define ROT_MIN_ROT_CLK 20000000
|
|
|
|
/* default minimum bandwidth vote */
|
|
#define ROT_ENABLE_BW_VOTE 64000
|
|
/*
|
|
* Max rotator hw blocks possible. Used for upper array limits instead of
|
|
* alloc and freeing small array
|
|
*/
|
|
#define ROT_MAX_HW_BLOCKS 2
|
|
|
|
#define BUS_VOTE_19_MHZ 153600000
|
|
|
|
#define ROT_HAS_UBWC(caps) (test_bit(SDE_CAPS_UBWC_2, caps) ||\
|
|
test_bit(SDE_CAPS_UBWC_3, caps) ||\
|
|
test_bit(SDE_CAPS_UBWC_4, caps))
|
|
|
|
/* forward prototype */
|
|
static int sde_rotator_update_perf(struct sde_rot_mgr *mgr);
|
|
|
|
static int sde_rotator_bus_scale_set_quota(struct sde_rot_bus_data_type *bus,
|
|
u64 quota)
|
|
{
|
|
int ret = 0, i = 0, j = 0;
|
|
u64 ab = 0;
|
|
|
|
if (!bus || !bus->data_paths_cnt) {
|
|
SDEROT_DBG("bus scaling not register\n");
|
|
return 0;
|
|
}
|
|
|
|
if (bus->curr_quota_val == quota) {
|
|
SDEROT_DBG("bw request already requested\n");
|
|
return 0;
|
|
}
|
|
|
|
SDEROT_EVTLOG(quota);
|
|
SDEROT_DBG("quota=%llu\n", quota);
|
|
ATRACE_BEGIN("msm_bus_scale_req_rot");
|
|
ab = div_u64(quota, bus->data_paths_cnt);
|
|
|
|
for (i = 0; i < bus->data_paths_cnt; i++) {
|
|
if (bus->data_bus_hdl[i]) {
|
|
ret = icc_set_bw(bus->data_bus_hdl[i], Bps_to_icc(ab),
|
|
Bps_to_icc(ab));
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
ATRACE_END("msm_bus_scale_req_rot");
|
|
bus->curr_quota_val = quota;
|
|
|
|
return 0;
|
|
err:
|
|
ab = div_u64(bus->curr_quota_val, bus->data_paths_cnt);
|
|
for (j = 0; j < i; j++)
|
|
icc_set_bw(bus->data_bus_hdl[j], Bps_to_icc(ab),
|
|
Bps_to_icc(ab));
|
|
ATRACE_END("msm_bus_scale_req_rot");
|
|
pr_err("failed to set data bus quota %llu\n", quota);
|
|
|
|
if (!bus->curr_quota_val)
|
|
pr_err("rotator: data bus was set to 0\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_enable_reg_bus(struct sde_rot_mgr *mgr, u64 quota)
|
|
{
|
|
int ret = 0, changed = 0;
|
|
u32 usecase_ndx = 0;
|
|
const struct sde_rot_bus_data *reg_bus_value = NULL;
|
|
|
|
if (!mgr || !mgr->reg_bus.data_paths_cnt)
|
|
return 0;
|
|
|
|
if (quota)
|
|
usecase_ndx = VOTE_INDEX_76_MHZ;
|
|
|
|
if (usecase_ndx != mgr->reg_bus.curr_bw_uc_idx)
|
|
changed++;
|
|
|
|
SDEROT_DBG("%s, changed=%d register bus %s\n", __func__, changed,
|
|
quota ? "Enable":"Disable");
|
|
|
|
if (changed) {
|
|
ATRACE_BEGIN("msm_bus_scale_req_rot_reg");
|
|
|
|
reg_bus_value = sde_get_rot_reg_bus_value(usecase_ndx);
|
|
ret = icc_set_bw(mgr->reg_bus.data_bus_hdl[0],
|
|
reg_bus_value->ab, reg_bus_value->ib);
|
|
ATRACE_END("msm_bus_scale_req_rot_reg");
|
|
|
|
}
|
|
if (ret) {
|
|
pr_err("rotator: set reg bus failed ab=%llu, lb=%llu\n",
|
|
reg_bus_value->ab, reg_bus_value->ib);
|
|
if (mgr->reg_bus.curr_bw_uc_idx == VOTE_INDEX_DISABLE)
|
|
pr_err("rotator: reg bus was disabled\n");
|
|
} else {
|
|
mgr->reg_bus.curr_bw_uc_idx = usecase_ndx;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*
|
|
* Clock rate of all open sessions working a particular hw block
|
|
* are added together to get the required rate for that hw block.
|
|
* The max of each hw block becomes the final clock rate voted for
|
|
*/
|
|
static unsigned long sde_rotator_clk_rate_calc(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
struct sde_rot_perf *perf;
|
|
unsigned long clk_rate[ROT_MAX_HW_BLOCKS] = {0};
|
|
unsigned long total_clk_rate = 0;
|
|
int i, wb_idx;
|
|
|
|
list_for_each_entry(perf, &private->perf_list, list) {
|
|
bool rate_accounted_for = false;
|
|
/*
|
|
* If there is one session that has two work items across
|
|
* different hw blocks rate is accounted for in both blocks.
|
|
*/
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
if (perf->work_distribution[i]) {
|
|
clk_rate[i] += perf->clk_rate;
|
|
rate_accounted_for = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sessions that are open but not distributed on any hw block
|
|
* Still need to be accounted for. Rate is added to last known
|
|
* wb idx.
|
|
*/
|
|
wb_idx = perf->last_wb_idx;
|
|
if ((!rate_accounted_for) && (wb_idx >= 0) &&
|
|
(wb_idx < mgr->queue_count))
|
|
clk_rate[wb_idx] += perf->clk_rate;
|
|
}
|
|
|
|
for (i = 0; i < mgr->queue_count; i++)
|
|
total_clk_rate = max(clk_rate[i], total_clk_rate);
|
|
|
|
SDEROT_DBG("Total clk rate calc=%lu\n", total_clk_rate);
|
|
return total_clk_rate;
|
|
}
|
|
|
|
static struct clk *sde_rotator_get_clk(struct sde_rot_mgr *mgr, u32 clk_idx)
|
|
{
|
|
if (clk_idx >= mgr->num_rot_clk) {
|
|
SDEROT_ERR("Invalid clk index:%u", clk_idx);
|
|
return NULL;
|
|
}
|
|
|
|
return mgr->rot_clk[clk_idx].clk;
|
|
}
|
|
|
|
static void sde_rotator_set_clk_rate(struct sde_rot_mgr *mgr,
|
|
unsigned long rate, u32 clk_idx)
|
|
{
|
|
unsigned long clk_rate;
|
|
struct clk *clk = sde_rotator_get_clk(mgr, clk_idx);
|
|
int ret;
|
|
|
|
if (clk) {
|
|
clk_rate = clk_round_rate(clk, rate);
|
|
if (IS_ERR_VALUE(clk_rate)) {
|
|
SDEROT_ERR("unable to round rate err=%ld\n", clk_rate);
|
|
} else {
|
|
ret = clk_set_rate(clk, clk_rate);
|
|
if (ret < 0)
|
|
SDEROT_ERR("clk_set_rate failed, err:%d\n",
|
|
ret);
|
|
else
|
|
SDEROT_DBG("rotator clk rate=%lu\n", clk_rate);
|
|
}
|
|
} else {
|
|
SDEROT_ERR("rotator clk not setup properly\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update clock according to all open files on rotator block.
|
|
*/
|
|
static int sde_rotator_update_clk(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct sde_rot_file_private *priv;
|
|
unsigned long clk_rate, total_clk_rate;
|
|
|
|
total_clk_rate = 0;
|
|
list_for_each_entry(priv, &mgr->file_list, list) {
|
|
clk_rate = sde_rotator_clk_rate_calc(mgr, priv);
|
|
total_clk_rate += clk_rate;
|
|
}
|
|
|
|
SDEROT_DBG("core_clk %lu\n", total_clk_rate);
|
|
ATRACE_INT("core_clk", total_clk_rate);
|
|
sde_rotator_set_clk_rate(mgr, total_clk_rate, SDE_ROTATOR_CLK_MDSS_ROT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_footswitch_ctrl(struct sde_rot_mgr *mgr, bool on)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (mgr->regulator_enable == on) {
|
|
SDEROT_DBG("Regulators already in selected mode on=%d\n", on);
|
|
return 0;
|
|
}
|
|
|
|
SDEROT_EVTLOG(on);
|
|
SDEROT_DBG("%s: rotator regulators\n", on ? "Enable" : "Disable");
|
|
|
|
if (on) {
|
|
mgr->minimum_bw_vote = mgr->enable_bw_vote;
|
|
sde_rotator_update_perf(mgr);
|
|
}
|
|
|
|
if (mgr->ops_hw_pre_pmevent)
|
|
mgr->ops_hw_pre_pmevent(mgr, on);
|
|
|
|
if (!sde_rot_mgr_pd_enabled(mgr))
|
|
ret = sde_rot_enable_vreg(mgr->module_power.vreg_config,
|
|
mgr->module_power.num_vreg, on);
|
|
if (ret) {
|
|
pr_err("rotator regulator failed to %s ret:%d client:%d\n",
|
|
on ? "enable" : "disable", ret,
|
|
sde_rot_mgr_pd_enabled(mgr));
|
|
return ret;
|
|
}
|
|
|
|
if (mgr->ops_hw_post_pmevent)
|
|
mgr->ops_hw_post_pmevent(mgr, on);
|
|
|
|
if (!on) {
|
|
mgr->minimum_bw_vote = 0;
|
|
sde_rotator_update_perf(mgr);
|
|
}
|
|
|
|
mgr->regulator_enable = on;
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_enable_clk(struct sde_rot_mgr *mgr, int clk_idx)
|
|
{
|
|
struct clk *clk;
|
|
int ret = 0;
|
|
|
|
clk = sde_rotator_get_clk(mgr, clk_idx);
|
|
if (clk) {
|
|
ret = clk_prepare_enable(clk);
|
|
if (ret)
|
|
SDEROT_ERR("enable failed clk_idx %d\n", clk_idx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sde_rotator_disable_clk(struct sde_rot_mgr *mgr, int clk_idx)
|
|
{
|
|
struct clk *clk;
|
|
|
|
clk = sde_rotator_get_clk(mgr, clk_idx);
|
|
if (clk)
|
|
clk_disable_unprepare(clk);
|
|
}
|
|
|
|
int sde_rotator_clk_ctrl(struct sde_rot_mgr *mgr, int enable)
|
|
{
|
|
int ret = 0;
|
|
int changed = 0;
|
|
int i = 0, bus_cnt = 0;
|
|
|
|
if (enable) {
|
|
if (mgr->rot_enable_clk_cnt == 0)
|
|
changed++;
|
|
mgr->rot_enable_clk_cnt++;
|
|
} else {
|
|
if (mgr->rot_enable_clk_cnt) {
|
|
mgr->rot_enable_clk_cnt--;
|
|
if (mgr->rot_enable_clk_cnt == 0)
|
|
changed++;
|
|
} else {
|
|
SDEROT_ERR("Can not be turned off\n");
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
SDEROT_EVTLOG(enable);
|
|
SDEROT_DBG("Rotator clk %s\n", enable ? "enable" : "disable");
|
|
|
|
if (enable) {
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MNOC_AHB);
|
|
if (ret)
|
|
goto error_mnoc_ahb;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_GCC_AHB);
|
|
if (ret)
|
|
goto error_gcc_ahb;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_GCC_AXI);
|
|
if (ret)
|
|
goto error_gcc_axi;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_AHB);
|
|
if (ret)
|
|
goto error_mdss_ahb;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_AXI);
|
|
if (ret)
|
|
goto error_mdss_axi;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_ROT);
|
|
if (ret)
|
|
goto error_mdss_rot;
|
|
ret = sde_rotator_enable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_ROT_SUB);
|
|
if (ret)
|
|
goto error_rot_sub;
|
|
|
|
/* Active+Sleep */
|
|
if (mgr->data_bus.bus_active_only) {
|
|
bus_cnt = mgr->data_bus.data_paths_cnt;
|
|
for (i = 0; i < bus_cnt; i++) {
|
|
icc_set_tag(
|
|
mgr->data_bus.data_bus_hdl[i],
|
|
(QCOM_ICC_TAG_ACTIVE_ONLY |
|
|
QCOM_ICC_TAG_SLEEP));
|
|
}
|
|
}
|
|
trace_rot_bw_ao_as_context(0);
|
|
} else {
|
|
sde_rotator_disable_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_ROT_SUB);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_ROT);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_AXI);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_AHB);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_GCC_AXI);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_GCC_AHB);
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MNOC_AHB);
|
|
|
|
/* Active Only */
|
|
if (mgr->data_bus.bus_active_only) {
|
|
bus_cnt = mgr->data_bus.data_paths_cnt;
|
|
for (i = 0; i < bus_cnt; i++) {
|
|
icc_set_tag(
|
|
mgr->data_bus.data_bus_hdl[i],
|
|
QCOM_ICC_TAG_ACTIVE_ONLY);
|
|
}
|
|
}
|
|
|
|
trace_rot_bw_ao_as_context(1);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
error_rot_sub:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_ROT);
|
|
error_mdss_rot:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_AXI);
|
|
error_mdss_axi:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MDSS_AHB);
|
|
error_mdss_ahb:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_GCC_AXI);
|
|
error_gcc_axi:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_GCC_AHB);
|
|
error_gcc_ahb:
|
|
sde_rotator_disable_clk(mgr, SDE_ROTATOR_CLK_MNOC_AHB);
|
|
error_mnoc_ahb:
|
|
return ret;
|
|
}
|
|
|
|
/* sde_rotator_resource_ctrl - control state of power resource
|
|
* @mgr: Pointer to rotator manager
|
|
* @enable: 1 to enable; 0 to disable
|
|
*
|
|
* This function returns 1 if resource is already in the requested state,
|
|
* return 0 if the state is changed successfully, or negative error code
|
|
* if not successful.
|
|
*/
|
|
static int sde_rotator_resource_ctrl(struct sde_rot_mgr *mgr, int enable)
|
|
{
|
|
int ret;
|
|
|
|
if (enable) {
|
|
mgr->res_ref_cnt++;
|
|
ret = pm_runtime_get_sync(&mgr->pdev->dev);
|
|
} else {
|
|
mgr->res_ref_cnt--;
|
|
ret = pm_runtime_put_sync(&mgr->pdev->dev);
|
|
}
|
|
|
|
SDEROT_DBG("%s: res_cnt=%d pm=%d enable=%d\n",
|
|
__func__, mgr->res_ref_cnt, ret, enable);
|
|
ATRACE_INT("res_cnt", mgr->res_ref_cnt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* caller is expected to hold perf->work_dis_lock lock */
|
|
static bool sde_rotator_is_work_pending(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_perf *perf)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
if (perf->work_distribution[i]) {
|
|
SDEROT_DBG("Work is still scheduled to complete\n");
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void sde_rotator_clear_fence(struct sde_rot_entry *entry)
|
|
{
|
|
if (entry->input_fence) {
|
|
SDEROT_EVTLOG(entry->input_fence, 1111);
|
|
SDEROT_DBG("sys_fence_put i:%pK\n", entry->input_fence);
|
|
sde_rotator_put_sync_fence(entry->input_fence);
|
|
entry->input_fence = NULL;
|
|
}
|
|
|
|
/* fence failed to copy to user space */
|
|
if (entry->output_fence) {
|
|
if (entry->fenceq && entry->fenceq->timeline)
|
|
sde_rotator_resync_timeline(entry->fenceq->timeline);
|
|
|
|
SDEROT_EVTLOG(entry->output_fence, 2222);
|
|
SDEROT_DBG("sys_fence_put o:%pK\n", entry->output_fence);
|
|
sde_rotator_put_sync_fence(entry->output_fence);
|
|
entry->output_fence = NULL;
|
|
}
|
|
}
|
|
|
|
static int sde_rotator_signal_output(struct sde_rot_entry *entry)
|
|
{
|
|
struct sde_rot_timeline *rot_timeline;
|
|
|
|
if (!entry->fenceq)
|
|
return -EINVAL;
|
|
|
|
rot_timeline = entry->fenceq->timeline;
|
|
|
|
if (entry->output_signaled) {
|
|
SDEROT_DBG("output already signaled\n");
|
|
return 0;
|
|
}
|
|
|
|
SDEROT_DBG("signal fence s:%d.%d\n", entry->item.session_id,
|
|
entry->item.sequence_id);
|
|
|
|
sde_rotator_inc_timeline(rot_timeline, 1);
|
|
|
|
entry->output_signaled = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_import_buffer(struct sde_layer_buffer *buffer,
|
|
struct sde_mdp_data *data, u32 flags, struct device *dev, bool input)
|
|
{
|
|
int i, ret = 0;
|
|
struct sde_fb_data planes[SDE_ROT_MAX_PLANES];
|
|
int dir = DMA_TO_DEVICE;
|
|
|
|
if (!input)
|
|
dir = DMA_FROM_DEVICE;
|
|
|
|
if (buffer->plane_count > SDE_ROT_MAX_PLANES) {
|
|
SDEROT_ERR("buffer plane_count exceeds MAX_PLANE limit:%d\n",
|
|
buffer->plane_count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->sbuf = buffer->sbuf;
|
|
data->scid = buffer->scid;
|
|
data->writeback = buffer->writeback;
|
|
|
|
memset(planes, 0, sizeof(planes));
|
|
|
|
for (i = 0; i < buffer->plane_count; i++) {
|
|
planes[i].memory_id = buffer->planes[i].fd;
|
|
planes[i].offset = buffer->planes[i].offset;
|
|
planes[i].buffer = buffer->planes[i].buffer;
|
|
planes[i].addr = buffer->planes[i].addr;
|
|
planes[i].len = buffer->planes[i].len;
|
|
}
|
|
|
|
ret = sde_mdp_data_get_and_validate_size(data, planes,
|
|
buffer->plane_count, flags, dev, true, dir, buffer);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_secure_session_ctrl(bool enable)
|
|
{
|
|
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
|
|
uint32_t *sid_info = NULL;
|
|
int ret = 0;
|
|
phys_addr_t mem_addr;
|
|
u64 mem_size;
|
|
u32 vmid;
|
|
struct qtee_shm shm;
|
|
bool qtee_en = qtee_shmbridge_is_enabled();
|
|
|
|
if (test_bit(SDE_CAPS_SEC_ATTACH_DETACH_SMMU, mdata->sde_caps_map)) {
|
|
|
|
if (qtee_en) {
|
|
ret = qtee_shmbridge_allocate_shm(sizeof(uint32_t),
|
|
&shm);
|
|
if (ret)
|
|
return -ENOMEM;
|
|
|
|
sid_info = (uint32_t *) shm.vaddr;
|
|
mem_addr = shm.paddr;
|
|
mem_size = sizeof(uint32_t);
|
|
} else {
|
|
sid_info = kzalloc(sizeof(uint32_t), GFP_KERNEL);
|
|
if (!sid_info)
|
|
return -ENOMEM;
|
|
|
|
mem_addr = virt_to_phys(sid_info);
|
|
mem_size = sizeof(uint32_t);
|
|
}
|
|
|
|
sid_info[0] = mdata->sde_smmu[SDE_IOMMU_DOMAIN_ROT_SECURE].sid;
|
|
|
|
if (!mdata->sec_cam_en && enable) {
|
|
/*
|
|
* Enable secure camera operation
|
|
* Send SCM call to hypervisor to switch the
|
|
* secure_vmid to secure context
|
|
*/
|
|
vmid = VMID_CP_CAMERA_PREVIEW;
|
|
|
|
mdata->sec_cam_en = 1;
|
|
sde_smmu_secure_ctrl(0);
|
|
|
|
ret = qcom_scm_mem_protect_sd_ctrl(SDE_ROTATOR_DEVICE,
|
|
mem_addr, mem_size, vmid);
|
|
if (ret) {
|
|
SDEROT_ERR("qcom_scm_mem_protect ret=%d\n", ret);
|
|
/* failure, attach smmu */
|
|
mdata->sec_cam_en = 0;
|
|
sde_smmu_secure_ctrl(1);
|
|
ret = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
SDEROT_DBG(
|
|
"scm(1) sid0x%x dev0x%llx vmid0x%llx qtee_en%d ret%d\n",
|
|
sid_info[0], SDE_ROTATOR_DEVICE, vmid,
|
|
qtee_en, ret);
|
|
SDEROT_EVTLOG(1, sid_info, sid_info[0], SDE_ROTATOR_DEVICE,
|
|
vmid, qtee_en, ret);
|
|
} else if (mdata->sec_cam_en && !enable) {
|
|
/*
|
|
* Disable secure camera operation
|
|
* Send SCM call to hypervisor to switch the
|
|
* secure_vmid to non-secure context
|
|
*/
|
|
vmid = VMID_CP_PIXEL;
|
|
mdata->sec_cam_en = 0;
|
|
|
|
ret = qcom_scm_mem_protect_sd_ctrl(SDE_ROTATOR_DEVICE,
|
|
mem_addr, mem_size, vmid);
|
|
if (ret)
|
|
SDEROT_ERR("qcom_scm_mem_protect ret=%d\n", ret);
|
|
|
|
SDEROT_DBG(
|
|
"scm(0) sid0x%x dev0x%llx vmid0x%llx qtee_en%d ret%d\n",
|
|
sid_info[0], SDE_ROTATOR_DEVICE, vmid,
|
|
qtee_en, ret);
|
|
|
|
/* force smmu to reattach */
|
|
sde_smmu_secure_ctrl(1);
|
|
|
|
SDEROT_EVTLOG(0, sid_info, sid_info[0], SDE_ROTATOR_DEVICE,
|
|
vmid, qtee_en, ret);
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
end:
|
|
if (qtee_en)
|
|
qtee_shmbridge_free_shm(&shm);
|
|
else
|
|
kfree(sid_info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int sde_rotator_map_and_check_data(struct sde_rot_entry *entry)
|
|
{
|
|
int ret;
|
|
struct sde_layer_buffer *input;
|
|
struct sde_layer_buffer *output;
|
|
struct sde_mdp_format_params *in_fmt, *out_fmt;
|
|
struct sde_mdp_plane_sizes ps;
|
|
bool rotation;
|
|
bool secure;
|
|
|
|
input = &entry->item.input;
|
|
output = &entry->item.output;
|
|
|
|
rotation = (entry->item.flags & SDE_ROTATION_90) ? true : false;
|
|
|
|
ret = sde_smmu_ctrl(1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
secure = (entry->item.flags & SDE_ROTATION_SECURE_CAMERA) ?
|
|
true : false;
|
|
ret = sde_rotator_secure_session_ctrl(secure);
|
|
if (ret) {
|
|
SDEROT_ERR("failed secure session enabling/disabling %d\n",
|
|
ret);
|
|
goto end;
|
|
}
|
|
|
|
in_fmt = sde_get_format_params(input->format);
|
|
if (!in_fmt) {
|
|
SDEROT_ERR("invalid input format:%d\n", input->format);
|
|
ret = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
out_fmt = sde_get_format_params(output->format);
|
|
if (!out_fmt) {
|
|
SDEROT_ERR("invalid output format:%d\n", output->format);
|
|
ret = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* if error during map, the caller will release the data */
|
|
ret = sde_mdp_data_map(&entry->src_buf, true, DMA_TO_DEVICE);
|
|
if (ret) {
|
|
SDEROT_ERR("source buffer mapping failed ret:%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = sde_mdp_data_map(&entry->dst_buf, true, DMA_FROM_DEVICE);
|
|
if (ret) {
|
|
SDEROT_ERR("destination buffer mapping failed ret:%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = sde_mdp_get_plane_sizes(
|
|
in_fmt, input->width, input->height, &ps, 0, rotation);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to get input plane size ret=%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = sde_mdp_data_check(&entry->src_buf, &ps, in_fmt);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to check input data ret=%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = sde_mdp_get_plane_sizes(out_fmt, output->width, output->height,
|
|
&ps, 0, rotation);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to get output plane size ret=%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = sde_mdp_data_check(&entry->dst_buf, &ps, out_fmt);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to check output data ret=%d\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
sde_smmu_ctrl(0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct sde_rot_perf *__sde_rotator_find_session(
|
|
struct sde_rot_file_private *private,
|
|
u32 session_id)
|
|
{
|
|
struct sde_rot_perf *perf, *perf_next;
|
|
bool found = false;
|
|
|
|
list_for_each_entry_safe(perf, perf_next, &private->perf_list, list) {
|
|
if (perf->config.session_id == session_id) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
perf = NULL;
|
|
return perf;
|
|
}
|
|
|
|
static struct sde_rot_perf *sde_rotator_find_session(
|
|
struct sde_rot_file_private *private,
|
|
u32 session_id)
|
|
{
|
|
struct sde_rot_perf *perf;
|
|
|
|
perf = __sde_rotator_find_session(private, session_id);
|
|
return perf;
|
|
}
|
|
|
|
static void sde_rotator_release_data(struct sde_rot_entry *entry)
|
|
{
|
|
SDEROT_EVTLOG(entry->src_buf.p[0].addr, entry->dst_buf.p[0].addr);
|
|
sde_mdp_data_free(&entry->src_buf, true, DMA_TO_DEVICE);
|
|
sde_mdp_data_free(&entry->dst_buf, true, DMA_FROM_DEVICE);
|
|
}
|
|
|
|
static int sde_rotator_import_data(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry *entry)
|
|
{
|
|
int ret;
|
|
struct sde_layer_buffer *input;
|
|
struct sde_layer_buffer *output;
|
|
u32 flag = 0;
|
|
|
|
input = &entry->item.input;
|
|
output = &entry->item.output;
|
|
|
|
if (entry->item.flags & SDE_ROTATION_SECURE)
|
|
flag = SDE_SECURE_OVERLAY_SESSION;
|
|
|
|
if (entry->item.flags & SDE_ROTATION_EXT_DMA_BUF)
|
|
flag |= SDE_ROT_EXT_DMA_BUF;
|
|
|
|
if (entry->item.flags & SDE_ROTATION_EXT_IOVA)
|
|
flag |= SDE_ROT_EXT_IOVA;
|
|
|
|
if (entry->item.flags & SDE_ROTATION_SECURE_CAMERA)
|
|
flag |= SDE_SECURE_CAMERA_SESSION;
|
|
|
|
ret = sde_rotator_import_buffer(input, &entry->src_buf, flag,
|
|
&mgr->pdev->dev, true);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to import input buffer ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* driver assumes output buffer is ready to be written
|
|
* immediately
|
|
*/
|
|
ret = sde_rotator_import_buffer(output, &entry->dst_buf, flag,
|
|
&mgr->pdev->dev, false);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to import output buffer ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_require_reconfiguration - check if reconfiguration is required
|
|
* @mgr: Pointer to rotator manager
|
|
* @hw: Pointer to rotator hw resource
|
|
* @entry: Pointer to next rotation entry
|
|
*
|
|
* Parameters are validated by caller.
|
|
*/
|
|
static int sde_rotator_require_reconfiguration(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry)
|
|
{
|
|
/* OT setting change may impact queued entries */
|
|
if (entry->perf && (entry->perf->rdot_limit != mgr->rdot_limit ||
|
|
entry->perf->wrot_limit != mgr->wrot_limit))
|
|
return true;
|
|
|
|
/* sbuf mode is exclusive and may impact queued entries */
|
|
if (!mgr->sbuf_ctx && entry->perf && entry->perf->config.output.sbuf)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_is_hw_idle - check if hw block is not processing request
|
|
* @mgr: Pointer to rotator manager
|
|
* @hw: Pointer to rotator hw resource
|
|
*
|
|
* Parameters are validated by caller.
|
|
*/
|
|
static int sde_rotator_is_hw_idle(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_hw_resource *hw)
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
* Wait until all queues are idle in order to update global
|
|
* setting such as VBIF QoS. This check can be relaxed if global
|
|
* settings can be updated individually by entries already
|
|
* queued in hw queue, i.e. REGDMA can update VBIF directly.
|
|
*/
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
struct sde_rot_hw_resource *hw_res = mgr->commitq[i].hw;
|
|
|
|
if (hw_res && atomic_read(&hw_res->num_active))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_is_hw_available - check if hw is available for the given entry
|
|
* @mgr: Pointer to rotator manager
|
|
* @hw: Pointer to rotator hw resource
|
|
* @entry: Pointer to rotation entry
|
|
*
|
|
* Parameters are validated by caller.
|
|
*/
|
|
static int sde_rotator_is_hw_available(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry)
|
|
{
|
|
/*
|
|
* Wait until hw is idle if reconfiguration is required; otherwise,
|
|
* wait until free queue entry is available
|
|
*/
|
|
if (sde_rotator_require_reconfiguration(mgr, hw, entry)) {
|
|
SDEROT_DBG(
|
|
"wait4idle active=%d pending=%d rdot:%u/%u wrot:%u/%u s:%d.%d\n",
|
|
atomic_read(&hw->num_active), hw->pending_count,
|
|
mgr->rdot_limit, entry->perf->rdot_limit,
|
|
mgr->wrot_limit, entry->perf->wrot_limit,
|
|
entry->item.session_id,
|
|
entry->item.sequence_id);
|
|
return sde_rotator_is_hw_idle(mgr, hw);
|
|
} else if (mgr->sbuf_ctx && mgr->sbuf_ctx != entry->private) {
|
|
SDEROT_DBG("wait until sbuf mode is off\n");
|
|
return false;
|
|
} else {
|
|
return (atomic_read(&hw->num_active) < hw->max_active);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_req_wait_for_idle - wait for hw for a request to be idle
|
|
* @mgr: Pointer to rotator manager
|
|
* @req: Pointer to rotation request
|
|
*/
|
|
static void sde_rotator_req_wait_for_idle(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct sde_rot_queue *queue;
|
|
struct sde_rot_hw_resource *hw;
|
|
int i, ret;
|
|
|
|
if (!mgr || !req) {
|
|
SDEROT_ERR("invalid params\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
queue = req->entries[i].commitq;
|
|
if (!queue || !queue->hw)
|
|
continue;
|
|
hw = queue->hw;
|
|
while (atomic_read(&hw->num_active) > 1) {
|
|
sde_rot_mgr_unlock(mgr);
|
|
ret = wait_event_timeout(hw->wait_queue,
|
|
atomic_read(&hw->num_active) <= 1,
|
|
msecs_to_jiffies(mgr->hwacquire_timeout));
|
|
sde_rot_mgr_lock(mgr);
|
|
if (!ret) {
|
|
SDEROT_ERR(
|
|
"timeout waiting for hw idle, a:%d\n",
|
|
atomic_read(&hw->num_active));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_get_hw_resource - block waiting for hw availability or timeout
|
|
* @queue: Pointer to rotator queue
|
|
* @entry: Pointer to rotation entry
|
|
*/
|
|
static struct sde_rot_hw_resource *sde_rotator_get_hw_resource(
|
|
struct sde_rot_queue *queue, struct sde_rot_entry *entry)
|
|
{
|
|
struct sde_rot_hw_resource *hw;
|
|
struct sde_rot_mgr *mgr;
|
|
int ret;
|
|
|
|
if (!queue || !entry || !queue->hw) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return NULL;
|
|
}
|
|
|
|
hw = queue->hw;
|
|
mgr = entry->private->mgr;
|
|
|
|
WARN_ON(atomic_read(&hw->num_active) > hw->max_active);
|
|
while (!sde_rotator_is_hw_available(mgr, hw, entry)) {
|
|
sde_rot_mgr_unlock(mgr);
|
|
ret = wait_event_timeout(hw->wait_queue,
|
|
sde_rotator_is_hw_available(mgr, hw, entry),
|
|
msecs_to_jiffies(mgr->hwacquire_timeout));
|
|
sde_rot_mgr_lock(mgr);
|
|
if (!ret) {
|
|
SDEROT_ERR(
|
|
"timeout waiting for hw resource, a:%d p:%d\n",
|
|
atomic_read(&hw->num_active),
|
|
hw->pending_count);
|
|
SDEROT_EVTLOG(entry->item.session_id,
|
|
entry->item.sequence_id,
|
|
atomic_read(&hw->num_active),
|
|
hw->pending_count,
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
return NULL;
|
|
}
|
|
}
|
|
atomic_inc(&hw->num_active);
|
|
SDEROT_EVTLOG(atomic_read(&hw->num_active), hw->pending_count,
|
|
mgr->rdot_limit, entry->perf->rdot_limit,
|
|
mgr->wrot_limit, entry->perf->wrot_limit,
|
|
entry->item.session_id, entry->item.sequence_id);
|
|
SDEROT_DBG("active=%d pending=%d rdot=%u/%u wrot=%u/%u s:%d.%d\n",
|
|
atomic_read(&hw->num_active), hw->pending_count,
|
|
mgr->rdot_limit, entry->perf->rdot_limit,
|
|
mgr->wrot_limit, entry->perf->wrot_limit,
|
|
entry->item.session_id, entry->item.sequence_id);
|
|
mgr->rdot_limit = entry->perf->rdot_limit;
|
|
mgr->wrot_limit = entry->perf->wrot_limit;
|
|
|
|
if (!mgr->sbuf_ctx && entry->perf->config.output.sbuf) {
|
|
SDEROT_DBG("acquire sbuf s:%d.%d\n", entry->item.session_id,
|
|
entry->item.sequence_id);
|
|
SDEROT_EVTLOG(entry->item.session_id, entry->item.sequence_id);
|
|
mgr->sbuf_ctx = entry->private;
|
|
}
|
|
|
|
return hw;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_put_hw_resource - return hw resource and wake up waiting clients
|
|
* @queue: Pointer to rotator queue
|
|
* @entry: Pointer to rotation entry
|
|
* @hw: Pointer to hw resource to be returned
|
|
*/
|
|
static void sde_rotator_put_hw_resource(struct sde_rot_queue *queue,
|
|
struct sde_rot_entry *entry, struct sde_rot_hw_resource *hw)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
int i;
|
|
|
|
if (!queue || !entry || !hw) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
mgr = entry->private->mgr;
|
|
|
|
WARN_ON(atomic_read(&hw->num_active) < 1);
|
|
if (!atomic_add_unless(&hw->num_active, -1, 0))
|
|
SDEROT_ERR("underflow active=%d pending=%d s:%d.%d\n",
|
|
atomic_read(&hw->num_active), hw->pending_count,
|
|
entry->item.session_id, entry->item.sequence_id);
|
|
/*
|
|
* Wake up all queues in case any entry is waiting for hw idle,
|
|
* in order to update global settings, such as VBIF QoS.
|
|
* This can be relaxed to the given hw resource if global
|
|
* settings can be updated individually by entries already
|
|
* queued in hw queue.
|
|
*/
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
struct sde_rot_hw_resource *hw_res = mgr->commitq[i].hw;
|
|
|
|
if (hw_res)
|
|
wake_up(&hw_res->wait_queue);
|
|
}
|
|
SDEROT_EVTLOG(atomic_read(&hw->num_active), hw->pending_count,
|
|
entry->item.session_id, entry->item.sequence_id);
|
|
SDEROT_DBG("active=%d pending=%d s:%d.%d\n",
|
|
atomic_read(&hw->num_active), hw->pending_count,
|
|
entry->item.session_id, entry->item.sequence_id);
|
|
}
|
|
|
|
/*
|
|
* caller will need to call sde_rotator_deinit_queue when
|
|
* the function returns error
|
|
*/
|
|
static int sde_rotator_init_queue(struct sde_rot_mgr *mgr)
|
|
{
|
|
int i, size, ret = 0;
|
|
char name[32];
|
|
struct sched_param param = { .sched_priority = 5 };
|
|
|
|
size = sizeof(struct sde_rot_queue) * mgr->queue_count;
|
|
mgr->commitq = devm_kzalloc(mgr->device, size, GFP_KERNEL);
|
|
if (!mgr->commitq)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
snprintf(name, sizeof(name), "rot_commitq_%d_%d",
|
|
mgr->device->id, i);
|
|
SDEROT_DBG("work queue name=%s\n", name);
|
|
kthread_init_worker(&mgr->commitq[i].rot_kw);
|
|
mgr->commitq[i].rot_thread = kthread_run(kthread_worker_fn,
|
|
&mgr->commitq[i].rot_kw, name);
|
|
if (IS_ERR(mgr->commitq[i].rot_thread)) {
|
|
ret = -EPERM;
|
|
mgr->commitq[i].rot_thread = NULL;
|
|
break;
|
|
}
|
|
|
|
ret = sched_setscheduler(mgr->commitq[i].rot_thread,
|
|
SCHED_FIFO, ¶m);
|
|
if (ret) {
|
|
SDEROT_ERR(
|
|
"failed to set kthread priority for commitq %d\n",
|
|
ret);
|
|
break;
|
|
}
|
|
|
|
/* timeline not used */
|
|
mgr->commitq[i].timeline = NULL;
|
|
}
|
|
|
|
size = sizeof(struct sde_rot_queue) * mgr->queue_count;
|
|
mgr->doneq = devm_kzalloc(mgr->device, size, GFP_KERNEL);
|
|
if (!mgr->doneq)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
snprintf(name, sizeof(name), "rot_doneq_%d_%d",
|
|
mgr->device->id, i);
|
|
SDEROT_DBG("work queue name=%s\n", name);
|
|
kthread_init_worker(&mgr->doneq[i].rot_kw);
|
|
mgr->doneq[i].rot_thread = kthread_run(kthread_worker_fn,
|
|
&mgr->doneq[i].rot_kw, name);
|
|
if (IS_ERR(mgr->doneq[i].rot_thread)) {
|
|
ret = -EPERM;
|
|
mgr->doneq[i].rot_thread = NULL;
|
|
break;
|
|
}
|
|
|
|
ret = sched_setscheduler(mgr->doneq[i].rot_thread,
|
|
SCHED_FIFO, ¶m);
|
|
if (ret) {
|
|
SDEROT_ERR(
|
|
"failed to set kthread priority for doneq %d\n",
|
|
ret);
|
|
break;
|
|
}
|
|
|
|
/* timeline not used */
|
|
mgr->doneq[i].timeline = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void sde_rotator_deinit_queue(struct sde_rot_mgr *mgr)
|
|
{
|
|
int i;
|
|
|
|
if (mgr->commitq) {
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
if (mgr->commitq[i].rot_thread) {
|
|
kthread_flush_worker(&mgr->commitq[i].rot_kw);
|
|
kthread_stop(mgr->commitq[i].rot_thread);
|
|
}
|
|
}
|
|
devm_kfree(mgr->device, mgr->commitq);
|
|
mgr->commitq = NULL;
|
|
}
|
|
if (mgr->doneq) {
|
|
for (i = 0; i < mgr->queue_count; i++) {
|
|
if (mgr->doneq[i].rot_thread) {
|
|
kthread_flush_worker(&mgr->doneq[i].rot_kw);
|
|
kthread_stop(mgr->doneq[i].rot_thread);
|
|
}
|
|
}
|
|
devm_kfree(mgr->device, mgr->doneq);
|
|
mgr->doneq = NULL;
|
|
}
|
|
mgr->queue_count = 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_assign_queue() - Function assign rotation work onto hw
|
|
* @mgr: Rotator manager.
|
|
* @entry: Contains details on rotator work item being requested
|
|
* @private: Private struct used for access rot session performance struct
|
|
*
|
|
* This Function allocates hw required to complete rotation work item
|
|
* requested.
|
|
*
|
|
* Caller is responsible for calling cleanup function if error is returned
|
|
*/
|
|
static int sde_rotator_assign_queue(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry *entry,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
struct sde_rot_perf *perf;
|
|
struct sde_rot_queue *queue;
|
|
struct sde_rot_hw_resource *hw;
|
|
struct sde_rotation_item *item = &entry->item;
|
|
u32 wb_idx = item->wb_idx;
|
|
u32 pipe_idx = item->pipe_idx;
|
|
int ret = 0;
|
|
|
|
if (wb_idx >= mgr->queue_count) {
|
|
/* assign to the lowest priority queue */
|
|
wb_idx = mgr->queue_count - 1;
|
|
}
|
|
|
|
entry->doneq = &mgr->doneq[wb_idx];
|
|
entry->commitq = &mgr->commitq[wb_idx];
|
|
queue = mgr->commitq;
|
|
|
|
if (!queue->hw) {
|
|
hw = mgr->ops_hw_alloc(mgr, pipe_idx, wb_idx);
|
|
if (IS_ERR_OR_NULL(hw)) {
|
|
SDEROT_ERR("fail to allocate hw\n");
|
|
ret = PTR_ERR(hw);
|
|
} else {
|
|
queue->hw = hw;
|
|
}
|
|
}
|
|
|
|
if (queue->hw) {
|
|
entry->commitq = queue;
|
|
queue->hw->pending_count++;
|
|
}
|
|
|
|
perf = sde_rotator_find_session(private, item->session_id);
|
|
if (!perf) {
|
|
SDEROT_ERR(
|
|
"Could not find session based on rotation work item\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
entry->perf = perf;
|
|
perf->last_wb_idx = wb_idx;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sde_rotator_unassign_queue(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry *entry)
|
|
{
|
|
struct sde_rot_queue *queue = entry->commitq;
|
|
|
|
if (!queue)
|
|
return;
|
|
|
|
entry->fenceq = NULL;
|
|
entry->commitq = NULL;
|
|
entry->doneq = NULL;
|
|
|
|
if (!queue->hw) {
|
|
SDEROT_ERR("entry assigned a queue with no hw\n");
|
|
return;
|
|
}
|
|
|
|
queue->hw->pending_count--;
|
|
if (queue->hw->pending_count == 0) {
|
|
mgr->ops_hw_free(mgr, queue->hw);
|
|
queue->hw = NULL;
|
|
}
|
|
}
|
|
|
|
void sde_rotator_queue_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct sde_rot_entry *entry;
|
|
struct sde_rot_queue *queue;
|
|
u32 wb_idx;
|
|
int i;
|
|
|
|
if (!mgr || !private || !req) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
if (!req->entries) {
|
|
SDEROT_DBG("no entries in request\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
entry = req->entries + i;
|
|
queue = entry->commitq;
|
|
wb_idx = queue->hw->wb_id;
|
|
entry->perf->work_distribution[wb_idx]++;
|
|
entry->work_assigned = true;
|
|
}
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
entry = req->entries + i;
|
|
queue = entry->commitq;
|
|
entry->output_fence = NULL;
|
|
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_QUEUE] = ktime_get();
|
|
kthread_queue_work(&queue->rot_kw, &entry->commit_work);
|
|
}
|
|
}
|
|
|
|
static u32 sde_rotator_calc_buf_bw(struct sde_mdp_format_params *fmt,
|
|
uint32_t width, uint32_t height, uint32_t frame_rate)
|
|
{
|
|
u32 bw;
|
|
|
|
bw = width * height * frame_rate;
|
|
|
|
if (sde_mdp_is_tp10_format(fmt))
|
|
bw *= 2;
|
|
else if (sde_mdp_is_p010_format(fmt))
|
|
bw *= 3;
|
|
else if (fmt->chroma_sample == SDE_MDP_CHROMA_420)
|
|
bw = (bw * 3) / 2;
|
|
else
|
|
bw *= fmt->bpp;
|
|
SDEROT_EVTLOG(bw, width, height, frame_rate, fmt->format);
|
|
return bw;
|
|
}
|
|
|
|
static int sde_rotator_find_max_fps(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct sde_rot_file_private *priv;
|
|
struct sde_rot_perf *perf;
|
|
int max_fps = 0;
|
|
|
|
list_for_each_entry(priv, &mgr->file_list, list) {
|
|
list_for_each_entry(perf, &priv->perf_list, list) {
|
|
if (perf->config.frame_rate > max_fps)
|
|
max_fps = perf->config.frame_rate;
|
|
}
|
|
}
|
|
|
|
SDEROT_DBG("Max fps:%d\n", max_fps);
|
|
return max_fps;
|
|
}
|
|
|
|
static int sde_rotator_calc_perf(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_perf *perf)
|
|
{
|
|
struct sde_rotation_config *config = &perf->config;
|
|
u32 read_bw, write_bw;
|
|
struct sde_mdp_format_params *in_fmt, *out_fmt;
|
|
struct sde_rotator_device *rot_dev;
|
|
int max_fps;
|
|
|
|
rot_dev = platform_get_drvdata(mgr->pdev);
|
|
|
|
in_fmt = sde_get_format_params(config->input.format);
|
|
if (!in_fmt) {
|
|
SDEROT_ERR("invalid input format %d\n", config->input.format);
|
|
return -EINVAL;
|
|
}
|
|
out_fmt = sde_get_format_params(config->output.format);
|
|
if (!out_fmt) {
|
|
SDEROT_ERR("invalid output format %d\n", config->output.format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* rotator processes 4 pixels per clock, but the actual throughtput
|
|
* is 3.6. We also need to take into account for overhead time. Final
|
|
* equation is:
|
|
* W x H / throughput / (1/fps - overhead) * fudge_factor
|
|
*/
|
|
max_fps = sde_rotator_find_max_fps(mgr);
|
|
perf->clk_rate = config->input.width * config->input.height;
|
|
perf->clk_rate = (perf->clk_rate * mgr->pixel_per_clk.denom) /
|
|
mgr->pixel_per_clk.numer;
|
|
perf->clk_rate *= max_fps;
|
|
perf->clk_rate = (perf->clk_rate * mgr->fudge_factor.numer) /
|
|
mgr->fudge_factor.denom;
|
|
perf->clk_rate *= mgr->overhead.denom;
|
|
|
|
/*
|
|
* check for override overhead default value
|
|
*/
|
|
if (rot_dev->min_overhead_us > (mgr->overhead.numer * 100))
|
|
perf->clk_rate = DIV_ROUND_UP_ULL(perf->clk_rate,
|
|
(mgr->overhead.denom - max_fps *
|
|
(rot_dev->min_overhead_us / 100)));
|
|
else
|
|
perf->clk_rate = DIV_ROUND_UP_ULL(perf->clk_rate,
|
|
(mgr->overhead.denom - max_fps *
|
|
mgr->overhead.numer));
|
|
|
|
/* use client provided clock if specified */
|
|
if (config->flags & SDE_ROTATION_EXT_PERF)
|
|
perf->clk_rate = config->clk_rate;
|
|
|
|
/*
|
|
* check for Override clock calculation
|
|
*/
|
|
if (rot_dev->min_rot_clk > perf->clk_rate)
|
|
perf->clk_rate = rot_dev->min_rot_clk;
|
|
|
|
if (mgr->min_rot_clk > perf->clk_rate)
|
|
perf->clk_rate = mgr->min_rot_clk;
|
|
|
|
if (mgr->max_rot_clk && (perf->clk_rate > mgr->max_rot_clk)) {
|
|
SDEROT_ERR("invalid clock:%ld exceeds max:%ld allowed\n",
|
|
perf->clk_rate, mgr->max_rot_clk);
|
|
return -EINVAL;
|
|
}
|
|
|
|
read_bw = sde_rotator_calc_buf_bw(in_fmt, config->input.width,
|
|
config->input.height, max_fps);
|
|
|
|
write_bw = sde_rotator_calc_buf_bw(out_fmt, config->output.width,
|
|
config->output.height, max_fps);
|
|
|
|
read_bw = sde_apply_comp_ratio_factor(read_bw, in_fmt,
|
|
&config->input.comp_ratio);
|
|
write_bw = sde_apply_comp_ratio_factor(write_bw, out_fmt,
|
|
&config->output.comp_ratio);
|
|
|
|
perf->bw = read_bw + write_bw;
|
|
|
|
/*
|
|
* check for override bw calculation
|
|
*/
|
|
if (rot_dev->min_bw > perf->bw)
|
|
perf->bw = rot_dev->min_bw;
|
|
|
|
/* use client provided bandwidth if specified */
|
|
if (config->flags & SDE_ROTATION_EXT_PERF)
|
|
perf->bw = config->data_bw;
|
|
|
|
perf->rdot_limit = sde_mdp_get_ot_limit(
|
|
config->input.width, config->input.height,
|
|
config->input.format, config->frame_rate, true);
|
|
perf->wrot_limit = sde_mdp_get_ot_limit(
|
|
config->input.width, config->input.height,
|
|
config->input.format, config->frame_rate, false);
|
|
|
|
SDEROT_DBG("clk:%lu, rdBW:%d, wrBW:%d, rdOT:%d, wrOT:%d\n",
|
|
perf->clk_rate, read_bw, write_bw, perf->rdot_limit,
|
|
perf->wrot_limit);
|
|
SDEROT_EVTLOG(perf->clk_rate, read_bw, write_bw, perf->rdot_limit,
|
|
perf->wrot_limit);
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_update_perf(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct sde_rot_file_private *priv;
|
|
struct sde_rot_perf *perf;
|
|
int not_in_suspend_mode;
|
|
u64 total_bw = 0;
|
|
|
|
not_in_suspend_mode = !atomic_read(&mgr->device_suspended);
|
|
|
|
if (not_in_suspend_mode) {
|
|
list_for_each_entry(priv, &mgr->file_list, list) {
|
|
list_for_each_entry(perf, &priv->perf_list, list) {
|
|
total_bw += perf->bw;
|
|
}
|
|
}
|
|
}
|
|
|
|
total_bw += mgr->pending_close_bw_vote;
|
|
total_bw = max_t(u64, total_bw, mgr->minimum_bw_vote);
|
|
sde_rotator_enable_reg_bus(mgr, total_bw);
|
|
ATRACE_INT("bus_quota", total_bw);
|
|
sde_rotator_bus_scale_set_quota(&mgr->data_bus, total_bw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sde_rotator_release_from_work_distribution(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry *entry)
|
|
{
|
|
if (entry->work_assigned) {
|
|
bool free_perf = false;
|
|
u32 wb_idx = entry->commitq->hw->wb_id;
|
|
|
|
if (entry->perf->work_distribution[wb_idx])
|
|
entry->perf->work_distribution[wb_idx]--;
|
|
|
|
if (!entry->perf->work_distribution[wb_idx]
|
|
&& list_empty(&entry->perf->list)) {
|
|
/* close session has offloaded perf free to us */
|
|
free_perf = true;
|
|
}
|
|
|
|
entry->work_assigned = false;
|
|
if (free_perf) {
|
|
if (mgr->pending_close_bw_vote < entry->perf->bw) {
|
|
SDEROT_ERR(
|
|
"close bw vote underflow %llu / %llu\n",
|
|
mgr->pending_close_bw_vote,
|
|
entry->perf->bw);
|
|
mgr->pending_close_bw_vote = 0;
|
|
} else {
|
|
mgr->pending_close_bw_vote -= entry->perf->bw;
|
|
}
|
|
devm_kfree(&mgr->pdev->dev,
|
|
entry->perf->work_distribution);
|
|
devm_kfree(&mgr->pdev->dev, entry->perf);
|
|
sde_rotator_update_perf(mgr);
|
|
sde_rotator_clk_ctrl(mgr, false);
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
entry->perf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sde_rotator_release_entry(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry *entry)
|
|
{
|
|
sde_rotator_release_from_work_distribution(mgr, entry);
|
|
sde_rotator_clear_fence(entry);
|
|
sde_rotator_release_data(entry);
|
|
sde_rotator_unassign_queue(mgr, entry);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_commit_handler - Commit workqueue handler.
|
|
* @file: Pointer to work struct.
|
|
*
|
|
* This handler is responsible for commit the job to h/w.
|
|
* Once the job is committed, the job entry is added to the done queue.
|
|
*
|
|
* Note this asynchronous handler is protected by hal lock.
|
|
*/
|
|
static void sde_rotator_commit_handler(struct kthread_work *work)
|
|
{
|
|
struct sde_rot_entry *entry;
|
|
struct sde_rot_entry_container *request;
|
|
struct sde_rot_hw_resource *hw;
|
|
struct sde_rot_mgr *mgr;
|
|
struct sched_param param = { .sched_priority = 5 };
|
|
struct sde_rot_trace_entry rot_trace;
|
|
int ret;
|
|
|
|
entry = container_of(work, struct sde_rot_entry, commit_work);
|
|
request = entry->request;
|
|
|
|
if (!request || !entry->private || !entry->private->mgr) {
|
|
SDEROT_ERR("fatal error, null request/context/device\n");
|
|
return;
|
|
}
|
|
|
|
ret = sched_setscheduler(entry->fenceq->rot_thread, SCHED_FIFO, ¶m);
|
|
if (ret) {
|
|
SDEROT_WARN("Fail to set kthread priority for fenceq: %d\n",
|
|
ret);
|
|
}
|
|
|
|
mgr = entry->private->mgr;
|
|
|
|
SDEROT_EVTLOG(
|
|
entry->item.session_id, entry->item.sequence_id,
|
|
entry->item.src_rect.x, entry->item.src_rect.y,
|
|
entry->item.src_rect.w, entry->item.src_rect.h,
|
|
entry->item.dst_rect.x, entry->item.dst_rect.y,
|
|
entry->item.dst_rect.w, entry->item.dst_rect.h,
|
|
entry->item.flags,
|
|
entry->dnsc_factor_w, entry->dnsc_factor_h);
|
|
|
|
SDEDEV_DBG(mgr->device,
|
|
"commit handler s:%d.%u src:(%d,%d,%d,%d) dst:(%d,%d,%d,%d) f:0x%x dnsc:%u/%u\n",
|
|
entry->item.session_id, entry->item.sequence_id,
|
|
entry->item.src_rect.x, entry->item.src_rect.y,
|
|
entry->item.src_rect.w, entry->item.src_rect.h,
|
|
entry->item.dst_rect.x, entry->item.dst_rect.y,
|
|
entry->item.dst_rect.w, entry->item.dst_rect.h,
|
|
entry->item.flags,
|
|
entry->dnsc_factor_w, entry->dnsc_factor_h);
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
|
|
hw = sde_rotator_get_hw_resource(entry->commitq, entry);
|
|
if (!hw) {
|
|
SDEROT_ERR("no hw for the queue\n");
|
|
goto get_hw_res_err;
|
|
}
|
|
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_COMMIT] = ktime_get();
|
|
|
|
/* Set values to pass to trace */
|
|
rot_trace.wb_idx = entry->item.wb_idx;
|
|
rot_trace.flags = entry->item.flags;
|
|
rot_trace.input_format = entry->item.input.format;
|
|
rot_trace.input_width = entry->item.input.width;
|
|
rot_trace.input_height = entry->item.input.height;
|
|
rot_trace.src_x = entry->item.src_rect.x;
|
|
rot_trace.src_y = entry->item.src_rect.y;
|
|
rot_trace.src_w = entry->item.src_rect.w;
|
|
rot_trace.src_h = entry->item.src_rect.h;
|
|
rot_trace.output_format = entry->item.output.format;
|
|
rot_trace.output_width = entry->item.output.width;
|
|
rot_trace.output_height = entry->item.output.height;
|
|
rot_trace.dst_x = entry->item.dst_rect.x;
|
|
rot_trace.dst_y = entry->item.dst_rect.y;
|
|
rot_trace.dst_w = entry->item.dst_rect.w;
|
|
rot_trace.dst_h = entry->item.dst_rect.h;
|
|
|
|
trace_rot_entry_commit(
|
|
entry->item.session_id, entry->item.sequence_id, &rot_trace);
|
|
|
|
ATRACE_INT("sde_smmu_ctrl", 0);
|
|
ret = sde_smmu_ctrl(1);
|
|
if (ret < 0) {
|
|
SDEROT_ERR("IOMMU attach failed\n");
|
|
goto smmu_error;
|
|
}
|
|
ATRACE_INT("sde_smmu_ctrl", 1);
|
|
|
|
ret = sde_rotator_map_and_check_data(entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to prepare input/output data %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
ret = mgr->ops_config_hw(hw, entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to configure hw resource %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_START] = ktime_get();
|
|
|
|
ret = sde_rotator_req_wait_start(mgr, request);
|
|
if (ret) {
|
|
SDEROT_WARN("timeout waiting for inline start\n");
|
|
SDEROT_EVTLOG(entry->item.session_id, entry->item.sequence_id,
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
goto kickoff_error;
|
|
}
|
|
|
|
ret = mgr->ops_kickoff_entry(hw, entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to do kickoff %d\n", ret);
|
|
SDEROT_EVTLOG(entry->item.session_id, entry->item.sequence_id,
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
goto kickoff_error;
|
|
}
|
|
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_FLUSH] = ktime_get();
|
|
|
|
SDEROT_EVTLOG(entry->item.session_id, 1);
|
|
|
|
kthread_queue_work(&entry->doneq->rot_kw, &entry->done_work);
|
|
sde_rot_mgr_unlock(mgr);
|
|
return;
|
|
kickoff_error:
|
|
/*
|
|
* Wait for any pending operations to complete before cancelling this
|
|
* one so that the system is left in a consistent state.
|
|
*/
|
|
sde_rotator_req_wait_for_idle(mgr, request);
|
|
mgr->ops_cancel_hw(hw, entry);
|
|
error:
|
|
sde_smmu_ctrl(0);
|
|
smmu_error:
|
|
sde_rotator_put_hw_resource(entry->commitq, entry, hw);
|
|
get_hw_res_err:
|
|
sde_rotator_signal_output(entry);
|
|
sde_rotator_release_entry(mgr, entry);
|
|
atomic_dec(&request->pending_count);
|
|
atomic_inc(&request->failed_count);
|
|
if (request->retire_kw && request->retire_work)
|
|
kthread_queue_work(request->retire_kw, request->retire_work);
|
|
sde_rot_mgr_unlock(mgr);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_done_handler - Done workqueue handler.
|
|
* @file: Pointer to work struct.
|
|
*
|
|
* This handler is responsible for waiting for h/w done event.
|
|
* Once the job is done, the output fence will be signaled and the job entry
|
|
* will be retired.
|
|
*
|
|
* Note this asynchronous handler is protected by hal lock.
|
|
*/
|
|
static void sde_rotator_done_handler(struct kthread_work *work)
|
|
{
|
|
struct sde_rot_entry *entry;
|
|
struct sde_rot_entry_container *request;
|
|
struct sde_rot_hw_resource *hw;
|
|
struct sde_rot_mgr *mgr;
|
|
struct sde_rot_trace_entry rot_trace;
|
|
int ret;
|
|
|
|
entry = container_of(work, struct sde_rot_entry, done_work);
|
|
request = entry->request;
|
|
|
|
if (!request || !entry->private || !entry->private->mgr) {
|
|
SDEROT_ERR("fatal error, null request/context/device\n");
|
|
return;
|
|
}
|
|
|
|
mgr = entry->private->mgr;
|
|
hw = entry->commitq->hw;
|
|
|
|
SDEDEV_DBG(mgr->device,
|
|
"done handler s:%d.%u src:(%d,%d,%d,%d) dst:(%d,%d,%d,%d) f:0x%x dsnc:%u/%u\n",
|
|
entry->item.session_id, entry->item.sequence_id,
|
|
entry->item.src_rect.x, entry->item.src_rect.y,
|
|
entry->item.src_rect.w, entry->item.src_rect.h,
|
|
entry->item.dst_rect.x, entry->item.dst_rect.y,
|
|
entry->item.dst_rect.w, entry->item.dst_rect.h,
|
|
entry->item.flags,
|
|
entry->dnsc_factor_w, entry->dnsc_factor_h);
|
|
|
|
SDEROT_EVTLOG(entry->item.session_id, 0);
|
|
ret = mgr->ops_wait_for_entry(hw, entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to wait for completion %d\n", ret);
|
|
atomic_inc(&request->failed_count);
|
|
}
|
|
SDEROT_EVTLOG(entry->item.session_id, 1);
|
|
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_DONE] = ktime_get();
|
|
|
|
/* Set values to pass to trace */
|
|
rot_trace.wb_idx = entry->item.wb_idx;
|
|
rot_trace.flags = entry->item.flags;
|
|
rot_trace.input_format = entry->item.input.format;
|
|
rot_trace.input_width = entry->item.input.width;
|
|
rot_trace.input_height = entry->item.input.height;
|
|
rot_trace.src_x = entry->item.src_rect.x;
|
|
rot_trace.src_y = entry->item.src_rect.y;
|
|
rot_trace.src_w = entry->item.src_rect.w;
|
|
rot_trace.src_h = entry->item.src_rect.h;
|
|
rot_trace.output_format = entry->item.output.format;
|
|
rot_trace.output_width = entry->item.output.width;
|
|
rot_trace.output_height = entry->item.output.height;
|
|
rot_trace.dst_x = entry->item.dst_rect.x;
|
|
rot_trace.dst_y = entry->item.dst_rect.y;
|
|
rot_trace.dst_w = entry->item.dst_rect.w;
|
|
rot_trace.dst_h = entry->item.dst_rect.h;
|
|
|
|
trace_rot_entry_done(entry->item.session_id, entry->item.sequence_id,
|
|
&rot_trace);
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
sde_rotator_put_hw_resource(entry->commitq, entry, entry->commitq->hw);
|
|
sde_rotator_signal_output(entry);
|
|
ATRACE_INT("sde_rot_done", 1);
|
|
sde_rotator_release_entry(mgr, entry);
|
|
atomic_dec(&request->pending_count);
|
|
if (request->retire_kw && request->retire_work)
|
|
kthread_queue_work(request->retire_kw, request->retire_work);
|
|
if (entry->item.ts)
|
|
entry->item.ts[SDE_ROTATOR_TS_RETIRE] = ktime_get();
|
|
sde_rot_mgr_unlock(mgr);
|
|
|
|
ATRACE_INT("sde_smmu_ctrl", 3);
|
|
sde_smmu_ctrl(0);
|
|
ATRACE_INT("sde_smmu_ctrl", 4);
|
|
}
|
|
|
|
static bool sde_rotator_verify_format(struct sde_rot_mgr *mgr,
|
|
struct sde_mdp_format_params *in_fmt,
|
|
struct sde_mdp_format_params *out_fmt, bool rotation, u32 mode)
|
|
{
|
|
u8 in_v_subsample, in_h_subsample;
|
|
u8 out_v_subsample, out_h_subsample;
|
|
|
|
if (!sde_rotator_is_valid_pixfmt(mgr, in_fmt->format, true, mode)) {
|
|
SDEROT_ERR("Invalid input format 0x%x (%4.4s)\n",
|
|
in_fmt->format, (char *)&in_fmt->format);
|
|
goto verify_error;
|
|
}
|
|
|
|
if (!sde_rotator_is_valid_pixfmt(mgr, out_fmt->format, false, mode)) {
|
|
SDEROT_ERR("Invalid output format 0x%x (%4.4s)\n",
|
|
out_fmt->format, (char *)&out_fmt->format);
|
|
goto verify_error;
|
|
}
|
|
|
|
if ((in_fmt->is_yuv != out_fmt->is_yuv) ||
|
|
(in_fmt->pixel_mode != out_fmt->pixel_mode) ||
|
|
(in_fmt->unpack_tight != out_fmt->unpack_tight)) {
|
|
SDEROT_ERR(
|
|
"Rotator does not support CSC yuv:%d/%d pm:%d/%d ut:%d/%d\n",
|
|
in_fmt->is_yuv, out_fmt->is_yuv,
|
|
in_fmt->pixel_mode, out_fmt->pixel_mode,
|
|
in_fmt->unpack_tight, out_fmt->unpack_tight);
|
|
goto verify_error;
|
|
}
|
|
|
|
/* Forcing same pixel depth */
|
|
if (memcmp(in_fmt->bits, out_fmt->bits, sizeof(in_fmt->bits))) {
|
|
/* Exception is that RGB can drop alpha or add X */
|
|
if (in_fmt->is_yuv || out_fmt->alpha_enable ||
|
|
(in_fmt->bits[C2_R_Cr] != out_fmt->bits[C2_R_Cr]) ||
|
|
(in_fmt->bits[C0_G_Y] != out_fmt->bits[C0_G_Y]) ||
|
|
(in_fmt->bits[C1_B_Cb] != out_fmt->bits[C1_B_Cb])) {
|
|
SDEROT_ERR("Bit format does not match\n");
|
|
goto verify_error;
|
|
}
|
|
}
|
|
|
|
/* Need to make sure that sub-sampling persists through rotation */
|
|
if (rotation) {
|
|
sde_mdp_get_v_h_subsample_rate(in_fmt->chroma_sample,
|
|
&in_v_subsample, &in_h_subsample);
|
|
sde_mdp_get_v_h_subsample_rate(out_fmt->chroma_sample,
|
|
&out_v_subsample, &out_h_subsample);
|
|
|
|
if ((in_v_subsample != out_h_subsample) ||
|
|
(in_h_subsample != out_v_subsample)) {
|
|
SDEROT_ERR("Rotation has invalid subsampling\n");
|
|
goto verify_error;
|
|
}
|
|
} else {
|
|
if (in_fmt->chroma_sample != out_fmt->chroma_sample) {
|
|
SDEROT_ERR("Format subsampling mismatch\n");
|
|
goto verify_error;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
verify_error:
|
|
SDEROT_ERR("in_fmt=0x%x (%4.4s), out_fmt=0x%x (%4.4s), mode=%d\n",
|
|
in_fmt->format, (char *)&in_fmt->format,
|
|
out_fmt->format, (char *)&out_fmt->format,
|
|
mode);
|
|
return false;
|
|
}
|
|
|
|
static struct sde_mdp_format_params *__verify_input_config(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
struct sde_mdp_format_params *in_fmt;
|
|
u8 in_v_subsample, in_h_subsample;
|
|
u32 input;
|
|
int verify_input_only;
|
|
|
|
if (!mgr || !config) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return NULL;
|
|
}
|
|
|
|
input = config->input.format;
|
|
verify_input_only =
|
|
(config->flags & SDE_ROTATION_VERIFY_INPUT_ONLY) ? 1 : 0;
|
|
|
|
in_fmt = sde_get_format_params(input);
|
|
if (!in_fmt) {
|
|
if (!verify_input_only)
|
|
SDEROT_ERR("Unrecognized input format:0x%x\n", input);
|
|
return NULL;
|
|
}
|
|
|
|
sde_mdp_get_v_h_subsample_rate(in_fmt->chroma_sample,
|
|
&in_v_subsample, &in_h_subsample);
|
|
|
|
/* Dimension of image needs to be divisible by subsample rate */
|
|
if ((config->input.height % in_v_subsample) ||
|
|
(config->input.width % in_h_subsample)) {
|
|
if (!verify_input_only)
|
|
SDEROT_ERR(
|
|
"In ROI, subsample mismatch, w=%d, h=%d, vss%d, hss%d\n",
|
|
config->input.width,
|
|
config->input.height,
|
|
in_v_subsample, in_h_subsample);
|
|
return NULL;
|
|
}
|
|
|
|
return in_fmt;
|
|
}
|
|
|
|
static struct sde_mdp_format_params *__verify_output_config(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
struct sde_mdp_format_params *out_fmt;
|
|
u8 out_v_subsample, out_h_subsample;
|
|
u32 output;
|
|
int verify_input_only;
|
|
|
|
if (!mgr || !config) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return NULL;
|
|
}
|
|
|
|
output = config->output.format;
|
|
verify_input_only =
|
|
(config->flags & SDE_ROTATION_VERIFY_INPUT_ONLY) ? 1 : 0;
|
|
|
|
out_fmt = sde_get_format_params(output);
|
|
if (!out_fmt) {
|
|
if (!verify_input_only)
|
|
SDEROT_ERR("Unrecognized output format:0x%x\n", output);
|
|
return NULL;
|
|
}
|
|
|
|
sde_mdp_get_v_h_subsample_rate(out_fmt->chroma_sample,
|
|
&out_v_subsample, &out_h_subsample);
|
|
|
|
/* Dimension of image needs to be divisible by subsample rate */
|
|
if ((config->output.height % out_v_subsample) ||
|
|
(config->output.width % out_h_subsample)) {
|
|
if (!verify_input_only)
|
|
SDEROT_ERR(
|
|
"Out ROI, subsample mismatch, w=%d, h=%d, vss%d, hss%d\n",
|
|
config->output.width,
|
|
config->output.height,
|
|
out_v_subsample, out_h_subsample);
|
|
return NULL;
|
|
}
|
|
|
|
return out_fmt;
|
|
}
|
|
|
|
int sde_rotator_verify_config_input(struct sde_rot_mgr *mgr,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
struct sde_mdp_format_params *in_fmt;
|
|
|
|
in_fmt = __verify_input_config(mgr, config);
|
|
if (!in_fmt)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sde_rotator_verify_config_output(struct sde_rot_mgr *mgr,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
struct sde_mdp_format_params *out_fmt;
|
|
|
|
out_fmt = __verify_output_config(mgr, config);
|
|
if (!out_fmt)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sde_rotator_verify_config_all(struct sde_rot_mgr *mgr,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
struct sde_mdp_format_params *in_fmt, *out_fmt;
|
|
bool rotation;
|
|
u32 mode;
|
|
|
|
if (!mgr || !config) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rotation = (config->flags & SDE_ROTATION_90) ? true : false;
|
|
|
|
mode = config->output.sbuf ? SDE_ROTATOR_MODE_SBUF :
|
|
SDE_ROTATOR_MODE_OFFLINE;
|
|
|
|
in_fmt = __verify_input_config(mgr, config);
|
|
if (!in_fmt)
|
|
return -EINVAL;
|
|
|
|
out_fmt = __verify_output_config(mgr, config);
|
|
if (!out_fmt)
|
|
return -EINVAL;
|
|
|
|
if (!sde_rotator_verify_format(mgr, in_fmt, out_fmt, rotation, mode)) {
|
|
SDEROT_ERR(
|
|
"Rot format pairing invalid, in_fmt:0x%x, out_fmt:0x%x\n",
|
|
config->input.format,
|
|
config->output.format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_validate_item_matches_session(
|
|
struct sde_rotation_config *config, struct sde_rotation_item *item)
|
|
{
|
|
int ret;
|
|
|
|
ret = __compare_session_item_rect(&config->input,
|
|
&item->src_rect, item->input.format, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __compare_session_item_rect(&config->output,
|
|
&item->dst_rect, item->output.format, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __compare_session_rotations(config->flags, item->flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Only need to validate x and y offset for ubwc dst fmt */
|
|
static int sde_rotator_validate_img_roi(struct sde_rotation_item *item)
|
|
{
|
|
struct sde_mdp_format_params *fmt;
|
|
int ret = 0;
|
|
|
|
fmt = sde_get_format_params(item->output.format);
|
|
if (!fmt) {
|
|
SDEROT_DBG("invalid output format:%d\n",
|
|
item->output.format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sde_mdp_is_ubwc_format(fmt))
|
|
ret = sde_validate_offset_for_ubwc_format(fmt,
|
|
item->dst_rect.x, item->dst_rect.y);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_validate_fmt_and_item_flags(
|
|
struct sde_rotation_config *config, struct sde_rotation_item *item)
|
|
{
|
|
struct sde_mdp_format_params *in_fmt, *out_fmt;
|
|
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
|
|
bool has_ubwc;
|
|
|
|
in_fmt = sde_get_format_params(item->input.format);
|
|
out_fmt = sde_get_format_params(item->output.format);
|
|
if ((item->flags & SDE_ROTATION_DEINTERLACE) &&
|
|
sde_mdp_is_ubwc_format(in_fmt)) {
|
|
SDEROT_DBG("cannot perform deinterlace on tiled formats\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
has_ubwc = ROT_HAS_UBWC(mdata->sde_caps_map);
|
|
if (!has_ubwc && (sde_mdp_is_ubwc_format(in_fmt) ||
|
|
sde_mdp_is_ubwc_format(out_fmt))) {
|
|
SDEROT_ERR("ubwc format is not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_validate_entry(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry *entry)
|
|
{
|
|
int ret;
|
|
struct sde_rotation_item *item;
|
|
struct sde_rot_perf *perf;
|
|
|
|
item = &entry->item;
|
|
|
|
if (item->wb_idx >= mgr->queue_count)
|
|
item->wb_idx = mgr->queue_count - 1;
|
|
|
|
perf = sde_rotator_find_session(private, item->session_id);
|
|
if (!perf) {
|
|
SDEROT_DBG("Could not find session:%u\n", item->session_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = sde_rotator_validate_item_matches_session(&perf->config, item);
|
|
if (ret) {
|
|
SDEROT_DBG("Work item does not match session:%u\n",
|
|
item->session_id);
|
|
return ret;
|
|
}
|
|
|
|
ret = sde_rotator_validate_img_roi(item);
|
|
if (ret) {
|
|
SDEROT_DBG("Image roi is invalid\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sde_rotator_validate_fmt_and_item_flags(&perf->config, item);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mgr->ops_hw_validate_entry(mgr, entry);
|
|
if (ret) {
|
|
SDEROT_DBG("fail to configure downscale factor\n");
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Upon failure from the function, caller needs to make sure
|
|
* to call sde_rotator_remove_request to clean up resources.
|
|
*/
|
|
static int sde_rotator_add_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct sde_rot_entry *entry;
|
|
struct sde_rotation_item *item;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
entry = req->entries + i;
|
|
item = &entry->item;
|
|
entry->fenceq = private->fenceq;
|
|
|
|
ret = sde_rotator_validate_entry(mgr, private, entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to validate the entry\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sde_rotator_import_data(mgr, entry);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to import the data\n");
|
|
return ret;
|
|
}
|
|
|
|
entry->input_fence = item->input.fence;
|
|
entry->output_fence = item->output.fence;
|
|
|
|
ret = sde_rotator_assign_queue(mgr, entry, private);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to assign queue to entry\n");
|
|
return ret;
|
|
}
|
|
|
|
entry->request = req;
|
|
|
|
kthread_init_work(&entry->commit_work,
|
|
sde_rotator_commit_handler);
|
|
kthread_init_work(&entry->done_work,
|
|
sde_rotator_done_handler);
|
|
SDEROT_DBG(
|
|
"Entry added. wbidx=%u, src{%u,%u,%u,%u}f=%x dst{%u,%u,%u,%u}f=%x session_id=%u\n",
|
|
item->wb_idx,
|
|
item->src_rect.x, item->src_rect.y,
|
|
item->src_rect.w, item->src_rect.h, item->input.format,
|
|
item->dst_rect.x, item->dst_rect.y,
|
|
item->dst_rect.w, item->dst_rect.h, item->output.format,
|
|
item->session_id);
|
|
}
|
|
|
|
list_add(&req->list, &private->req_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sde_rotator_remove_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
int i;
|
|
|
|
if (!mgr || !private || !req) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < req->count; i++)
|
|
sde_rotator_release_entry(mgr, req->entries + i);
|
|
list_del_init(&req->list);
|
|
}
|
|
|
|
/* This function should be called with req_lock */
|
|
static void sde_rotator_cancel_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct sde_rot_entry *entry;
|
|
int i;
|
|
|
|
if (atomic_read(&req->pending_count)) {
|
|
/*
|
|
* To avoid signal the rotation entry output fence in the wrong
|
|
* order, all the entries in the same request needs to be
|
|
* canceled first, before signaling the output fence.
|
|
*/
|
|
SDEROT_DBG("cancel work start\n");
|
|
sde_rot_mgr_unlock(mgr);
|
|
for (i = req->count - 1; i >= 0; i--) {
|
|
entry = req->entries + i;
|
|
kthread_cancel_work_sync(&entry->commit_work);
|
|
kthread_cancel_work_sync(&entry->done_work);
|
|
}
|
|
sde_rot_mgr_lock(mgr);
|
|
SDEROT_DBG("cancel work done\n");
|
|
for (i = req->count - 1; i >= 0; i--) {
|
|
entry = req->entries + i;
|
|
sde_rotator_signal_output(entry);
|
|
sde_rotator_release_entry(mgr, entry);
|
|
}
|
|
}
|
|
|
|
list_del_init(&req->list);
|
|
devm_kfree(&mgr->pdev->dev, req);
|
|
}
|
|
|
|
void sde_rotator_cancel_all_requests(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
struct sde_rot_entry_container *req, *req_next;
|
|
|
|
SDEROT_DBG("Canceling all rotator requests\n");
|
|
|
|
list_for_each_entry_safe(req, req_next, &private->req_list, list)
|
|
sde_rotator_cancel_request(mgr, req);
|
|
}
|
|
|
|
static void sde_rotator_free_completed_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
struct sde_rot_entry_container *req, *req_next;
|
|
|
|
list_for_each_entry_safe(req, req_next, &private->req_list, list) {
|
|
if ((atomic_read(&req->pending_count) == 0) && req->finished) {
|
|
list_del_init(&req->list);
|
|
devm_kfree(&mgr->pdev->dev, req);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sde_rotator_release_rotator_perf_session(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
struct sde_rot_perf *perf, *perf_next;
|
|
|
|
SDEROT_DBG("Releasing all rotator request\n");
|
|
sde_rotator_cancel_all_requests(mgr, private);
|
|
|
|
list_for_each_entry_safe(perf, perf_next, &private->perf_list, list) {
|
|
list_del_init(&perf->list);
|
|
devm_kfree(&mgr->pdev->dev, perf->work_distribution);
|
|
devm_kfree(&mgr->pdev->dev, perf);
|
|
}
|
|
}
|
|
|
|
static void sde_rotator_release_all(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct sde_rot_file_private *priv, *priv_next;
|
|
|
|
list_for_each_entry_safe(priv, priv_next, &mgr->file_list, list) {
|
|
sde_rotator_release_rotator_perf_session(mgr, priv);
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
list_del_init(&priv->list);
|
|
}
|
|
|
|
sde_rotator_update_perf(mgr);
|
|
}
|
|
|
|
int sde_rotator_validate_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
int i, ret = 0;
|
|
struct sde_rot_entry *entry;
|
|
|
|
if (!mgr || !private || !req) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
entry = req->entries + i;
|
|
ret = sde_rotator_validate_entry(mgr, private,
|
|
entry);
|
|
if (ret) {
|
|
SDEROT_DBG("invalid entry\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_open_session(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private, u32 session_id)
|
|
{
|
|
struct sde_rotation_config config;
|
|
struct sde_rot_perf *perf;
|
|
int ret;
|
|
|
|
if (!mgr || !private)
|
|
return -EINVAL;
|
|
|
|
memset(&config, 0, sizeof(struct sde_rotation_config));
|
|
|
|
/* initialize with default parameters */
|
|
config.frame_rate = 30;
|
|
config.input.comp_ratio.numer = 1;
|
|
config.input.comp_ratio.denom = 1;
|
|
config.input.format = SDE_PIX_FMT_Y_CBCR_H2V2;
|
|
config.input.width = 640;
|
|
config.input.height = 480;
|
|
config.output.comp_ratio.numer = 1;
|
|
config.output.comp_ratio.denom = 1;
|
|
config.output.format = SDE_PIX_FMT_Y_CBCR_H2V2;
|
|
config.output.width = 640;
|
|
config.output.height = 480;
|
|
|
|
perf = devm_kzalloc(&mgr->pdev->dev, sizeof(*perf), GFP_KERNEL);
|
|
if (!perf)
|
|
return -ENOMEM;
|
|
|
|
perf->work_distribution = devm_kzalloc(&mgr->pdev->dev,
|
|
sizeof(u32) * mgr->queue_count, GFP_KERNEL);
|
|
if (!perf->work_distribution) {
|
|
ret = -ENOMEM;
|
|
goto alloc_err;
|
|
}
|
|
|
|
config.session_id = session_id;
|
|
perf->config = config;
|
|
perf->last_wb_idx = 0;
|
|
|
|
INIT_LIST_HEAD(&perf->list);
|
|
list_add(&perf->list, &private->perf_list);
|
|
|
|
ret = sde_rotator_resource_ctrl(mgr, true);
|
|
if (ret < 0) {
|
|
SDEROT_ERR("Failed to acquire rotator resources\n");
|
|
goto resource_err;
|
|
}
|
|
|
|
ret = sde_rotator_update_clk(mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("failed to update clk %d\n", ret);
|
|
goto update_clk_err;
|
|
}
|
|
|
|
ret = sde_rotator_clk_ctrl(mgr, true);
|
|
if (ret) {
|
|
SDEROT_ERR("failed to enable clk %d\n", ret);
|
|
goto enable_clk_err;
|
|
}
|
|
|
|
SDEROT_DBG("open session id=%u in{%u,%u}f:%u out{%u,%u}f:%u\n",
|
|
config.session_id, config.input.width, config.input.height,
|
|
config.input.format, config.output.width, config.output.height,
|
|
config.output.format);
|
|
|
|
goto done;
|
|
enable_clk_err:
|
|
update_clk_err:
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
resource_err:
|
|
list_del_init(&perf->list);
|
|
devm_kfree(&mgr->pdev->dev, perf->work_distribution);
|
|
alloc_err:
|
|
devm_kfree(&mgr->pdev->dev, perf);
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_close_session(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private, u32 session_id)
|
|
{
|
|
struct sde_rot_perf *perf;
|
|
bool offload_release_work = false;
|
|
u32 id;
|
|
|
|
id = (u32)session_id;
|
|
perf = __sde_rotator_find_session(private, id);
|
|
if (!perf) {
|
|
SDEROT_ERR("Trying to close session that does not exist\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sde_rotator_is_work_pending(mgr, perf)) {
|
|
SDEROT_DBG("Work is still pending, offload free to wq\n");
|
|
mgr->pending_close_bw_vote += perf->bw;
|
|
offload_release_work = true;
|
|
}
|
|
list_del_init(&perf->list);
|
|
|
|
if (offload_release_work)
|
|
goto done;
|
|
|
|
devm_kfree(&mgr->pdev->dev, perf->work_distribution);
|
|
devm_kfree(&mgr->pdev->dev, perf);
|
|
sde_rotator_update_perf(mgr);
|
|
sde_rotator_clk_ctrl(mgr, false);
|
|
sde_rotator_update_clk(mgr);
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
done:
|
|
if (mgr->sbuf_ctx == private) {
|
|
SDEROT_DBG("release sbuf session id:%u\n", id);
|
|
SDEROT_EVTLOG(id);
|
|
mgr->sbuf_ctx = NULL;
|
|
}
|
|
|
|
SDEROT_DBG("Closed session id:%u\n", id);
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_config_session(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
int ret = 0;
|
|
struct sde_rot_perf *perf;
|
|
|
|
ret = sde_rotator_verify_config_all(mgr, config);
|
|
if (ret) {
|
|
SDEROT_ERR("Rotator verify format failed\n");
|
|
return ret;
|
|
}
|
|
|
|
perf = sde_rotator_find_session(private, config->session_id);
|
|
if (!perf) {
|
|
SDEROT_ERR("No session with id=%u could be found\n",
|
|
config->session_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
perf->config = *config;
|
|
ret = sde_rotator_calc_perf(mgr, perf);
|
|
|
|
if (ret) {
|
|
SDEROT_ERR("error in configuring the session %d\n", ret);
|
|
goto done;
|
|
}
|
|
|
|
ret = sde_rotator_update_perf(mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("error in updating perf: %d\n", ret);
|
|
goto done;
|
|
}
|
|
|
|
ret = sde_rotator_update_clk(mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("error in updating the rotator clk: %d\n", ret);
|
|
goto done;
|
|
}
|
|
|
|
if (config->output.sbuf && mgr->sbuf_ctx != private && mgr->sbuf_ctx) {
|
|
SDEROT_ERR("too many sbuf sessions\n");
|
|
ret = -EBUSY;
|
|
goto done;
|
|
}
|
|
|
|
SDEROT_DBG(
|
|
"reconfig session id=%u in{%u,%u}f:%x out{%u,%u}f:%x fps:%d clk:%lu bw:%llu\n",
|
|
config->session_id, config->input.width, config->input.height,
|
|
config->input.format, config->output.width,
|
|
config->output.height, config->output.format,
|
|
config->frame_rate, perf->clk_rate, perf->bw);
|
|
SDEROT_EVTLOG(config->session_id, config->input.width,
|
|
config->input.height, config->input.format,
|
|
config->output.width, config->output.height,
|
|
config->output.format, config->frame_rate);
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
struct sde_rot_entry_container *sde_rotator_req_init(
|
|
struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rotation_item *items,
|
|
u32 count, u32 flags)
|
|
{
|
|
struct sde_rot_entry_container *req;
|
|
int size, i;
|
|
|
|
if (!mgr || !private || !items) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
size = sizeof(struct sde_rot_entry_container);
|
|
size += sizeof(struct sde_rot_entry) * count;
|
|
req = devm_kzalloc(&mgr->pdev->dev, size, GFP_KERNEL);
|
|
|
|
if (!req)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
INIT_LIST_HEAD(&req->list);
|
|
req->count = count;
|
|
req->entries = (struct sde_rot_entry *)
|
|
((void *)req + sizeof(struct sde_rot_entry_container));
|
|
req->flags = flags;
|
|
atomic_set(&req->pending_count, count);
|
|
atomic_set(&req->failed_count, 0);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
req->entries[i].item = items[i];
|
|
req->entries[i].private = private;
|
|
|
|
init_completion(&req->entries[i].item.inline_start);
|
|
complete_all(&req->entries[i].item.inline_start);
|
|
}
|
|
|
|
return req;
|
|
}
|
|
|
|
void sde_rotator_req_reset_start(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
int i;
|
|
|
|
if (!mgr || !req)
|
|
return;
|
|
|
|
for (i = 0; i < req->count; i++)
|
|
reinit_completion(&req->entries[i].item.inline_start);
|
|
}
|
|
|
|
void sde_rotator_req_set_start(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct kthread_work *commit_work;
|
|
int i;
|
|
|
|
if (!mgr || !req || !req->entries)
|
|
return;
|
|
|
|
/* signal ready to start */
|
|
for (i = 0; i < req->count; i++)
|
|
complete_all(&req->entries[i].item.inline_start);
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
commit_work = &req->entries[i].commit_work;
|
|
|
|
SDEROT_EVTLOG(i, req->count);
|
|
|
|
sde_rot_mgr_unlock(mgr);
|
|
kthread_flush_work(commit_work);
|
|
sde_rot_mgr_lock(mgr);
|
|
}
|
|
}
|
|
|
|
int sde_rotator_req_wait_start(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct completion *inline_start;
|
|
int i, ret;
|
|
|
|
if (!mgr || !req || !req->entries)
|
|
return -EINVAL;
|
|
|
|
/* only wait for sbuf mode */
|
|
if (!mgr->sbuf_ctx || !req->count ||
|
|
mgr->sbuf_ctx != req->entries[0].private)
|
|
return 0;
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
inline_start = &req->entries[i].item.inline_start;
|
|
|
|
sde_rot_mgr_unlock(mgr);
|
|
ret = wait_for_completion_timeout(inline_start,
|
|
msecs_to_jiffies(ROT_INLINE_START_TIMEOUT_IN_MS));
|
|
sde_rot_mgr_lock(mgr);
|
|
}
|
|
|
|
/* wait call returns zero on timeout */
|
|
return ret ? 0 : -EBUSY;
|
|
}
|
|
|
|
void sde_rotator_req_finish(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
if (!mgr || !private || !req) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
req->finished = true;
|
|
}
|
|
|
|
void sde_rotator_abort_inline_request(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
struct kthread_work *commit_work;
|
|
struct kthread_work *done_work;
|
|
struct sde_rot_entry *entry;
|
|
struct sde_rot_hw_resource *hw;
|
|
int i;
|
|
|
|
if (!mgr || !private || !req || !req->entries)
|
|
return;
|
|
|
|
for (i = 0; i < req->count; i++) {
|
|
entry = &req->entries[i];
|
|
if (!entry)
|
|
continue;
|
|
|
|
commit_work = &entry->commit_work;
|
|
done_work = &entry->done_work;
|
|
|
|
hw = sde_rotator_get_hw_resource(entry->commitq, entry);
|
|
if (!hw) {
|
|
SDEROT_ERR("no hw for the queue\n");
|
|
SDEROT_EVTLOG(i, req->count, SDE_ROT_EVTLOG_ERROR);
|
|
continue;
|
|
}
|
|
|
|
SDEROT_EVTLOG(i, req->count);
|
|
|
|
mgr->ops_abort_hw(hw, entry);
|
|
|
|
sde_rot_mgr_unlock(mgr);
|
|
kthread_flush_work(commit_work);
|
|
kthread_flush_work(done_work);
|
|
sde_rot_mgr_lock(mgr);
|
|
}
|
|
}
|
|
|
|
int sde_rotator_handle_request_common(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rot_entry_container *req)
|
|
{
|
|
int ret;
|
|
|
|
if (!mgr || !private || !req) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rotator_free_completed_request(mgr, private);
|
|
|
|
ret = sde_rotator_add_request(mgr, private, req);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to add rotation request\n");
|
|
sde_rotator_remove_request(mgr, private, req);
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_open(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private **pprivate)
|
|
{
|
|
struct sde_rot_file_private *private;
|
|
|
|
if (!mgr || !pprivate)
|
|
return -ENODEV;
|
|
|
|
if (atomic_read(&mgr->device_suspended))
|
|
return -EPERM;
|
|
|
|
private = devm_kzalloc(&mgr->pdev->dev, sizeof(*private),
|
|
GFP_KERNEL);
|
|
if (!private)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&private->req_list);
|
|
INIT_LIST_HEAD(&private->perf_list);
|
|
INIT_LIST_HEAD(&private->list);
|
|
|
|
list_add(&private->list, &mgr->file_list);
|
|
|
|
*pprivate = private;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool sde_rotator_file_priv_allowed(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *priv)
|
|
{
|
|
struct sde_rot_file_private *_priv, *_priv_next;
|
|
bool ret = false;
|
|
|
|
list_for_each_entry_safe(_priv, _priv_next, &mgr->file_list, list) {
|
|
if (_priv == priv) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rotator_close(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private)
|
|
{
|
|
if (!mgr || !private)
|
|
return -ENODEV;
|
|
|
|
if (!(sde_rotator_file_priv_allowed(mgr, private))) {
|
|
SDEROT_ERR(
|
|
"Calling close with unrecognized rot_file_private\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* if secure camera session was enabled
|
|
* go back to non secure state
|
|
*/
|
|
sde_rotator_secure_session_ctrl(false);
|
|
sde_rotator_release_rotator_perf_session(mgr, private);
|
|
|
|
list_del_init(&private->list);
|
|
devm_kfree(&mgr->pdev->dev, private);
|
|
|
|
sde_rotator_update_perf(mgr);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t caps_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
size_t len = PAGE_SIZE;
|
|
int cnt = 0;
|
|
struct sde_rot_mgr *mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr)
|
|
return cnt;
|
|
|
|
#define SPRINT(fmt, ...) \
|
|
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
|
|
|
|
SPRINT("queue_count=%d\n", mgr->queue_count);
|
|
SPRINT("downscale=1\n");
|
|
SPRINT("ubwc=1\n");
|
|
|
|
if (mgr->ops_hw_show_caps)
|
|
cnt += mgr->ops_hw_show_caps(mgr, attr, buf + cnt, len - cnt);
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static ssize_t state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
size_t len = PAGE_SIZE;
|
|
int cnt = 0;
|
|
struct sde_rot_mgr *mgr = sde_rot_mgr_from_device(dev);
|
|
int i;
|
|
|
|
if (!mgr)
|
|
return cnt;
|
|
|
|
#define SPRINT(fmt, ...) \
|
|
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
|
|
|
|
SPRINT("reg_bus_bw=%llu\n", mgr->reg_bus.curr_quota_val);
|
|
SPRINT("data_bus_bw=%llu\n", mgr->data_bus.curr_quota_val);
|
|
SPRINT("pending_close_bw_vote=%llu\n", mgr->pending_close_bw_vote);
|
|
SPRINT("device_suspended=%d\n", atomic_read(&mgr->device_suspended));
|
|
SPRINT("footswitch_cnt=%d\n", mgr->res_ref_cnt);
|
|
SPRINT("regulator_enable=%d\n", mgr->regulator_enable);
|
|
SPRINT("enable_clk_cnt=%d\n", mgr->rot_enable_clk_cnt);
|
|
for (i = 0; i < mgr->num_rot_clk; i++)
|
|
if (mgr->rot_clk[i].clk)
|
|
SPRINT("%s=%lu\n", mgr->rot_clk[i].clk_name,
|
|
clk_get_rate(mgr->rot_clk[i].clk));
|
|
|
|
if (mgr->ops_hw_show_state)
|
|
cnt += mgr->ops_hw_show_state(mgr, attr, buf + cnt, len - cnt);
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(caps);
|
|
static DEVICE_ATTR_RO(state);
|
|
|
|
static struct attribute *sde_rotator_fs_attrs[] = {
|
|
&dev_attr_caps.attr,
|
|
&dev_attr_state.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group sde_rotator_fs_attr_group = {
|
|
.attrs = sde_rotator_fs_attrs
|
|
};
|
|
|
|
static int sde_rotator_parse_dt_bus(struct sde_rot_mgr *mgr,
|
|
struct platform_device *dev)
|
|
{
|
|
char bus_name[32];
|
|
int ret = 0, i = 0;
|
|
|
|
mgr->reg_bus.data_bus_hdl[0] = of_icc_get(&dev->dev,
|
|
"qcom,sde-reg-bus");
|
|
|
|
if (mgr->reg_bus.data_bus_hdl[0] == NULL) {
|
|
mgr->reg_bus.data_paths_cnt = 0;
|
|
pr_debug("rotator: reg bus dt node missing\n");
|
|
goto data_bus;
|
|
} else if (IS_ERR(mgr->reg_bus.data_bus_hdl[0])) {
|
|
SDEROT_ERR("sde rotator parse reg bus failed. ret=%d\n",
|
|
ret);
|
|
mgr->reg_bus.data_bus_hdl[0] = NULL;
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
mgr->reg_bus.data_paths_cnt = 1;
|
|
|
|
data_bus:
|
|
for (i = 0; i < SDE_ROTATION_BUS_PATH_MAX; i++) {
|
|
snprintf(bus_name, 32, "%s%d", "qcom,rot-data-bus", i);
|
|
ret = of_property_match_string(dev->dev.of_node,
|
|
"interconnect-names", bus_name);
|
|
if (ret < 0) {
|
|
if (!mgr->data_bus.data_paths_cnt) {
|
|
pr_debug("rotator: bus %s dt node missing\n",
|
|
bus_name);
|
|
return 0;
|
|
} else {
|
|
goto end;
|
|
}
|
|
} else {
|
|
mgr->data_bus.data_bus_hdl[i] =
|
|
of_icc_get(&dev->dev, bus_name);
|
|
}
|
|
|
|
if (IS_ERR_OR_NULL(mgr->data_bus.data_bus_hdl[i])) {
|
|
SDEROT_ERR("rotator: get data bus %s failed\n",
|
|
bus_name);
|
|
break;
|
|
}
|
|
mgr->data_bus.data_paths_cnt++;
|
|
}
|
|
|
|
if (!mgr->data_bus.data_paths_cnt) {
|
|
pr_err("rotator: get none data bus path\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
end:
|
|
if (of_find_property(dev->dev.of_node,
|
|
"qcom,msm-bus,active-only", NULL)) {
|
|
mgr->data_bus.bus_active_only = true;
|
|
for (i = 0; i < mgr->data_bus.data_paths_cnt; i++) {
|
|
icc_set_tag(mgr->data_bus.data_bus_hdl[i],
|
|
QCOM_ICC_TAG_ACTIVE_ONLY);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rotator_parse_dt(struct sde_rot_mgr *mgr,
|
|
struct platform_device *dev)
|
|
{
|
|
int ret = 0;
|
|
u32 data;
|
|
|
|
ret = of_property_read_u32(dev->dev.of_node,
|
|
"qcom,mdss-wb-count", &data);
|
|
if (!ret) {
|
|
if (data > ROT_MAX_HW_BLOCKS) {
|
|
SDEROT_ERR(
|
|
"Err, num of wb block (%d) larger than sw max %d\n",
|
|
data, ROT_MAX_HW_BLOCKS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mgr->queue_count = data;
|
|
}
|
|
|
|
ret = sde_rotator_parse_dt_bus(mgr, dev);
|
|
if (ret)
|
|
SDEROT_ERR("Failed to parse bus data\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sde_rotator_put_dt_vreg_data(struct device *dev,
|
|
struct sde_module_power *mp)
|
|
{
|
|
if (!mp) {
|
|
SDEROT_ERR("%s: invalid input\n", __func__);
|
|
return;
|
|
}
|
|
|
|
sde_rot_config_vreg(dev, mp->vreg_config, mp->num_vreg, 0);
|
|
if (mp->vreg_config) {
|
|
devm_kfree(dev, mp->vreg_config);
|
|
mp->vreg_config = NULL;
|
|
}
|
|
mp->num_vreg = 0;
|
|
}
|
|
|
|
static int sde_rotator_get_dt_vreg_data(struct device *dev,
|
|
struct sde_module_power *mp)
|
|
{
|
|
const char *st = NULL;
|
|
struct device_node *of_node = NULL;
|
|
int dt_vreg_total = 0;
|
|
int i;
|
|
int rc;
|
|
|
|
if (!dev || !mp) {
|
|
SDEROT_ERR("%s: invalid input\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_node = dev->of_node;
|
|
|
|
dt_vreg_total = of_property_count_strings(of_node, "qcom,supply-names");
|
|
if (dt_vreg_total < 0) {
|
|
SDEROT_ERR("%s: vreg not found. rc=%d\n", __func__,
|
|
dt_vreg_total);
|
|
return 0;
|
|
}
|
|
mp->num_vreg = dt_vreg_total;
|
|
mp->vreg_config = devm_kzalloc(dev, sizeof(struct sde_vreg) *
|
|
dt_vreg_total, GFP_KERNEL);
|
|
if (!mp->vreg_config)
|
|
return -ENOMEM;
|
|
|
|
/* vreg-name */
|
|
for (i = 0; i < dt_vreg_total; i++) {
|
|
rc = of_property_read_string_index(of_node,
|
|
"qcom,supply-names", i, &st);
|
|
if (rc) {
|
|
SDEROT_ERR("%s: error reading name. i=%d, rc=%d\n",
|
|
__func__, i, rc);
|
|
goto error;
|
|
}
|
|
snprintf(mp->vreg_config[i].vreg_name, 32, "%s", st);
|
|
}
|
|
sde_rot_config_vreg(dev, mp->vreg_config, mp->num_vreg, 1);
|
|
|
|
for (i = 0; i < dt_vreg_total; i++) {
|
|
SDEROT_DBG("%s: %s min=%d, max=%d, enable=%d disable=%d\n",
|
|
__func__,
|
|
mp->vreg_config[i].vreg_name,
|
|
mp->vreg_config[i].min_voltage,
|
|
mp->vreg_config[i].max_voltage,
|
|
mp->vreg_config[i].enable_load,
|
|
mp->vreg_config[i].disable_load);
|
|
}
|
|
return rc;
|
|
|
|
error:
|
|
if (mp->vreg_config) {
|
|
devm_kfree(dev, mp->vreg_config);
|
|
mp->vreg_config = NULL;
|
|
}
|
|
mp->num_vreg = 0;
|
|
return rc;
|
|
}
|
|
|
|
static void sde_rotator_bus_scale_unregister(struct sde_rot_mgr *mgr)
|
|
{
|
|
int i = 0;
|
|
|
|
SDEROT_DBG("unregister sde rotator bus\n");
|
|
for (i = 0; i < mgr->data_bus.data_paths_cnt; i++) {
|
|
if (mgr->data_bus.data_bus_hdl[i])
|
|
icc_put(mgr->data_bus.data_bus_hdl[i]);
|
|
}
|
|
|
|
if (mgr->reg_bus.data_bus_hdl[0])
|
|
icc_put(mgr->reg_bus.data_bus_hdl[0]);
|
|
}
|
|
|
|
static inline int sde_rotator_search_dt_clk(struct platform_device *pdev,
|
|
struct sde_rot_mgr *mgr, char *clk_name, int clk_idx,
|
|
bool mandatory)
|
|
{
|
|
struct clk *tmp;
|
|
int rc = 0;
|
|
|
|
if (clk_idx >= SDE_ROTATOR_CLK_MAX) {
|
|
SDEROT_ERR("invalid clk index %d\n", clk_idx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tmp = devm_clk_get(&pdev->dev, clk_name);
|
|
if (IS_ERR(tmp)) {
|
|
if (mandatory)
|
|
SDEROT_ERR("unable to get clk: %s\n", clk_name);
|
|
else
|
|
tmp = NULL;
|
|
rc = PTR_ERR(tmp);
|
|
}
|
|
|
|
strlcpy(mgr->rot_clk[clk_idx].clk_name, clk_name,
|
|
sizeof(mgr->rot_clk[clk_idx].clk_name));
|
|
|
|
mgr->rot_clk[clk_idx].clk = tmp;
|
|
return mandatory ? rc : 0;
|
|
}
|
|
|
|
static int sde_rotator_parse_dt_clk(struct platform_device *pdev,
|
|
struct sde_rot_mgr *mgr)
|
|
{
|
|
u32 rc = 0;
|
|
int num_clk;
|
|
|
|
num_clk = of_property_count_strings(pdev->dev.of_node,
|
|
"clock-names");
|
|
if ((num_clk <= 0) || (num_clk > SDE_ROTATOR_CLK_MAX)) {
|
|
SDEROT_ERR("Number of clocks are out of range: %d\n", num_clk);
|
|
goto clk_err;
|
|
}
|
|
|
|
mgr->num_rot_clk = SDE_ROTATOR_CLK_MAX;
|
|
mgr->rot_clk = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct sde_rot_clk) * mgr->num_rot_clk,
|
|
GFP_KERNEL);
|
|
if (!mgr->rot_clk) {
|
|
rc = -ENOMEM;
|
|
mgr->num_rot_clk = 0;
|
|
goto clk_err;
|
|
}
|
|
|
|
if (sde_rotator_search_dt_clk(pdev, mgr, "mnoc_clk",
|
|
SDE_ROTATOR_CLK_MNOC_AHB, false) ||
|
|
sde_rotator_search_dt_clk(pdev, mgr, "gcc_iface",
|
|
SDE_ROTATOR_CLK_GCC_AHB, false) ||
|
|
sde_rotator_search_dt_clk(pdev, mgr, "gcc_bus",
|
|
SDE_ROTATOR_CLK_GCC_AXI, false) ||
|
|
sde_rotator_search_dt_clk(pdev, mgr, "iface_clk",
|
|
SDE_ROTATOR_CLK_MDSS_AHB, true) ||
|
|
sde_rotator_search_dt_clk(pdev, mgr, "axi_clk",
|
|
SDE_ROTATOR_CLK_MDSS_AXI, false) ||
|
|
sde_rotator_search_dt_clk(pdev, mgr, "rot_core_clk",
|
|
SDE_ROTATOR_CLK_MDSS_ROT, false)) {
|
|
rc = -EINVAL;
|
|
goto clk_err;
|
|
}
|
|
|
|
/*
|
|
* If 'MDSS_ROT' is already present, place 'rot_clk' under
|
|
* MDSS_ROT_SUB. Otherwise, place it directly into MDSS_ROT.
|
|
*/
|
|
if (sde_rotator_get_clk(mgr, SDE_ROTATOR_CLK_MDSS_ROT))
|
|
rc = sde_rotator_search_dt_clk(pdev, mgr, "rot_clk",
|
|
SDE_ROTATOR_CLK_MDSS_ROT_SUB, true);
|
|
else
|
|
rc = sde_rotator_search_dt_clk(pdev, mgr, "rot_clk",
|
|
SDE_ROTATOR_CLK_MDSS_ROT, true);
|
|
clk_err:
|
|
return rc;
|
|
}
|
|
|
|
static int sde_rotator_register_clk(struct platform_device *pdev,
|
|
struct sde_rot_mgr *mgr)
|
|
{
|
|
int ret;
|
|
|
|
ret = sde_rotator_parse_dt_clk(pdev, mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("unable to parse clocks\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sde_rotator_unregister_clk(struct sde_rot_mgr *mgr)
|
|
{
|
|
devm_kfree(mgr->device, mgr->rot_clk);
|
|
mgr->rot_clk = NULL;
|
|
mgr->num_rot_clk = 0;
|
|
}
|
|
|
|
static int sde_rotator_res_init(struct platform_device *pdev,
|
|
struct sde_rot_mgr *mgr)
|
|
{
|
|
int ret;
|
|
|
|
if (!sde_rot_mgr_pd_enabled(mgr)) {
|
|
ret = sde_rotator_get_dt_vreg_data(
|
|
&pdev->dev, &mgr->module_power);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = sde_rotator_register_clk(pdev, mgr);
|
|
if (ret)
|
|
goto error;
|
|
|
|
return 0;
|
|
error:
|
|
sde_rotator_put_dt_vreg_data(&pdev->dev, &mgr->module_power);
|
|
return ret;
|
|
}
|
|
|
|
static void sde_rotator_res_destroy(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct platform_device *pdev = mgr->pdev;
|
|
|
|
sde_rotator_unregister_clk(mgr);
|
|
sde_rotator_bus_scale_unregister(mgr);
|
|
|
|
if (!sde_rot_mgr_pd_enabled(mgr))
|
|
sde_rotator_put_dt_vreg_data(&pdev->dev, &mgr->module_power);
|
|
}
|
|
|
|
int sde_rotator_core_init(struct sde_rot_mgr **pmgr,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
|
|
struct sde_rot_mgr *mgr;
|
|
int ret;
|
|
|
|
if (!pmgr || !pdev) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mgr = devm_kzalloc(&pdev->dev, sizeof(struct sde_rot_mgr),
|
|
GFP_KERNEL);
|
|
if (!mgr)
|
|
return -ENOMEM;
|
|
|
|
mgr->pdev = pdev;
|
|
mgr->device = &pdev->dev;
|
|
mgr->pending_close_bw_vote = 0;
|
|
mgr->enable_bw_vote = ROT_ENABLE_BW_VOTE;
|
|
mgr->hwacquire_timeout = ROT_HW_ACQUIRE_TIMEOUT_IN_MS;
|
|
mgr->queue_count = 1;
|
|
mgr->pixel_per_clk.numer = ROT_PIXEL_PER_CLK_NUMERATOR;
|
|
mgr->pixel_per_clk.denom = ROT_PIXEL_PER_CLK_DENOMINATOR;
|
|
mgr->fudge_factor.numer = ROT_FUDGE_FACTOR_NUMERATOR;
|
|
mgr->fudge_factor.denom = ROT_FUDGE_FACTOR_DENOMINATOR;
|
|
mgr->overhead.numer = ROT_OVERHEAD_NUMERATOR;
|
|
mgr->overhead.denom = ROT_OVERHEAD_DENOMINATOR;
|
|
|
|
mutex_init(&mgr->lock);
|
|
atomic_set(&mgr->device_suspended, 0);
|
|
INIT_LIST_HEAD(&mgr->file_list);
|
|
|
|
ret = sysfs_create_group(&mgr->device->kobj,
|
|
&sde_rotator_fs_attr_group);
|
|
if (ret) {
|
|
SDEROT_ERR("unable to register rotator sysfs nodes\n");
|
|
goto error_create_sysfs;
|
|
}
|
|
|
|
ret = sde_rotator_parse_dt(mgr, pdev);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to parse the dt\n");
|
|
goto error_parse_dt;
|
|
}
|
|
|
|
ret = sde_rotator_res_init(pdev, mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("res_init failed %d\n", ret);
|
|
goto error_res_init;
|
|
}
|
|
|
|
*pmgr = mgr;
|
|
ret = sde_rotator_footswitch_ctrl(mgr, true);
|
|
if (ret) {
|
|
SDEROT_INFO("res_init failed %d, use probe defer\n", ret);
|
|
ret = -EPROBE_DEFER;
|
|
goto error_fs_en_fail;
|
|
}
|
|
|
|
/* enable power and clock before h/w initialization/query */
|
|
sde_rotator_update_clk(mgr);
|
|
sde_rotator_resource_ctrl(mgr, true);
|
|
sde_rotator_clk_ctrl(mgr, true);
|
|
|
|
mdata->mdss_version = SDE_REG_READ(mdata, SDE_REG_HW_VERSION);
|
|
SDEROT_DBG("mdss revision %x\n", mdata->mdss_version);
|
|
|
|
if (IS_SDE_MAJOR_MINOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_107)) {
|
|
mgr->ops_hw_init = sde_rotator_r1_init;
|
|
} else if (IS_SDE_MAJOR_MINOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_300) ||
|
|
IS_SDE_MAJOR_MINOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_400) ||
|
|
IS_SDE_MAJOR_MINOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_410) ||
|
|
IS_SDE_MAJOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_500) ||
|
|
IS_SDE_MAJOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_600)) {
|
|
mgr->ops_hw_init = sde_rotator_r3_init;
|
|
mgr->min_rot_clk = ROT_MIN_ROT_CLK;
|
|
|
|
/*
|
|
* on platforms where the maxlinewidth is greater than
|
|
* default we need to have a max clock rate check to
|
|
* ensure we do not cross the max allowed clock for rotator
|
|
*/
|
|
if (IS_SDE_MAJOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_500))
|
|
mgr->max_rot_clk = ROT_R3_MAX_ROT_CLK;
|
|
|
|
if (!IS_SDE_MAJOR_SAME(mdata->mdss_version,
|
|
SDE_MDP_HW_REV_600) &&
|
|
!sde_rotator_get_clk(mgr,
|
|
SDE_ROTATOR_CLK_MDSS_AXI)) {
|
|
SDEROT_ERR("unable to get mdss_axi_clk\n");
|
|
ret = -EINVAL;
|
|
goto error_map_hw_ops;
|
|
}
|
|
} else {
|
|
ret = -ENODEV;
|
|
SDEROT_ERR("unsupported sde version %x\n",
|
|
mdata->mdss_version);
|
|
goto error_map_hw_ops;
|
|
}
|
|
|
|
ret = mgr->ops_hw_init(mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("hw init failed %d\n", ret);
|
|
goto error_hw_init;
|
|
}
|
|
|
|
ret = sde_rotator_init_queue(mgr);
|
|
if (ret) {
|
|
SDEROT_ERR("fail to init queue\n");
|
|
goto error_init_queue;
|
|
}
|
|
|
|
/* disable power and clock after h/w initialization/query */
|
|
sde_rotator_clk_ctrl(mgr, false);
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
sde_rotator_footswitch_ctrl(mgr, false);
|
|
pm_runtime_set_suspended(&pdev->dev);
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
return 0;
|
|
|
|
error_init_queue:
|
|
mgr->ops_hw_destroy(mgr);
|
|
error_hw_init:
|
|
error_map_hw_ops:
|
|
sde_rotator_clk_ctrl(mgr, false);
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
sde_rotator_footswitch_ctrl(mgr, false);
|
|
error_fs_en_fail:
|
|
sde_rotator_res_destroy(mgr);
|
|
error_res_init:
|
|
error_parse_dt:
|
|
sysfs_remove_group(&mgr->device->kobj, &sde_rotator_fs_attr_group);
|
|
error_create_sysfs:
|
|
devm_kfree(&pdev->dev, mgr);
|
|
*pmgr = NULL;
|
|
return ret;
|
|
}
|
|
|
|
void sde_rotator_core_destroy(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct device *dev;
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
dev = mgr->device;
|
|
sde_rotator_deinit_queue(mgr);
|
|
mgr->ops_hw_destroy(mgr);
|
|
sde_rotator_release_all(mgr);
|
|
pm_runtime_disable(mgr->device);
|
|
sde_rotator_res_destroy(mgr);
|
|
sysfs_remove_group(&mgr->device->kobj, &sde_rotator_fs_attr_group);
|
|
devm_kfree(dev, mgr);
|
|
}
|
|
|
|
void sde_rotator_core_dump(struct sde_rot_mgr *mgr)
|
|
{
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
sde_rotator_resource_ctrl(mgr, true);
|
|
|
|
if (mgr->ops_hw_dump_status)
|
|
mgr->ops_hw_dump_status(mgr);
|
|
|
|
SDEROT_EVTLOG_TOUT_HANDLER("rot", "rot_dbg_bus", "vbif_dbg_bus");
|
|
|
|
sde_rotator_resource_ctrl(mgr, false);
|
|
}
|
|
|
|
static void sde_rotator_suspend_cancel_rot_work(struct sde_rot_mgr *mgr)
|
|
{
|
|
struct sde_rot_file_private *priv, *priv_next;
|
|
|
|
list_for_each_entry_safe(priv, priv_next, &mgr->file_list, list) {
|
|
sde_rotator_cancel_all_requests(mgr, priv);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_PM)
|
|
/*
|
|
* sde_rotator_runtime_suspend - Turn off power upon runtime suspend event
|
|
* @dev: Pointer to device structure
|
|
*/
|
|
int sde_rotator_runtime_suspend(struct device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (mgr->rot_enable_clk_cnt) {
|
|
SDEROT_ERR("invalid runtime suspend request %d\n",
|
|
mgr->rot_enable_clk_cnt);
|
|
return -EBUSY;
|
|
}
|
|
|
|
sde_rotator_footswitch_ctrl(mgr, false);
|
|
ATRACE_END("runtime_active");
|
|
SDEROT_DBG("exit runtime_active\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_runtime_resume - Turn on power upon runtime resume event
|
|
* @dev: Pointer to device structure
|
|
*/
|
|
int sde_rotator_runtime_resume(struct device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
SDEROT_DBG("begin runtime_active\n");
|
|
ATRACE_BEGIN("runtime_active");
|
|
return sde_rotator_footswitch_ctrl(mgr, true);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_runtime_idle - check if device is idling
|
|
* @dev: Pointer to device structure
|
|
*/
|
|
int sde_rotator_runtime_idle(struct device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* add check for any busy status, if any */
|
|
SDEROT_DBG("idling ...\n");
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
/*
|
|
* sde_rotator_pm_suspend - put the device in pm suspend state by cancelling
|
|
* all active requests
|
|
* @dev: Pointer to device structure
|
|
*/
|
|
int sde_rotator_pm_suspend(struct device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
atomic_inc(&mgr->device_suspended);
|
|
sde_rotator_suspend_cancel_rot_work(mgr);
|
|
mgr->minimum_bw_vote = 0;
|
|
sde_rotator_update_perf(mgr);
|
|
ATRACE_END("pm_active");
|
|
SDEROT_DBG("end pm active %d\n", atomic_read(&mgr->device_suspended));
|
|
sde_rot_mgr_unlock(mgr);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_pm_resume - put the device in pm active state
|
|
* @dev: Pointer to device structure
|
|
*/
|
|
int sde_rotator_pm_resume(struct device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_device(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* It is possible that the runtime status of the device may
|
|
* have been active when the system was suspended. Reset the runtime
|
|
* status to suspended state after a complete system resume.
|
|
*/
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
SDEROT_DBG("begin pm active %d\n", atomic_read(&mgr->device_suspended));
|
|
ATRACE_BEGIN("pm_active");
|
|
atomic_dec(&mgr->device_suspended);
|
|
sde_rotator_update_perf(mgr);
|
|
sde_rot_mgr_unlock(mgr);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
|
|
int sde_rotator_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_pdevice(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null_parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
atomic_inc(&mgr->device_suspended);
|
|
sde_rotator_suspend_cancel_rot_work(mgr);
|
|
sde_rotator_update_perf(mgr);
|
|
sde_rot_mgr_unlock(mgr);
|
|
return 0;
|
|
}
|
|
|
|
int sde_rotator_resume(struct platform_device *dev)
|
|
{
|
|
struct sde_rot_mgr *mgr;
|
|
|
|
mgr = sde_rot_mgr_from_pdevice(dev);
|
|
|
|
if (!mgr) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
sde_rot_mgr_lock(mgr);
|
|
atomic_dec(&mgr->device_suspended);
|
|
sde_rotator_update_perf(mgr);
|
|
sde_rot_mgr_unlock(mgr);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* sde_rotator_session_open - external wrapper for open function
|
|
*
|
|
* Note each file open (sde_rot_file_private) is mapped to one session only.
|
|
*/
|
|
int sde_rotator_session_open(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private **pprivate, int session_id,
|
|
struct sde_rot_queue_v1 *queue)
|
|
{
|
|
int ret;
|
|
struct sde_rot_file_private *private;
|
|
|
|
if (!mgr || !pprivate || !queue) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = sde_rotator_open(mgr, &private);
|
|
if (ret)
|
|
goto error_open;
|
|
|
|
private->mgr = mgr;
|
|
private->fenceq = queue;
|
|
|
|
ret = sde_rotator_open_session(mgr, private, session_id);
|
|
if (ret)
|
|
goto error_open_session;
|
|
|
|
*pprivate = private;
|
|
|
|
return 0;
|
|
error_open_session:
|
|
sde_rotator_close(mgr, private);
|
|
error_open:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_session_close - external wrapper for close function
|
|
*/
|
|
void sde_rotator_session_close(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private, int session_id)
|
|
{
|
|
if (!mgr || !private) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return;
|
|
}
|
|
|
|
sde_rotator_close_session(mgr, private, session_id);
|
|
sde_rotator_close(mgr, private);
|
|
|
|
SDEROT_DBG("session closed s:%d\n", session_id);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_session_config - external wrapper for config function
|
|
*/
|
|
int sde_rotator_session_config(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
if (!mgr || !private || !config) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return sde_rotator_config_session(mgr, private, config);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_session_validate - validate session
|
|
*/
|
|
int sde_rotator_session_validate(struct sde_rot_mgr *mgr,
|
|
struct sde_rot_file_private *private,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
int ret;
|
|
|
|
if (!mgr || !private || !config) {
|
|
SDEROT_ERR("null parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDEROT_DBG(
|
|
"validate session id=%u in{%u,%u}f:%x out{%u,%u}f:%x fps:%d\n",
|
|
config->session_id, config->input.width, config->input.height,
|
|
config->input.format, config->output.width,
|
|
config->output.height, config->output.format,
|
|
config->frame_rate);
|
|
|
|
ret = sde_rotator_verify_config_all(mgr, config);
|
|
if (ret) {
|
|
SDEROT_WARN("rotator verify format failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (config->output.sbuf && mgr->sbuf_ctx != private && mgr->sbuf_ctx) {
|
|
SDEROT_WARN("too many sbuf sessions\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|