
When poms supported panel is booting up with video mode rsc timers are not configured. Made changes to update rsc timers configuration during bootup with video mode as well, to avoid rsc stuck issues during poms switch. Change-Id: I8c03b3e5483c17f73e3d8c6b57bd8d3eabb33b10 Signed-off-by: Andhavarapu Karthik <quic_kartkart@quicinc.com>
1930 行
52 KiB
C
1930 行
52 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "[sde_rsc:%s:%d]: " fmt, __func__, __LINE__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/of.h>
|
|
#include <linux/string.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/component.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <soc/qcom/rpmh.h>
|
|
#include "msm_drv.h"
|
|
#include "sde_rsc_priv.h"
|
|
#include "sde_dbg.h"
|
|
#include "sde_trace.h"
|
|
|
|
#define SDE_RSC_DRV_DBG_NAME "sde_rsc_drv"
|
|
#define SDE_RSC_WRAPPER_DBG_NAME "sde_rsc_wrapper"
|
|
|
|
#define SINGLE_TCS_EXECUTION_TIME_V1 1064000
|
|
#define SINGLE_TCS_EXECUTION_TIME_V2 930000
|
|
#define SINGLE_TCS_EXECUTION_TIME_V3 930000
|
|
#define SINGLE_TCS_EXECUTION_TIME_V4 930000
|
|
#define SINGLE_TCS_EXECUTION_TIME_V5 650000
|
|
|
|
#define RSC_MODE_INSTRUCTION_TIME 100
|
|
#define RSC_MODE_THRESHOLD_OVERHEAD 2700
|
|
|
|
/**
|
|
* rsc_min_threshold will be set to MIN_THRESHOLD_OVERHEAD_TIME which
|
|
* takes into account back off time + overhead from RSC/RSC_WRAPPER. The
|
|
* overhead buffer time is required to be greater than 14. Program it
|
|
* with a higher value (3.3 ms), so it has sufficient time to complete
|
|
* the sequence in rare cases.
|
|
*/
|
|
#define MIN_THRESHOLD_OVERHEAD_TIME 64
|
|
|
|
#define DEFAULT_PANEL_FPS 60
|
|
#define DEFAULT_PANEL_JITTER_NUMERATOR 2
|
|
#define DEFAULT_PANEL_JITTER_DENOMINATOR 1
|
|
#define DEFAULT_PANEL_PREFILL_LINES 25
|
|
#define DEFAULT_PANEL_VTOTAL (480 + DEFAULT_PANEL_PREFILL_LINES)
|
|
#define TICKS_IN_NANO_SECOND 1000000000
|
|
|
|
#define MAX_BUFFER_SIZE 256
|
|
|
|
#define CMD_MODE_SWITCH_SUCCESS 0xFFFF
|
|
#define VID_MODE_SWITCH_SUCCESS 0xFFFE
|
|
#define CLK_MODE_SWITCH_SUCCESS 0xFFFD
|
|
#define STATE_UPDATE_NOT_ALLOWED 0xFFFC
|
|
|
|
/* Primary panel worst case VSYNC expected to be no less than 30fps */
|
|
#define PRIMARY_VBLANK_WORST_CASE_MS 34
|
|
|
|
#define DEFAULT_PANEL_MIN_V_PREFILL 35
|
|
|
|
/* add 10ms constant for low fps cases and use default timeout for existing cases */
|
|
#define RSC_VSYNC_TIMEOUT_MS(x) ((x && x->cmd_config.fps < 30) ? \
|
|
((1000 / x->cmd_config.fps) + 10) : PRIMARY_VBLANK_WORST_CASE_MS)
|
|
|
|
static struct sde_rsc_priv *rsc_prv_list[MAX_RSC_COUNT];
|
|
static struct device *rpmh_dev[MAX_RSC_COUNT];
|
|
|
|
/**
|
|
* sde_rsc_client_create() - create the client for sde rsc.
|
|
* Different displays like DSI, HDMI, DP, WB, etc should call this
|
|
* api to register their vote for rpmh. They still need to vote for
|
|
* power handle to get the clocks.
|
|
|
|
* @rsc_index: A client will be created on this RSC. As of now only
|
|
* SDE_RSC_INDEX is valid rsc index.
|
|
* @name: Caller needs to provide some valid string to identify
|
|
* the client. "primary", "dp", "hdmi" are suggested name.
|
|
* @is_primary: Caller needs to provide information if client is primary
|
|
* or not. Primary client votes will be redirected to
|
|
* display rsc.
|
|
* @vsync_source: This parameter is only valid for primary display. It provides
|
|
* vsync source information
|
|
*
|
|
* Return: client node pointer.
|
|
*/
|
|
struct sde_rsc_client *sde_rsc_client_create(u32 rsc_index, char *client_name,
|
|
enum sde_rsc_client_type client_type, u32 vsync_source)
|
|
{
|
|
struct sde_rsc_client *client;
|
|
struct sde_rsc_priv *rsc;
|
|
static int id;
|
|
|
|
if (!client_name) {
|
|
pr_err("client name is null- not supported\n");
|
|
return ERR_PTR(-EINVAL);
|
|
} else if (rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return ERR_PTR(-EINVAL);
|
|
} else if (!rsc_prv_list[rsc_index]) {
|
|
pr_debug("rsc not probed yet or not available\n");
|
|
return NULL;
|
|
}
|
|
|
|
rsc = rsc_prv_list[rsc_index];
|
|
client = kzalloc(sizeof(struct sde_rsc_client), GFP_KERNEL);
|
|
if (!client)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
strlcpy(client->name, client_name, MAX_RSC_CLIENT_NAME_LEN);
|
|
client->current_state = SDE_RSC_IDLE_STATE;
|
|
client->rsc_index = rsc_index;
|
|
client->id = id;
|
|
client->client_type = client_type;
|
|
if (client->client_type == SDE_RSC_PRIMARY_DISP_CLIENT) {
|
|
rsc->primary_client = client;
|
|
rsc->vsync_source = vsync_source;
|
|
}
|
|
pr_debug("client %s rsc index:%d client_type:%d\n", client_name,
|
|
rsc_index, client->client_type);
|
|
|
|
list_add(&client->list, &rsc->client_list);
|
|
id++;
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
return client;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_client_create);
|
|
|
|
/**
|
|
* sde_rsc_client_destroy() - Destroy the sde rsc client.
|
|
*
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
*
|
|
* Return: none
|
|
*/
|
|
void sde_rsc_client_destroy(struct sde_rsc_client *client)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
enum sde_rsc_state state;
|
|
|
|
if (!client) {
|
|
pr_debug("invalid client\n");
|
|
goto end;
|
|
} else if (client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("client %s destroyed\n", client->name);
|
|
rsc = rsc_prv_list[client->rsc_index];
|
|
if (!rsc)
|
|
goto end;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
state = client->current_state;
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
if (state != SDE_RSC_IDLE_STATE) {
|
|
int wait_vblank_crtc_id;
|
|
|
|
sde_rsc_client_state_update(client, SDE_RSC_IDLE_STATE, NULL,
|
|
SDE_RSC_INVALID_CRTC_ID, &wait_vblank_crtc_id);
|
|
|
|
/* if vblank wait required at shutdown, use a simple sleep */
|
|
if (wait_vblank_crtc_id != SDE_RSC_INVALID_CRTC_ID) {
|
|
pr_err("unexpected sleep required on crtc %d at rsc client destroy\n",
|
|
wait_vblank_crtc_id);
|
|
SDE_EVT32(client->id, state, rsc->current_state,
|
|
client->crtc_id, wait_vblank_crtc_id,
|
|
SDE_EVTLOG_ERROR);
|
|
msleep(RSC_VSYNC_TIMEOUT_MS(rsc));
|
|
}
|
|
}
|
|
mutex_lock(&rsc->client_lock);
|
|
list_del_init(&client->list);
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
kfree(client);
|
|
end:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_client_destroy);
|
|
|
|
struct sde_rsc_event *sde_rsc_register_event(int rsc_index, uint32_t event_type,
|
|
void (*cb_func)(uint32_t event_type, void *usr), void *usr)
|
|
{
|
|
struct sde_rsc_event *evt;
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index:%d\n", rsc_index);
|
|
return ERR_PTR(-EINVAL);
|
|
} else if (!rsc_prv_list[rsc_index]) {
|
|
pr_err("rsc idx:%d not probed yet or not available\n",
|
|
rsc_index);
|
|
return ERR_PTR(-EINVAL);
|
|
} else if (!cb_func || !event_type) {
|
|
pr_err("no event or cb func\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
rsc = rsc_prv_list[rsc_index];
|
|
evt = kzalloc(sizeof(struct sde_rsc_event), GFP_KERNEL);
|
|
if (!evt)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
evt->event_type = event_type;
|
|
evt->rsc_index = rsc_index;
|
|
evt->usr = usr;
|
|
evt->cb_func = cb_func;
|
|
pr_debug("event register type:%d rsc index:%d\n",
|
|
event_type, rsc_index);
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
list_add(&evt->list, &rsc->event_list);
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
return evt;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_register_event);
|
|
|
|
void sde_rsc_unregister_event(struct sde_rsc_event *event)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (!event) {
|
|
pr_debug("invalid event client\n");
|
|
goto end;
|
|
} else if (event->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("event client destroyed\n");
|
|
rsc = rsc_prv_list[event->rsc_index];
|
|
if (!rsc)
|
|
goto end;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
list_del_init(&event->list);
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
kfree(event);
|
|
end:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_unregister_event);
|
|
|
|
bool is_sde_rsc_available(int rsc_index)
|
|
{
|
|
if (rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index:%d\n", rsc_index);
|
|
return false;
|
|
} else if (!rsc_prv_list[rsc_index]) {
|
|
pr_debug("rsc idx:%d not probed yet or not available\n",
|
|
rsc_index);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(is_sde_rsc_available);
|
|
|
|
enum sde_rsc_state get_sde_rsc_current_state(int rsc_index)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index:%d\n", rsc_index);
|
|
return SDE_RSC_IDLE_STATE;
|
|
} else if (!rsc_prv_list[rsc_index]) {
|
|
pr_err("rsc idx:%d not probed yet or not available\n",
|
|
rsc_index);
|
|
return SDE_RSC_IDLE_STATE;
|
|
}
|
|
|
|
rsc = rsc_prv_list[rsc_index];
|
|
return rsc->current_state;
|
|
}
|
|
EXPORT_SYMBOL(get_sde_rsc_current_state);
|
|
|
|
static u32 sde_rsc_timer_calculate(struct sde_rsc_priv *rsc,
|
|
struct sde_rsc_cmd_config *cmd_config, enum sde_rsc_state state)
|
|
{
|
|
const u32 cxo_period_ns = 52;
|
|
u64 rsc_backoff_time_ns = rsc->backoff_time_ns;
|
|
u64 rsc_mode_threshold_time_ns = rsc->mode_threshold_time_ns;
|
|
u64 rsc_time_slot_0_ns = rsc->time_slot_0_ns;
|
|
u64 rsc_time_slot_1_ns;
|
|
const u64 pdc_jitter = 20; /* 20% more */
|
|
|
|
u64 frame_time_ns, frame_jitter;
|
|
u64 line_time_ns, prefill_time_ns;
|
|
u64 pdc_backoff_time_ns;
|
|
s64 total;
|
|
int ret = 0;
|
|
u32 default_prefill_lines;
|
|
|
|
if (cmd_config)
|
|
memcpy(&rsc->cmd_config, cmd_config, sizeof(*cmd_config));
|
|
|
|
/* calculate for 640x480 60 fps resolution by default */
|
|
if (!rsc->cmd_config.fps)
|
|
rsc->cmd_config.fps = DEFAULT_PANEL_FPS;
|
|
if (!rsc->cmd_config.jitter_numer)
|
|
rsc->cmd_config.jitter_numer = DEFAULT_PANEL_JITTER_NUMERATOR;
|
|
if (!rsc->cmd_config.jitter_denom)
|
|
rsc->cmd_config.jitter_denom = DEFAULT_PANEL_JITTER_DENOMINATOR;
|
|
if (!rsc->cmd_config.vtotal)
|
|
rsc->cmd_config.vtotal = DEFAULT_PANEL_VTOTAL;
|
|
|
|
default_prefill_lines = (rsc->cmd_config.fps *
|
|
DEFAULT_PANEL_MIN_V_PREFILL) / DEFAULT_PANEL_FPS;
|
|
if (!rsc->cmd_config.prefill_lines)
|
|
rsc->cmd_config.prefill_lines = default_prefill_lines;
|
|
|
|
pr_debug("frame fps:%d jitter_numer:%d jitter_denom:%d vtotal:%d prefill lines:%d\n",
|
|
rsc->cmd_config.fps, rsc->cmd_config.jitter_numer,
|
|
rsc->cmd_config.jitter_denom, rsc->cmd_config.vtotal,
|
|
rsc->cmd_config.prefill_lines);
|
|
|
|
/* 1 nano second */
|
|
frame_time_ns = TICKS_IN_NANO_SECOND;
|
|
frame_time_ns = div_u64(frame_time_ns, rsc->cmd_config.fps);
|
|
|
|
frame_jitter = frame_time_ns * rsc->cmd_config.jitter_numer;
|
|
frame_jitter = div_u64(frame_jitter, rsc->cmd_config.jitter_denom);
|
|
/* convert it to percentage */
|
|
frame_jitter = div_u64(frame_jitter, 100);
|
|
|
|
line_time_ns = frame_time_ns;
|
|
line_time_ns = div_u64(line_time_ns, rsc->cmd_config.vtotal);
|
|
prefill_time_ns = line_time_ns * rsc->cmd_config.prefill_lines;
|
|
|
|
total = frame_time_ns - frame_jitter - prefill_time_ns;
|
|
if (total < 0) {
|
|
pr_err("invalid total time period time:%llu jiter_time:%llu blanking time:%llu\n",
|
|
frame_time_ns, frame_jitter, prefill_time_ns);
|
|
total = 0;
|
|
}
|
|
|
|
total = div_u64(total, cxo_period_ns);
|
|
rsc->timer_config.static_wakeup_time_ns = total;
|
|
|
|
pr_debug("frame time:%llu frame jiter_time:%llu\n",
|
|
frame_time_ns, frame_jitter);
|
|
pr_debug("line time:%llu prefill time ps:%llu\n",
|
|
line_time_ns, prefill_time_ns);
|
|
pr_debug("static wakeup time:%lld cxo:%u\n", total, cxo_period_ns);
|
|
|
|
pdc_backoff_time_ns = rsc_backoff_time_ns;
|
|
rsc_backoff_time_ns = div_u64(rsc_backoff_time_ns, cxo_period_ns);
|
|
rsc->timer_config.rsc_backoff_time_ns = (u32) rsc_backoff_time_ns;
|
|
|
|
pdc_backoff_time_ns *= pdc_jitter;
|
|
pdc_backoff_time_ns = div_u64(pdc_backoff_time_ns, 100);
|
|
rsc->timer_config.pdc_backoff_time_ns = (u32) pdc_backoff_time_ns;
|
|
|
|
rsc_mode_threshold_time_ns =
|
|
div_u64(rsc_mode_threshold_time_ns, cxo_period_ns);
|
|
rsc->timer_config.rsc_mode_threshold_time_ns
|
|
= (u32) rsc_mode_threshold_time_ns;
|
|
|
|
/* time_slot_0 for mode0 latency */
|
|
rsc_time_slot_0_ns = div_u64(rsc_time_slot_0_ns, cxo_period_ns);
|
|
rsc->timer_config.rsc_time_slot_0_ns = (u32) rsc_time_slot_0_ns;
|
|
|
|
/* time_slot_1 for mode1 latency - 1 fps */
|
|
rsc_time_slot_1_ns = div_u64(TICKS_IN_NANO_SECOND, cxo_period_ns);
|
|
rsc->timer_config.rsc_time_slot_1_ns = (u32) rsc_time_slot_1_ns;
|
|
|
|
/* mode 2 is infinite */
|
|
rsc->timer_config.rsc_time_slot_2_ns = 0xFFFFFFFF;
|
|
|
|
rsc->timer_config.min_threshold_time_ns = MIN_THRESHOLD_OVERHEAD_TIME;
|
|
rsc->timer_config.bwi_threshold_time_ns =
|
|
rsc->timer_config.rsc_time_slot_0_ns;
|
|
|
|
/* timer update should be called with client call */
|
|
if (cmd_config && rsc->hw_ops.timer_update) {
|
|
ret = rsc->hw_ops.timer_update(rsc);
|
|
if (ret)
|
|
pr_err("sde rsc: hw timer update failed ret:%d\n", ret);
|
|
/* rsc init should be called during rsc probe - one time only */
|
|
} else if (rsc->hw_ops.init) {
|
|
ret = rsc->hw_ops.init(rsc);
|
|
if (ret)
|
|
pr_err("sde rsc: hw init failed ret:%d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rsc_resource_disable(struct sde_rsc_priv *rsc)
|
|
{
|
|
struct sde_power_handle *phandle;
|
|
struct dss_module_power *mp;
|
|
|
|
if (!rsc) {
|
|
pr_err("invalid drv data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_read(&rsc->resource_refcount) == 0) {
|
|
pr_err("%pS: invalid rsc resource disable call\n",
|
|
__builtin_return_address(0));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_dec_return(&rsc->resource_refcount) != 0)
|
|
return 0;
|
|
|
|
phandle = &rsc->phandle;
|
|
mp = &phandle->mp;
|
|
msm_dss_enable_clk(mp->clk_config, mp->num_clk, false);
|
|
sde_power_scale_reg_bus(phandle, VOTE_INDEX_DISABLE, false);
|
|
msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rsc_resource_enable(struct sde_rsc_priv *rsc)
|
|
{
|
|
struct sde_power_handle *phandle;
|
|
struct dss_module_power *mp;
|
|
int rc = 0;
|
|
|
|
if (!rsc) {
|
|
pr_err("invalid drv data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_inc_return(&rsc->resource_refcount) != 1)
|
|
return 0;
|
|
|
|
phandle = &rsc->phandle;
|
|
mp = &phandle->mp;
|
|
rc = msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, true);
|
|
if (rc) {
|
|
pr_err("failed to enable vregs rc=%d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
rc = sde_power_scale_reg_bus(phandle, VOTE_INDEX_LOW, false);
|
|
if (rc) {
|
|
pr_err("failed to set reg bus vote rc=%d\n", rc);
|
|
goto reg_bus_hdl_err;
|
|
}
|
|
|
|
rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, true);
|
|
if (rc) {
|
|
pr_err("clock enable failed rc:%d\n", rc);
|
|
goto clk_err;
|
|
}
|
|
|
|
return rc;
|
|
|
|
clk_err:
|
|
sde_power_scale_reg_bus(phandle, VOTE_INDEX_DISABLE, false);
|
|
|
|
reg_bus_hdl_err:
|
|
msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, false);
|
|
end:
|
|
atomic_dec(&rsc->resource_refcount);
|
|
return rc;
|
|
}
|
|
|
|
static int sde_rsc_switch_to_cmd(struct sde_rsc_priv *rsc,
|
|
struct sde_rsc_cmd_config *config,
|
|
struct sde_rsc_client *caller_client,
|
|
int *wait_vblank_crtc_id)
|
|
{
|
|
struct sde_rsc_client *client;
|
|
int rc = STATE_UPDATE_NOT_ALLOWED;
|
|
|
|
if (!rsc->primary_client) {
|
|
pr_err("primary client not available for cmd state switch\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
} else if (caller_client != rsc->primary_client) {
|
|
pr_err("primary client state:%d not cmd state request\n",
|
|
rsc->primary_client->current_state);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* update timers - might not be available at next switch */
|
|
if (config)
|
|
sde_rsc_timer_calculate(rsc, config, SDE_RSC_CMD_STATE);
|
|
|
|
/**
|
|
* rsc clients can still send config at any time. If a config is
|
|
* received during cmd_state then vsync_wait will execute with the logic
|
|
* below. If a config is received when rsc is in AMC mode; A mode
|
|
* switch will do the vsync wait. updated checks still support all cases
|
|
* for dynamic mode switch and inline rotation.
|
|
*/
|
|
if (rsc->current_state == SDE_RSC_CMD_STATE) {
|
|
rc = 0;
|
|
if (config && rsc->version < SDE_RSC_REV_3)
|
|
goto vsync_wait;
|
|
else
|
|
goto end;
|
|
}
|
|
|
|
/* any non-primary clk state client blocks the cmd state switch */
|
|
list_for_each_entry(client, &rsc->client_list, list)
|
|
if (client->current_state == SDE_RSC_CLK_STATE &&
|
|
client->client_type == SDE_RSC_EXTERNAL_DISP_CLIENT)
|
|
goto end;
|
|
|
|
if (rsc->hw_ops.state_update) {
|
|
rc = rsc->hw_ops.state_update(rsc, SDE_RSC_CMD_STATE);
|
|
if (!rc)
|
|
rpmh_mode_solver_set(rsc->rpmh_dev, true);
|
|
}
|
|
|
|
/* vsync wait not needed during VID->CMD switch (rev 4+ HW only) */
|
|
if (rsc->current_state == SDE_RSC_VID_STATE &&
|
|
rsc->version >= SDE_RSC_REV_4) {
|
|
rc = 0;
|
|
goto end;
|
|
}
|
|
|
|
vsync_wait:
|
|
/* indicate wait for vsync for vid to cmd state switch & cfg update */
|
|
if (!rc && (rsc->current_state == SDE_RSC_VID_STATE ||
|
|
rsc->current_state == SDE_RSC_CMD_STATE)) {
|
|
rsc->post_poms = true;
|
|
|
|
/* clear VSYNC timestamp for indication when update completes */
|
|
if (rsc->hw_ops.hw_vsync)
|
|
rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
|
|
if (!wait_vblank_crtc_id) {
|
|
pr_err("invalid crtc id wait pointer, client %d\n",
|
|
caller_client->id);
|
|
SDE_EVT32(caller_client->id, rsc->current_state,
|
|
caller_client->crtc_id,
|
|
wait_vblank_crtc_id, SDE_EVTLOG_ERROR);
|
|
msleep(RSC_VSYNC_TIMEOUT_MS(rsc));
|
|
} else {
|
|
*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
|
|
}
|
|
}
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int sde_rsc_switch_to_clk(struct sde_rsc_priv *rsc,
|
|
int *wait_vblank_crtc_id)
|
|
{
|
|
struct sde_rsc_client *client;
|
|
int rc = STATE_UPDATE_NOT_ALLOWED;
|
|
bool multi_display_active = false;
|
|
bool vid_display_active = false, cmd_display_active = false;
|
|
|
|
list_for_each_entry(client, &rsc->client_list, list) {
|
|
if (client->current_state == SDE_RSC_CLK_STATE &&
|
|
client->client_type == SDE_RSC_EXTERNAL_DISP_CLIENT)
|
|
multi_display_active = true;
|
|
else if (client->current_state == SDE_RSC_VID_STATE)
|
|
vid_display_active = true;
|
|
else if (client->current_state == SDE_RSC_CMD_STATE)
|
|
cmd_display_active = true;
|
|
}
|
|
|
|
pr_debug("multi_display:%d vid_display:%d cmd_display:%d\n",
|
|
multi_display_active, vid_display_active, cmd_display_active);
|
|
if (!multi_display_active && (vid_display_active || cmd_display_active))
|
|
goto end;
|
|
|
|
if (rsc->hw_ops.state_update) {
|
|
rc = rsc->hw_ops.state_update(rsc, SDE_RSC_CLK_STATE);
|
|
if (!rc)
|
|
rpmh_mode_solver_set(rsc->rpmh_dev, false);
|
|
}
|
|
|
|
/* indicate wait for vsync for cmd/vid to clk state switch */
|
|
if (!rc && rsc->primary_client &&
|
|
(rsc->current_state == SDE_RSC_CMD_STATE ||
|
|
rsc->current_state == SDE_RSC_VID_STATE)) {
|
|
/* clear VSYNC timestamp for indication when update completes */
|
|
if (rsc->hw_ops.hw_vsync)
|
|
rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
|
|
if (!wait_vblank_crtc_id) {
|
|
pr_err("invalid crtc id wait pointer provided\n");
|
|
msleep(RSC_VSYNC_TIMEOUT_MS(rsc));
|
|
} else {
|
|
*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
|
|
|
|
/* increase refcount, so we wait for the next vsync */
|
|
atomic_inc(&rsc->rsc_vsync_wait);
|
|
SDE_EVT32(atomic_read(&rsc->rsc_vsync_wait));
|
|
}
|
|
} else if (atomic_read(&rsc->rsc_vsync_wait)) {
|
|
SDE_EVT32(rsc->primary_client, rsc->current_state,
|
|
atomic_read(&rsc->rsc_vsync_wait));
|
|
|
|
/* Wait for the vsync, if the refcount is set */
|
|
rc = wait_event_timeout(rsc->rsc_vsync_waitq,
|
|
atomic_read(&rsc->rsc_vsync_wait) == 0,
|
|
msecs_to_jiffies(RSC_VSYNC_TIMEOUT_MS(rsc) * 2));
|
|
if (!rc) {
|
|
pr_err("Timeout waiting for vsync\n");
|
|
rc = -ETIMEDOUT;
|
|
SDE_EVT32(atomic_read(&rsc->rsc_vsync_wait), rc,
|
|
SDE_EVTLOG_ERROR);
|
|
} else {
|
|
SDE_EVT32(atomic_read(&rsc->rsc_vsync_wait), rc);
|
|
rc = 0;
|
|
}
|
|
}
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int sde_rsc_switch_to_vid(struct sde_rsc_priv *rsc,
|
|
struct sde_rsc_cmd_config *config,
|
|
struct sde_rsc_client *caller_client,
|
|
int *wait_vblank_crtc_id)
|
|
{
|
|
struct sde_rsc_client *client;
|
|
int rc = STATE_UPDATE_NOT_ALLOWED;
|
|
|
|
if (!rsc->primary_client) {
|
|
pr_err("primary client not available for vid state switch\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
} else if (caller_client != rsc->primary_client) {
|
|
pr_err("primary client state:%d not vid state request\n",
|
|
rsc->primary_client->current_state);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
/* update timers - might not be available at next switch */
|
|
if (config)
|
|
sde_rsc_timer_calculate(rsc, config, SDE_RSC_VID_STATE);
|
|
|
|
/**
|
|
* rsc clients can still send config at any time. If a config is
|
|
* received during vid_state then vsync_wait will execute with the logic
|
|
* below.
|
|
*/
|
|
if (rsc->current_state == SDE_RSC_VID_STATE) {
|
|
rc = 0;
|
|
if (config && rsc->version < SDE_RSC_REV_3)
|
|
goto vsync_wait;
|
|
else
|
|
goto end;
|
|
}
|
|
|
|
/* any non-primary clk state client blocks the vid state switch */
|
|
list_for_each_entry(client, &rsc->client_list, list)
|
|
if (client->current_state == SDE_RSC_CLK_STATE &&
|
|
client->client_type == SDE_RSC_EXTERNAL_DISP_CLIENT)
|
|
goto end;
|
|
|
|
if (rsc->hw_ops.state_update) {
|
|
rc = rsc->hw_ops.state_update(rsc, SDE_RSC_VID_STATE);
|
|
if (!rc)
|
|
rpmh_mode_solver_set(rsc->rpmh_dev,
|
|
rsc->version >= SDE_RSC_REV_3);
|
|
}
|
|
|
|
/* vsync wait not needed during CMD->VID switch (rev 4+ HW only) */
|
|
if (rsc->current_state == SDE_RSC_CMD_STATE &&
|
|
rsc->version >= SDE_RSC_REV_4) {
|
|
rc = 0;
|
|
goto end;
|
|
}
|
|
|
|
vsync_wait:
|
|
/* indicate wait for vsync for vid to cmd state switch & cfg update */
|
|
if (!rc && (rsc->current_state == SDE_RSC_VID_STATE ||
|
|
rsc->current_state == SDE_RSC_CMD_STATE)) {
|
|
rsc->post_poms = true;
|
|
|
|
/* clear VSYNC timestamp for indication when update completes */
|
|
if (rsc->hw_ops.hw_vsync)
|
|
rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL, 0, 0);
|
|
if (!wait_vblank_crtc_id) {
|
|
pr_err("invalid crtc id wait pointer, client %d\n",
|
|
caller_client->id);
|
|
SDE_EVT32(caller_client->id, rsc->current_state,
|
|
caller_client->crtc_id,
|
|
wait_vblank_crtc_id, SDE_EVTLOG_ERROR);
|
|
msleep(RSC_VSYNC_TIMEOUT_MS(rsc));
|
|
} else {
|
|
*wait_vblank_crtc_id = rsc->primary_client->crtc_id;
|
|
}
|
|
}
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int sde_rsc_switch_to_idle(struct sde_rsc_priv *rsc,
|
|
struct sde_rsc_cmd_config *config,
|
|
struct sde_rsc_client *caller_client,
|
|
int *wait_vblank_crtc_id)
|
|
{
|
|
struct sde_rsc_client *client;
|
|
int rc = STATE_UPDATE_NOT_ALLOWED;
|
|
bool clk_client_active = false, multi_display_active = false;
|
|
bool vid_display_active = false, cmd_display_active = false;
|
|
|
|
/*
|
|
* following code needs to run the loop through each
|
|
* client because they might be in different order
|
|
* sorting is not possible; only preference is available
|
|
*/
|
|
list_for_each_entry(client, &rsc->client_list, list) {
|
|
if (client->current_state == SDE_RSC_CLK_STATE &&
|
|
client->client_type == SDE_RSC_EXTERNAL_DISP_CLIENT)
|
|
multi_display_active = true;
|
|
else if (client->current_state == SDE_RSC_CLK_STATE &&
|
|
client->client_type == SDE_RSC_CLK_CLIENT)
|
|
clk_client_active = true;
|
|
else if (client->current_state == SDE_RSC_VID_STATE)
|
|
vid_display_active = true;
|
|
else if (client->current_state == SDE_RSC_CMD_STATE)
|
|
cmd_display_active = true;
|
|
pr_debug("client state:%d type:%d\n",
|
|
client->current_state, client->client_type);
|
|
}
|
|
|
|
pr_debug("multi_display:%d clk_client:%d vid_display:%d cmd_display:%d\n",
|
|
multi_display_active, clk_client_active, vid_display_active,
|
|
cmd_display_active);
|
|
if (vid_display_active && !multi_display_active) {
|
|
rc = sde_rsc_switch_to_vid(rsc, NULL, rsc->primary_client,
|
|
wait_vblank_crtc_id);
|
|
if (!rc)
|
|
rc = VID_MODE_SWITCH_SUCCESS;
|
|
} else if (cmd_display_active && !multi_display_active) {
|
|
rc = sde_rsc_switch_to_cmd(rsc, NULL, rsc->primary_client,
|
|
wait_vblank_crtc_id);
|
|
if (!rc)
|
|
rc = CMD_MODE_SWITCH_SUCCESS;
|
|
} else if (clk_client_active) {
|
|
rc = sde_rsc_switch_to_clk(rsc, wait_vblank_crtc_id);
|
|
if (!rc)
|
|
rc = CLK_MODE_SWITCH_SUCCESS;
|
|
} else if (rsc->hw_ops.state_update) {
|
|
rc = rsc->hw_ops.state_update(rsc, SDE_RSC_IDLE_STATE);
|
|
rsc->post_poms = false;
|
|
if (!rc)
|
|
rpmh_mode_solver_set(rsc->rpmh_dev, true);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_client_get_vsync_refcount() - returns the status of the vsync
|
|
* refcount, to signal if the client needs to reset the refcounting logic
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
*
|
|
* Return: value of the vsync refcount.
|
|
*/
|
|
int sde_rsc_client_get_vsync_refcount(
|
|
struct sde_rsc_client *caller_client)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (!caller_client) {
|
|
pr_err("invalid client for rsc state update\n");
|
|
return -EINVAL;
|
|
} else if (caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc = rsc_prv_list[caller_client->rsc_index];
|
|
if (!rsc)
|
|
return 0;
|
|
|
|
return atomic_read(&rsc->rsc_vsync_wait);
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_client_reset_vsync_refcount() - reduces the refcounting
|
|
* logic that waits for the vsync.
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
*
|
|
* Return: zero if refcount was already zero.
|
|
*/
|
|
int sde_rsc_client_reset_vsync_refcount(
|
|
struct sde_rsc_client *caller_client)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
int ret;
|
|
|
|
if (!caller_client) {
|
|
pr_err("invalid client for rsc state update\n");
|
|
return -EINVAL;
|
|
} else if (caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc = rsc_prv_list[caller_client->rsc_index];
|
|
if (!rsc)
|
|
return 0;
|
|
|
|
ret = atomic_add_unless(&rsc->rsc_vsync_wait, -1, 0);
|
|
wake_up_all(&rsc->rsc_vsync_waitq);
|
|
SDE_EVT32(atomic_read(&rsc->rsc_vsync_wait));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_client_is_state_update_complete() - check if state update is complete
|
|
* RSC state transition is not complete until HW receives VBLANK signal. This
|
|
* function checks RSC HW to determine whether that signal has been received.
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
*
|
|
* Return: true if the state update has completed.
|
|
*/
|
|
bool sde_rsc_client_is_state_update_complete(
|
|
struct sde_rsc_client *caller_client)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
u32 vsync_timestamp0 = 0;
|
|
|
|
if (!caller_client) {
|
|
pr_err("invalid client for rsc state update\n");
|
|
return false;
|
|
} else if (caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return false;
|
|
}
|
|
|
|
rsc = rsc_prv_list[caller_client->rsc_index];
|
|
if (!rsc)
|
|
return false;
|
|
|
|
/**
|
|
* state updates clear VSYNC timestamp, check if a new one arrived.
|
|
* use VSYNC mode 0 (CMD TE) always for this, per HW recommendation.
|
|
*/
|
|
if (rsc->hw_ops.hw_vsync)
|
|
vsync_timestamp0 = rsc->hw_ops.hw_vsync(rsc, VSYNC_READ_VSYNC0,
|
|
NULL, 0, 0);
|
|
|
|
return vsync_timestamp0 != 0;
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_client_state_update() - rsc client state update
|
|
* Video mode, cmd mode and clk state are suppoed as modes. A client need to
|
|
* set this property during panel config time. A switching client can set the
|
|
* property to change the state
|
|
*
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
* @state: Client state - video/cmd
|
|
* @config: fps, vtotal, porches, etc configuration for command mode
|
|
* panel
|
|
* @crtc_id: current client's crtc id
|
|
* @wait_vblank_crtc_id: Output parameter. If set to non-zero, rsc hw
|
|
* state update requires a wait for one vblank on
|
|
* the primary crtc. In that case, this output
|
|
* param will be set to the crtc on which to wait.
|
|
* If SDE_RSC_INVALID_CRTC_ID, no wait necessary
|
|
*
|
|
* Return: error code.
|
|
*/
|
|
int sde_rsc_client_state_update(struct sde_rsc_client *caller_client,
|
|
enum sde_rsc_state state,
|
|
struct sde_rsc_cmd_config *config, int crtc_id,
|
|
int *wait_vblank_crtc_id)
|
|
{
|
|
int rc = 0;
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (!caller_client) {
|
|
pr_err("invalid client for rsc state update\n");
|
|
return -EINVAL;
|
|
} else if (caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc = rsc_prv_list[caller_client->rsc_index];
|
|
if (!rsc)
|
|
return -EINVAL;
|
|
|
|
if (wait_vblank_crtc_id)
|
|
*wait_vblank_crtc_id = SDE_RSC_INVALID_CRTC_ID;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
SDE_EVT32_VERBOSE(caller_client->id, caller_client->current_state,
|
|
state, rsc->current_state, SDE_EVTLOG_FUNC_ENTRY);
|
|
|
|
pr_debug("%pS: rsc state:%d request client:%s state:%d\n",
|
|
__builtin_return_address(0), rsc->current_state,
|
|
caller_client->name, state);
|
|
|
|
if ((state == SDE_RSC_VID_STATE) && (rsc->version >= SDE_RSC_REV_3))
|
|
state = SDE_RSC_CLK_STATE;
|
|
|
|
/**
|
|
* This can only happen if splash is active or qsync is enabled.
|
|
* In both cases timers need to be updated for when a transition to
|
|
* solver occurs. Update timers now as config might not be available
|
|
* at next switch. Updates for cmd/vid are handled when switching to
|
|
* those states.
|
|
*/
|
|
if (config && (state == SDE_RSC_CLK_STATE) &&
|
|
(caller_client == rsc->primary_client))
|
|
sde_rsc_timer_calculate(rsc, config, state);
|
|
|
|
|
|
caller_client->crtc_id = crtc_id;
|
|
caller_client->current_state = state;
|
|
|
|
if ((rsc->current_state == state) && !config) {
|
|
SDE_EVT32(caller_client->id, caller_client->current_state,
|
|
state, rsc->current_state, SDE_EVTLOG_FUNC_CASE3);
|
|
goto end;
|
|
}
|
|
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE)
|
|
sde_rsc_resource_enable(rsc);
|
|
|
|
switch (state) {
|
|
case SDE_RSC_IDLE_STATE:
|
|
rc = sde_rsc_switch_to_idle(rsc, NULL, rsc->primary_client,
|
|
wait_vblank_crtc_id);
|
|
|
|
if (rc == CMD_MODE_SWITCH_SUCCESS) {
|
|
state = SDE_RSC_CMD_STATE;
|
|
rc = 0;
|
|
} else if (rc == VID_MODE_SWITCH_SUCCESS) {
|
|
state = SDE_RSC_VID_STATE;
|
|
rc = 0;
|
|
} else if (rc == CLK_MODE_SWITCH_SUCCESS) {
|
|
state = SDE_RSC_CLK_STATE;
|
|
rc = 0;
|
|
}
|
|
break;
|
|
|
|
case SDE_RSC_CMD_STATE:
|
|
rc = sde_rsc_switch_to_cmd(rsc, config, caller_client,
|
|
wait_vblank_crtc_id);
|
|
break;
|
|
|
|
case SDE_RSC_VID_STATE:
|
|
rc = sde_rsc_switch_to_vid(rsc, config, caller_client,
|
|
wait_vblank_crtc_id);
|
|
break;
|
|
|
|
case SDE_RSC_CLK_STATE:
|
|
rc = sde_rsc_switch_to_clk(rsc, wait_vblank_crtc_id);
|
|
break;
|
|
|
|
default:
|
|
pr_err("invalid state handling %d\n", state);
|
|
break;
|
|
}
|
|
|
|
if (rc == STATE_UPDATE_NOT_ALLOWED) {
|
|
rc = 0;
|
|
SDE_EVT32(caller_client->id, caller_client->current_state,
|
|
state, rsc->current_state, rc, SDE_EVTLOG_FUNC_CASE1);
|
|
goto clk_disable;
|
|
} else if (rc) {
|
|
pr_debug("state:%d update failed rc:%d\n", state, rc);
|
|
SDE_EVT32(caller_client->id, caller_client->current_state,
|
|
state, rsc->current_state, rc, SDE_EVTLOG_FUNC_CASE2);
|
|
goto clk_disable;
|
|
}
|
|
|
|
pr_debug("state switch successfully complete: %d\n", state);
|
|
SDE_ATRACE_INT("rsc_state", state);
|
|
SDE_EVT32(caller_client->id, caller_client->current_state,
|
|
state, rsc->current_state, SDE_EVTLOG_FUNC_EXIT);
|
|
rsc->current_state = state;
|
|
rsc->update_tcs_content = true;
|
|
|
|
clk_disable:
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE)
|
|
sde_rsc_resource_disable(rsc);
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_client_state_update);
|
|
|
|
/**
|
|
* sde_rsc_client_vote() - ab/ib vote from rsc client
|
|
*
|
|
* @client: Client pointer provided by sde_rsc_client_create().
|
|
* @bus_id: data bus for which to be voted
|
|
* @ab: aggregated bandwidth vote from client.
|
|
* @ib: instant bandwidth vote from client.
|
|
*
|
|
* Return: error code.
|
|
*/
|
|
int sde_rsc_client_vote(struct sde_rsc_client *caller_client,
|
|
u32 bus_id, u64 ab_vote, u64 ib_vote)
|
|
{
|
|
int rsc_index;
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (caller_client && caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc client or client index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc_index = caller_client ? caller_client->rsc_index : SDE_RSC_INDEX;
|
|
rsc = rsc_prv_list[rsc_index];
|
|
if (!rsc || bus_id >= SDE_POWER_HANDLE_DBUS_ID_MAX)
|
|
return -EINVAL;
|
|
|
|
pr_debug("client:%s ab:%llu ib:%llu\n",
|
|
caller_client ? caller_client->name : "unknown",
|
|
ab_vote, ib_vote);
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
rsc->bw_config.new_ab_vote[bus_id] = ab_vote;
|
|
rsc->bw_config.new_ib_vote[bus_id] = ib_vote;
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_client_vote);
|
|
|
|
int sde_rsc_client_trigger_vote(struct sde_rsc_client *caller_client,
|
|
bool delta_vote)
|
|
{
|
|
int rc = 0, rsc_index, i;
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
if (caller_client && caller_client->rsc_index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid rsc index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc_index = caller_client ? caller_client->rsc_index : SDE_RSC_INDEX;
|
|
rsc = rsc_prv_list[rsc_index];
|
|
if (!rsc)
|
|
return -EINVAL;
|
|
|
|
if (rsc->bwi_update == BW_NO_CHANGE && !delta_vote && rsc->version >= SDE_RSC_REV_5)
|
|
return 0;
|
|
|
|
pr_debug("client:%s trigger bw delta vote:%d\n",
|
|
caller_client ? caller_client->name : "unknown", delta_vote);
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
|
|
if (!delta_vote && !rsc->update_tcs_content &&
|
|
(rsc->current_state == SDE_RSC_CLK_STATE))
|
|
goto end;
|
|
|
|
rsc->bwi_update = BW_HIGH_TO_LOW;
|
|
for (i = 0; i < SDE_POWER_HANDLE_DBUS_ID_MAX && delta_vote; i++) {
|
|
if (rsc->bw_config.new_ab_vote[i] > rsc->bw_config.ab_vote[i] ||
|
|
rsc->bw_config.new_ib_vote[i] > rsc->bw_config.ib_vote[i])
|
|
rsc->bwi_update = BW_LOW_TO_HIGH;
|
|
|
|
rsc->bw_config.ab_vote[i] = rsc->bw_config.new_ab_vote[i];
|
|
rsc->bw_config.ib_vote[i] = rsc->bw_config.new_ib_vote[i];
|
|
}
|
|
|
|
rc = sde_rsc_resource_enable(rsc);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
if (delta_vote) {
|
|
if (rsc->hw_ops.tcs_wait) {
|
|
rc = rsc->hw_ops.tcs_wait(rsc);
|
|
if (rc) {
|
|
pr_err("tcs is still busy; can't send command\n");
|
|
if (rsc->hw_ops.tcs_use_ok)
|
|
rsc->hw_ops.tcs_use_ok(rsc);
|
|
goto tcs_wait_failed;
|
|
}
|
|
}
|
|
|
|
rpmh_invalidate(rsc->rpmh_dev);
|
|
for (i = 0; i < SDE_POWER_HANDLE_DBUS_ID_MAX; i++)
|
|
sde_power_data_bus_set_quota(&rsc->phandle,
|
|
i, rsc->bw_config.ab_vote[i],
|
|
rsc->bw_config.ib_vote[i]);
|
|
rpmh_write_sleep_and_wake(rsc->rpmh_dev);
|
|
}
|
|
|
|
if (rsc->version >= SDE_RSC_REV_5 && !delta_vote)
|
|
rsc->bwi_update = BW_NO_CHANGE;
|
|
|
|
if (rsc->hw_ops.bwi_status &&
|
|
(rsc->current_state == SDE_RSC_CMD_STATE ||
|
|
rsc->current_state == SDE_RSC_VID_STATE))
|
|
rsc->hw_ops.bwi_status(rsc);
|
|
else if (rsc->hw_ops.tcs_use_ok)
|
|
rsc->hw_ops.tcs_use_ok(rsc);
|
|
|
|
rsc->update_tcs_content = false;
|
|
|
|
tcs_wait_failed:
|
|
sde_rsc_resource_disable(rsc);
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(sde_rsc_client_trigger_vote);
|
|
|
|
#if defined(CONFIG_DEBUG_FS)
|
|
void sde_rsc_debug_dump(u32 mux_sel)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
|
|
rsc = rsc_prv_list[SDE_RSC_INDEX];
|
|
if (!rsc)
|
|
return;
|
|
|
|
/* this must be called with rsc clocks enabled */
|
|
if (rsc->hw_ops.debug_dump)
|
|
rsc->hw_ops.debug_dump(rsc, mux_sel);
|
|
}
|
|
|
|
static int _sde_debugfs_status_show(struct seq_file *s, void *data)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
struct sde_rsc_client *client;
|
|
int ret;
|
|
|
|
if (!s || !s->private)
|
|
return -EINVAL;
|
|
|
|
rsc = s->private;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
|
|
seq_printf(s, "rsc current state:%d\n", rsc->current_state);
|
|
seq_printf(s, "wraper backoff time(ns):%d\n",
|
|
rsc->timer_config.static_wakeup_time_ns);
|
|
seq_printf(s, "rsc backoff time(ns):%d\n",
|
|
rsc->timer_config.rsc_backoff_time_ns);
|
|
seq_printf(s, "pdc backoff time(ns):%d\n",
|
|
rsc->timer_config.pdc_backoff_time_ns);
|
|
seq_printf(s, "rsc mode threshold time(ns):%d\n",
|
|
rsc->timer_config.rsc_mode_threshold_time_ns);
|
|
seq_printf(s, "rsc time slot 0(ns):%d\n",
|
|
rsc->timer_config.rsc_time_slot_0_ns);
|
|
seq_printf(s, "rsc time slot 1(ns):%d\n",
|
|
rsc->timer_config.rsc_time_slot_1_ns);
|
|
seq_printf(s, "frame fps:%d jitter_numer:%d jitter_denom:%d vtotal:%d prefill lines:%d\n",
|
|
rsc->cmd_config.fps, rsc->cmd_config.jitter_numer,
|
|
rsc->cmd_config.jitter_denom,
|
|
rsc->cmd_config.vtotal, rsc->cmd_config.prefill_lines);
|
|
|
|
seq_puts(s, "\n");
|
|
|
|
list_for_each_entry(client, &rsc->client_list, list)
|
|
seq_printf(s, "\t client:%s state:%d\n",
|
|
client->name, client->current_state);
|
|
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
seq_puts(s, "hw state is not supported during idle pc\n");
|
|
goto end;
|
|
}
|
|
|
|
if (rsc->hw_ops.debug_show) {
|
|
ret = rsc->hw_ops.debug_show(s, rsc);
|
|
if (ret)
|
|
pr_err("sde rsc: hw debug failed ret:%d\n", ret);
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int _sde_debugfs_status_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, _sde_debugfs_status_show, inode->i_private);
|
|
}
|
|
|
|
static int _sde_debugfs_counters_show(struct seq_file *s, void *data)
|
|
{
|
|
struct sde_rsc_priv *rsc = s->private;
|
|
u32 counters[NUM_RSC_PROFILING_COUNTERS];
|
|
int i, ret;
|
|
|
|
if (!rsc)
|
|
return -EINVAL;
|
|
|
|
if (!rsc->hw_ops.get_counters) {
|
|
seq_puts(s, "counters are not supported on this target\n");
|
|
return 0;
|
|
}
|
|
|
|
memset(counters, 0, sizeof(counters));
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("counters are not supported during idle state\n");
|
|
seq_puts(s, "no access to counters during idle pc\n");
|
|
goto end;
|
|
}
|
|
|
|
ret = rsc->hw_ops.get_counters(rsc, counters);
|
|
if (ret) {
|
|
pr_err("sde rsc: get_counters failed ret:%d\n", ret);
|
|
seq_puts(s, "failed to retrieve counts!\n");
|
|
goto end;
|
|
}
|
|
|
|
seq_puts(s, "rsc profiling counters:\n");
|
|
for (i = 0; i < NUM_RSC_PROFILING_COUNTERS; ++i)
|
|
seq_printf(s, "\tmode[%d] = 0x%08x:\n", i, counters[i]);
|
|
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int _sde_debugfs_counters_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, _sde_debugfs_counters_show, inode->i_private);
|
|
}
|
|
|
|
static int _sde_debugfs_generic_noseek_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
/* non-seekable */
|
|
file->private_data = inode->i_private;
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_profiling_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
size_t max_size = min_t(size_t, count, MAX_BUFFER_SIZE);
|
|
char buffer[MAX_BUFFER_SIZE];
|
|
int blen = 0;
|
|
|
|
if (*ppos || !rsc)
|
|
return 0;
|
|
|
|
if (!rsc->hw_ops.setup_counters) {
|
|
blen += scnprintf(&buffer[blen], max_size - blen,
|
|
"counters are not supported on this target\n");
|
|
goto end;
|
|
}
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("counters are not supported during idle state\n");
|
|
blen += scnprintf(&buffer[blen], max_size - blen,
|
|
"no access to counters during idle pc\n");
|
|
goto unlock;
|
|
}
|
|
|
|
blen += scnprintf(&buffer[blen], max_size - blen,
|
|
"%s\n", rsc->profiling_en ? "Y" : "N");
|
|
|
|
unlock:
|
|
mutex_unlock(&rsc->client_lock);
|
|
end:
|
|
if (copy_to_user(buf, buffer, blen))
|
|
return -EFAULT;
|
|
|
|
*ppos += blen;
|
|
return blen;
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_profiling_write(struct file *file,
|
|
const char __user *p, size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
bool input_valid, input_value;
|
|
char *input;
|
|
int rc;
|
|
|
|
if (!rsc || !rsc->hw_ops.setup_counters || !count ||
|
|
count > MAX_COUNT_SIZE_SUPPORTED)
|
|
return 0;
|
|
|
|
input = kmalloc(count + 1, GFP_KERNEL);
|
|
if (!input)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(input, p, count)) {
|
|
kfree(input);
|
|
return -EFAULT;
|
|
}
|
|
input[count] = '\0';
|
|
|
|
switch (input[0]) {
|
|
case 'y':
|
|
case 'Y':
|
|
case '1':
|
|
input_valid = true;
|
|
input_value = true;
|
|
break;
|
|
case 'n':
|
|
case 'N':
|
|
case '0':
|
|
input_valid = true;
|
|
input_value = false;
|
|
break;
|
|
default:
|
|
input_valid = false;
|
|
count = 0;
|
|
break;
|
|
}
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
count = 0;
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("input %s, profiling_en: %d\n",
|
|
input_valid ? "valid" : "invalid", rsc->profiling_en);
|
|
|
|
if (input_valid) {
|
|
rsc->profiling_en = input_value;
|
|
rc = rsc->hw_ops.setup_counters(rsc, rsc->profiling_en);
|
|
if (rc)
|
|
pr_err("setup_counters failed, rc:%d\n", rc);
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
kfree(input);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_mode_ctrl_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
char buffer[MAX_BUFFER_SIZE];
|
|
int blen = 0;
|
|
size_t max_size = min_t(size_t, count, MAX_BUFFER_SIZE);
|
|
|
|
if (*ppos || !rsc || !rsc->hw_ops.mode_ctrl)
|
|
return 0;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
blen = scnprintf(buffer, max_size,
|
|
"hw state is not supported during idle pc\n");
|
|
goto end;
|
|
}
|
|
|
|
blen = rsc->hw_ops.mode_ctrl(rsc, MODE_READ, buffer, max_size, 0);
|
|
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
if (blen <= 0)
|
|
return 0;
|
|
|
|
if (blen > count) {
|
|
blen = count;
|
|
buffer[count - 1] = '\0';
|
|
}
|
|
|
|
if (copy_to_user(buf, buffer, blen))
|
|
return -EFAULT;
|
|
|
|
*ppos += blen;
|
|
return blen;
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_mode_ctrl_write(struct file *file,
|
|
const char __user *p, size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
char *input;
|
|
u32 mode_state = 0;
|
|
int rc;
|
|
|
|
if (!rsc || !rsc->hw_ops.mode_ctrl || !count ||
|
|
count > MAX_COUNT_SIZE_SUPPORTED)
|
|
return 0;
|
|
|
|
input = kmalloc(count + 1, GFP_KERNEL);
|
|
if (!input)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(input, p, count)) {
|
|
kfree(input);
|
|
return -EFAULT;
|
|
}
|
|
input[count] = '\0';
|
|
|
|
rc = kstrtoint(input, 0, &mode_state);
|
|
if (rc) {
|
|
pr_err("mode_state: int conversion failed rc:%d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("mode_state: %d\n", mode_state);
|
|
mode_state &= 0x7;
|
|
if (mode_state != ALL_MODES_DISABLED &&
|
|
mode_state != ALL_MODES_ENABLED &&
|
|
mode_state != ONLY_MODE_0_ENABLED &&
|
|
mode_state != ONLY_MODE_0_1_ENABLED) {
|
|
pr_err("invalid mode:%d combination\n", mode_state);
|
|
goto end;
|
|
}
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
goto state_check;
|
|
}
|
|
|
|
rsc->hw_ops.mode_ctrl(rsc, MODE_UPDATE, NULL, 0, mode_state);
|
|
|
|
state_check:
|
|
mutex_unlock(&rsc->client_lock);
|
|
end:
|
|
kfree(input);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_vsync_mode_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
char buffer[MAX_BUFFER_SIZE];
|
|
int blen = 0;
|
|
size_t max_size = min_t(size_t, count, MAX_BUFFER_SIZE);
|
|
|
|
if (*ppos || !rsc || !rsc->hw_ops.hw_vsync)
|
|
return 0;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
blen = scnprintf(buffer, max_size,
|
|
"hw state is not supported during idle pc\n");
|
|
goto end;
|
|
}
|
|
|
|
blen = rsc->hw_ops.hw_vsync(rsc, VSYNC_READ, buffer, max_size, 0);
|
|
|
|
end:
|
|
mutex_unlock(&rsc->client_lock);
|
|
if (blen <= 0)
|
|
return 0;
|
|
|
|
if (blen > count) {
|
|
blen = count;
|
|
buffer[count - 1] = '\0';
|
|
}
|
|
|
|
if (copy_to_user(buf, buffer, blen))
|
|
return -EFAULT;
|
|
|
|
*ppos += blen;
|
|
return blen;
|
|
}
|
|
|
|
static ssize_t _sde_debugfs_vsync_mode_write(struct file *file,
|
|
const char __user *p, size_t count, loff_t *ppos)
|
|
{
|
|
struct sde_rsc_priv *rsc = file->private_data;
|
|
char *input;
|
|
u32 vsync_state = 0;
|
|
int rc;
|
|
|
|
if (!rsc || !rsc->hw_ops.hw_vsync || !count ||
|
|
count > MAX_COUNT_SIZE_SUPPORTED)
|
|
return 0;
|
|
|
|
input = kmalloc(count + 1, GFP_KERNEL);
|
|
if (!input)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(input, p, count)) {
|
|
kfree(input);
|
|
return -EFAULT;
|
|
}
|
|
input[count] = '\0';
|
|
|
|
rc = kstrtoint(input, 0, &vsync_state);
|
|
if (rc) {
|
|
pr_err("vsync_state: int conversion failed rc:%d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("vsync_state: %d\n", vsync_state);
|
|
vsync_state &= 0x7;
|
|
|
|
mutex_lock(&rsc->client_lock);
|
|
if (rsc->current_state == SDE_RSC_IDLE_STATE) {
|
|
pr_debug("debug node is not supported during idle state\n");
|
|
goto state_check;
|
|
}
|
|
|
|
if (vsync_state)
|
|
rsc->hw_ops.hw_vsync(rsc, VSYNC_ENABLE, NULL,
|
|
0, vsync_state - 1);
|
|
else
|
|
rsc->hw_ops.hw_vsync(rsc, VSYNC_DISABLE, NULL, 0, 0);
|
|
|
|
state_check:
|
|
mutex_unlock(&rsc->client_lock);
|
|
end:
|
|
kfree(input);
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations debugfs_status_fops = {
|
|
.open = _sde_debugfs_status_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static const struct file_operations mode_control_fops = {
|
|
.open = _sde_debugfs_generic_noseek_open,
|
|
.read = _sde_debugfs_mode_ctrl_read,
|
|
.write = _sde_debugfs_mode_ctrl_write,
|
|
};
|
|
|
|
static const struct file_operations vsync_status_fops = {
|
|
.open = _sde_debugfs_generic_noseek_open,
|
|
.read = _sde_debugfs_vsync_mode_read,
|
|
.write = _sde_debugfs_vsync_mode_write,
|
|
};
|
|
|
|
static const struct file_operations profiling_enable_fops = {
|
|
.open = _sde_debugfs_generic_noseek_open,
|
|
.read = _sde_debugfs_profiling_read,
|
|
.write = _sde_debugfs_profiling_write,
|
|
};
|
|
|
|
static const struct file_operations profiling_counts_fops = {
|
|
.open = _sde_debugfs_counters_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void _sde_rsc_init_debugfs(struct sde_rsc_priv *rsc, char *name)
|
|
{
|
|
rsc->debugfs_root = debugfs_create_dir(name, NULL);
|
|
if (!rsc->debugfs_root)
|
|
return;
|
|
|
|
/* don't error check these */
|
|
debugfs_create_file("status", 0400, rsc->debugfs_root, rsc,
|
|
&debugfs_status_fops);
|
|
debugfs_create_file("mode_control", 0600, rsc->debugfs_root, rsc,
|
|
&mode_control_fops);
|
|
debugfs_create_file("vsync_mode", 0600, rsc->debugfs_root, rsc,
|
|
&vsync_status_fops);
|
|
if (rsc->profiling_supp) {
|
|
debugfs_create_file("profiling_en", 0600, rsc->debugfs_root,
|
|
rsc, &profiling_enable_fops);
|
|
debugfs_create_file("profiling_counts", 0400,
|
|
rsc->debugfs_root, rsc,
|
|
&profiling_counts_fops);
|
|
}
|
|
|
|
debugfs_create_x32("debug_mode", 0600, rsc->debugfs_root,
|
|
&rsc->debug_mode);
|
|
}
|
|
#else
|
|
static void _sde_rsc_init_debugfs(struct sde_rsc_priv *rsc, char *name)
|
|
{
|
|
}
|
|
#endif /* defined(CONFIG_DEBUG_FS) */
|
|
|
|
static void sde_rsc_deinit(struct platform_device *pdev,
|
|
struct sde_rsc_priv *rsc)
|
|
{
|
|
if (!rsc)
|
|
return;
|
|
|
|
sde_rsc_resource_disable(rsc);
|
|
if (rsc->sw_fs_enabled)
|
|
regulator_disable(rsc->fs);
|
|
if (rsc->fs)
|
|
devm_regulator_put(rsc->fs);
|
|
if (rsc->wrapper_io.base)
|
|
msm_dss_iounmap(&rsc->wrapper_io);
|
|
if (rsc->drv_io.base)
|
|
msm_dss_iounmap(&rsc->drv_io);
|
|
|
|
sde_power_resource_deinit(pdev, &rsc->phandle);
|
|
debugfs_remove_recursive(rsc->debugfs_root);
|
|
kfree(rsc);
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_get_io_resources - collect register ranges for the device to
|
|
* perform access control on TUI transition
|
|
* @io_res: io resource list
|
|
* @data: payload data provided during msm_register_vm_event
|
|
* Returns: zero on success
|
|
*/
|
|
static int sde_rsc_get_io_resources(struct msm_io_res *io_res, void *data)
|
|
{
|
|
int rc = 0;
|
|
struct sde_rsc_priv *rsc = (struct sde_rsc_priv *)data;
|
|
struct platform_device *pdev = to_platform_device(rsc->dev);
|
|
|
|
rc = msm_dss_get_io_mem(pdev, &io_res->mem);
|
|
if (rc) {
|
|
pr_err("failed to get rsc io mem, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_bind - bind rsc device with controlling device
|
|
* @dev: Pointer to base of platform device
|
|
* @master: Pointer to container of drm device
|
|
* @data: Pointer to private data
|
|
* Returns: Zero on success
|
|
*/
|
|
static int sde_rsc_bind(struct device *dev,
|
|
struct device *master,
|
|
void *data)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
struct drm_device *drm;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct msm_vm_ops vm_event_ops = {
|
|
.vm_get_io_resources = sde_rsc_get_io_resources,
|
|
};
|
|
|
|
if (!dev || !pdev || !master) {
|
|
pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n",
|
|
dev, pdev, master);
|
|
return -EINVAL;
|
|
}
|
|
|
|
drm = dev_get_drvdata(master);
|
|
rsc = platform_get_drvdata(pdev);
|
|
if (!drm || !rsc) {
|
|
pr_err("invalid param(s), drm %pK, rsc %pK\n",
|
|
drm, rsc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_dbg_reg_register_base(SDE_RSC_DRV_DBG_NAME, rsc->drv_io.base,
|
|
rsc->drv_io.len, msm_get_phys_addr(pdev, "drv"), SDE_DBG_RSC);
|
|
sde_dbg_reg_register_base(SDE_RSC_WRAPPER_DBG_NAME, rsc->wrapper_io.base,
|
|
rsc->wrapper_io.len, msm_get_phys_addr(pdev, "wrapper"), SDE_DBG_RSC);
|
|
|
|
msm_register_vm_event(master, dev, &vm_event_ops, (void *)rsc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sde_rsc_unbind - unbind rsc from controlling device
|
|
* @dev: Pointer to base of platform device
|
|
* @master: Pointer to container of drm device
|
|
* @data: Pointer to private data
|
|
*/
|
|
static void sde_rsc_unbind(struct device *dev,
|
|
struct device *master, void *data)
|
|
{
|
|
struct sde_rsc_priv *rsc;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
if (!dev || !pdev) {
|
|
pr_err("invalid param(s)\n");
|
|
return;
|
|
}
|
|
|
|
rsc = platform_get_drvdata(pdev);
|
|
if (!rsc) {
|
|
pr_err("invalid display rsc\n");
|
|
return;
|
|
}
|
|
|
|
msm_unregister_vm_event(master, dev);
|
|
}
|
|
|
|
static const struct component_ops sde_rsc_comp_ops = {
|
|
.bind = sde_rsc_bind,
|
|
.unbind = sde_rsc_unbind,
|
|
};
|
|
|
|
static int sde_rsc_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct sde_rsc_priv *rsc;
|
|
static int counter;
|
|
char name[MAX_RSC_CLIENT_NAME_LEN];
|
|
|
|
if (counter >= MAX_RSC_COUNT) {
|
|
pr_err("sde rsc supports probe till MAX_RSC_COUNT=%d devices\n",
|
|
MAX_RSC_COUNT);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsc = kzalloc(sizeof(*rsc), GFP_KERNEL);
|
|
if (!rsc) {
|
|
ret = -ENOMEM;
|
|
goto rsc_alloc_fail;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, rsc);
|
|
rsc->dev = &pdev->dev;
|
|
of_property_read_u32(pdev->dev.of_node, "qcom,sde-rsc-version",
|
|
&rsc->version);
|
|
|
|
switch (rsc->version) {
|
|
case SDE_RSC_REV_1:
|
|
rsc->single_tcs_execution_time = SINGLE_TCS_EXECUTION_TIME_V1;
|
|
break;
|
|
case SDE_RSC_REV_2:
|
|
rsc->single_tcs_execution_time = SINGLE_TCS_EXECUTION_TIME_V2;
|
|
break;
|
|
case SDE_RSC_REV_3:
|
|
rsc->single_tcs_execution_time = SINGLE_TCS_EXECUTION_TIME_V3;
|
|
break;
|
|
case SDE_RSC_REV_4:
|
|
rsc->single_tcs_execution_time = SINGLE_TCS_EXECUTION_TIME_V4;
|
|
break;
|
|
case SDE_RSC_REV_5:
|
|
default:
|
|
rsc->single_tcs_execution_time = SINGLE_TCS_EXECUTION_TIME_V5;
|
|
break;
|
|
}
|
|
|
|
if (rsc->version >= SDE_RSC_REV_3) {
|
|
rsc->time_slot_0_ns = rsc->single_tcs_execution_time
|
|
+ RSC_MODE_INSTRUCTION_TIME;
|
|
rsc->backoff_time_ns = RSC_MODE_INSTRUCTION_TIME;
|
|
rsc->mode_threshold_time_ns = rsc->time_slot_0_ns;
|
|
} else {
|
|
rsc->time_slot_0_ns = (rsc->single_tcs_execution_time * 2)
|
|
+ RSC_MODE_INSTRUCTION_TIME;
|
|
rsc->backoff_time_ns = rsc->single_tcs_execution_time
|
|
+ RSC_MODE_INSTRUCTION_TIME;
|
|
rsc->mode_threshold_time_ns = rsc->backoff_time_ns
|
|
+ RSC_MODE_THRESHOLD_OVERHEAD;
|
|
}
|
|
|
|
if (rsc->version >= SDE_RSC_REV_4)
|
|
rsc->profiling_supp = true;
|
|
|
|
ret = sde_power_resource_init(pdev, &rsc->phandle);
|
|
if (ret) {
|
|
pr_err("sde rsc:power resource init failed ret:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
rsc->rpmh_dev = rpmh_dev[SDE_RSC_INDEX + counter];
|
|
if (IS_ERR_OR_NULL(rsc->rpmh_dev)) {
|
|
ret = !rsc->rpmh_dev ? -EINVAL : PTR_ERR(rsc->rpmh_dev);
|
|
rsc->rpmh_dev = NULL;
|
|
pr_err("rpmh device node is not available ret:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
ret = msm_dss_ioremap_byname(pdev, &rsc->wrapper_io, "wrapper");
|
|
if (ret) {
|
|
pr_err("sde rsc: wrapper io data mapping failed ret=%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
ret = msm_dss_ioremap_byname(pdev, &rsc->drv_io, "drv");
|
|
if (ret) {
|
|
pr_err("sde rsc: drv io data mapping failed ret:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
rsc->fs = devm_regulator_get(&pdev->dev, "vdd");
|
|
if (IS_ERR_OR_NULL(rsc->fs)) {
|
|
rsc->fs = NULL;
|
|
pr_err("unable to get regulator\n");
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
if (rsc->version >= SDE_RSC_REV_3)
|
|
ret = sde_rsc_hw_register_v3(rsc);
|
|
else
|
|
ret = sde_rsc_hw_register(rsc);
|
|
if (ret) {
|
|
pr_err("sde rsc: hw register failed ret:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
ret = regulator_enable(rsc->fs);
|
|
if (ret) {
|
|
pr_err("sde rsc: fs on failed ret:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
rsc->sw_fs_enabled = true;
|
|
|
|
ret = sde_rsc_resource_enable(rsc);
|
|
if (ret < 0) {
|
|
pr_err("failed to enable sde rsc power resources rc:%d\n", ret);
|
|
goto sde_rsc_fail;
|
|
}
|
|
|
|
if (sde_rsc_timer_calculate(rsc, NULL, SDE_RSC_IDLE_STATE))
|
|
goto sde_rsc_fail;
|
|
|
|
sde_rsc_resource_disable(rsc);
|
|
|
|
INIT_LIST_HEAD(&rsc->client_list);
|
|
INIT_LIST_HEAD(&rsc->event_list);
|
|
mutex_init(&rsc->client_lock);
|
|
init_waitqueue_head(&rsc->rsc_vsync_waitq);
|
|
atomic_set(&rsc->resource_refcount, 0);
|
|
|
|
pr_info("sde rsc index:%d probed successfully\n",
|
|
SDE_RSC_INDEX + counter);
|
|
|
|
rsc_prv_list[SDE_RSC_INDEX + counter] = rsc;
|
|
snprintf(name, MAX_RSC_CLIENT_NAME_LEN, "%s%d", "sde_rsc", counter);
|
|
_sde_rsc_init_debugfs(rsc, name);
|
|
counter++;
|
|
|
|
ret = component_add(&pdev->dev, &sde_rsc_comp_ops);
|
|
if (ret)
|
|
pr_debug("component add failed, ret=%d\n", ret);
|
|
ret = 0;
|
|
|
|
return ret;
|
|
|
|
sde_rsc_fail:
|
|
sde_rsc_deinit(pdev, rsc);
|
|
rsc_alloc_fail:
|
|
return ret;
|
|
}
|
|
|
|
static int sde_rsc_remove(struct platform_device *pdev)
|
|
{
|
|
struct sde_rsc_priv *rsc = platform_get_drvdata(pdev);
|
|
|
|
sde_rsc_deinit(pdev, rsc);
|
|
return 0;
|
|
}
|
|
|
|
static int sde_rsc_rpmh_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
uint32_t index = 0;
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node, "cell-index", &index);
|
|
if (ret) {
|
|
pr_err("unable to find sde rsc cell index\n");
|
|
return ret;
|
|
} else if (index >= MAX_RSC_COUNT) {
|
|
pr_err("invalid cell index for sde rsc:%d\n", index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rpmh_dev[index] = &pdev->dev;
|
|
return 0;
|
|
}
|
|
|
|
int sde_rsc_rpmh_remove(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_RSC_COUNT; i++)
|
|
rpmh_dev[i] = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id dt_match[] = {
|
|
{ .compatible = "qcom,sde-rsc"},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver sde_rsc_platform_driver = {
|
|
.probe = sde_rsc_probe,
|
|
.remove = sde_rsc_remove,
|
|
.driver = {
|
|
.name = "sde_rsc",
|
|
.of_match_table = dt_match,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
};
|
|
|
|
static const struct of_device_id sde_rsc_rpmh_match[] = {
|
|
{.compatible = "qcom,sde-rsc-rpmh"},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver sde_rsc_rpmh_driver = {
|
|
.probe = sde_rsc_rpmh_probe,
|
|
.remove = sde_rsc_rpmh_remove,
|
|
.driver = {
|
|
.name = "sde_rsc_rpmh",
|
|
.of_match_table = sde_rsc_rpmh_match,
|
|
},
|
|
};
|
|
|
|
void __init sde_rsc_register(void)
|
|
{
|
|
platform_driver_register(&sde_rsc_platform_driver);
|
|
}
|
|
|
|
void __exit sde_rsc_unregister(void)
|
|
{
|
|
platform_driver_unregister(&sde_rsc_platform_driver);
|
|
}
|
|
|
|
void __init sde_rsc_rpmh_register(void)
|
|
{
|
|
platform_driver_register(&sde_rsc_rpmh_driver);
|
|
}
|