You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
kernel_samsung_sm7125/drivers/video/fbdev/msm/mdss_rotator.c

3026 lines
73 KiB

/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#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/msm-bus.h>
#include <linux/msm-bus-board.h>
#include <linux/regulator/consumer.h>
#include <linux/sched/clock.h>
#include "mdss_rotator_internal.h"
#include "mdss_mdp.h"
#include "mdss_debug.h"
#include "mdss_sync.h"
/* waiting for hw time out, 3 vsync for 30fps*/
#define ROT_HW_ACQUIRE_TIMEOUT_IN_MS 100
/* acquire fence time out, following other driver fence time out practice */
#define ROT_FENCE_WAIT_TIMEOUT MSEC_PER_SEC
/*
* Max rotator hw blocks possible. Used for upper array limits instead of
* alloc and freeing small array
*/
#define ROT_MAX_HW_BLOCKS 2
#define ROT_CHECK_BOUNDS(offset, size, max_size) \
(((size) > (max_size)) || ((offset) > ((max_size) - (size))))
#define CLASS_NAME "rotator"
#define DRIVER_NAME "mdss_rotator"
#define MDP_REG_BUS_VECTOR_ENTRY(ab_val, ib_val) \
{ \
.src = MSM_BUS_MASTER_AMPSS_M0, \
.dst = MSM_BUS_SLAVE_DISPLAY_CFG, \
.ab = (ab_val), \
.ib = (ib_val), \
}
#define BUS_VOTE_19_MHZ 153600000
static struct msm_bus_vectors rot_reg_bus_vectors[] = {
MDP_REG_BUS_VECTOR_ENTRY(0, 0),
MDP_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_19_MHZ),
};
static struct msm_bus_paths rot_reg_bus_usecases[ARRAY_SIZE(
rot_reg_bus_vectors)];
static struct msm_bus_scale_pdata rot_reg_bus_scale_table = {
.usecase = rot_reg_bus_usecases,
.num_usecases = ARRAY_SIZE(rot_reg_bus_usecases),
.name = "mdss_rot_reg",
.active_only = 1,
};
static struct mdss_rot_mgr *rot_mgr;
static void mdss_rotator_wq_handler(struct work_struct *work);
static int mdss_rotator_bus_scale_set_quota(struct mdss_rot_bus_data_type *bus,
u64 quota)
{
int new_uc_idx;
int ret;
if (bus->bus_hdl < 1) {
pr_err("invalid bus handle %d\n", bus->bus_hdl);
return -EINVAL;
}
if (bus->curr_quota_val == quota) {
pr_debug("bw request already requested\n");
return 0;
}
if (!quota) {
new_uc_idx = 0;
} else {
struct msm_bus_vectors *vect = NULL;
struct msm_bus_scale_pdata *bw_table =
bus->bus_scale_pdata;
u64 port_quota = quota;
u32 total_axi_port_cnt;
int i;
new_uc_idx = (bus->curr_bw_uc_idx %
(bw_table->num_usecases - 1)) + 1;
total_axi_port_cnt = bw_table->usecase[new_uc_idx].num_paths;
if (total_axi_port_cnt == 0) {
pr_err("Number of bw paths is 0\n");
return -ENODEV;
}
do_div(port_quota, total_axi_port_cnt);
for (i = 0; i < total_axi_port_cnt; i++) {
vect = &bw_table->usecase[new_uc_idx].vectors[i];
vect->ab = port_quota;
vect->ib = 0;
}
}
bus->curr_bw_uc_idx = new_uc_idx;
bus->curr_quota_val = quota;
pr_debug("uc_idx=%d quota=%llu\n", new_uc_idx, quota);
MDSS_XLOG(new_uc_idx, ((quota >> 32) & 0xFFFFFFFF),
(quota & 0xFFFFFFFF));
ATRACE_BEGIN("msm_bus_scale_req_rot");
ret = msm_bus_scale_client_update_request(bus->bus_hdl,
new_uc_idx);
ATRACE_END("msm_bus_scale_req_rot");
return ret;
}
static int mdss_rotator_enable_reg_bus(struct mdss_rot_mgr *mgr, u64 quota)
{
int ret = 0, changed = 0;
u32 usecase_ndx = 0;
if (!mgr || !mgr->reg_bus.bus_hdl)
return 0;
if (quota)
usecase_ndx = 1;
if (usecase_ndx != mgr->reg_bus.curr_bw_uc_idx) {
mgr->reg_bus.curr_bw_uc_idx = usecase_ndx;
changed++;
}
pr_debug("%s, changed=%d register bus %s\n", __func__, changed,
quota ? "Enable":"Disable");
if (changed) {
ATRACE_BEGIN("msm_bus_scale_req_rot_reg");
ret = msm_bus_scale_client_update_request(mgr->reg_bus.bus_hdl,
usecase_ndx);
ATRACE_END("msm_bus_scale_req_rot_reg");
}
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 mdss_rotator_clk_rate_calc(
struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private)
{
struct mdss_rot_perf *perf;
unsigned long clk_rate[ROT_MAX_HW_BLOCKS] = {0};
unsigned long total_clk_rate = 0;
int i, wb_idx;
mutex_lock(&private->perf_lock);
list_for_each_entry(perf, &private->perf_list, list) {
bool rate_accounted_for = false;
mutex_lock(&perf->work_dis_lock);
/*
* 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;
mutex_unlock(&perf->work_dis_lock);
}
mutex_unlock(&private->perf_lock);
for (i = 0; i < mgr->queue_count; i++)
total_clk_rate = max(clk_rate[i], total_clk_rate);
pr_debug("Total clk rate calc=%lu\n", total_clk_rate);
return total_clk_rate;
}
static struct clk *mdss_rotator_get_clk(struct mdss_rot_mgr *mgr, u32 clk_idx)
{
if (clk_idx >= MDSS_CLK_ROTATOR_END_IDX) {
pr_err("Invalid clk index:%u", clk_idx);
return NULL;
}
return mgr->rot_clk[clk_idx];
}
static void mdss_rotator_set_clk_rate(struct mdss_rot_mgr *mgr,
unsigned long rate, u32 clk_idx)
{
unsigned long clk_rate;
struct clk *clk = mdss_rotator_get_clk(mgr, clk_idx);
int ret;
if (clk) {
mutex_lock(&mgr->clk_lock);
clk_rate = clk_round_rate(clk, rate);
if (IS_ERR_VALUE(clk_rate)) {
pr_err("unable to round rate err=%ld\n", clk_rate);
} else if (clk_rate != clk_get_rate(clk)) {
ret = clk_set_rate(clk, clk_rate);
if (IS_ERR_VALUE((unsigned long)ret)) {
pr_err("clk_set_rate failed, err:%d\n", ret);
} else {
pr_debug("rotator clk rate=%lu\n", clk_rate);
MDSS_XLOG(clk_rate);
}
}
mutex_unlock(&mgr->clk_lock);
} else {
pr_err("rotator clk not setup properly\n");
}
}
static void mdss_rotator_footswitch_ctrl(struct mdss_rot_mgr *mgr, bool on)
{
int ret;
if (mgr->regulator_enable == on) {
pr_err("Regulators already in selected mode on=%d\n", on);
return;
}
pr_debug("%s: rotator regulators", on ? "Enable" : "Disable");
ret = msm_dss_enable_vreg(mgr->module_power.vreg_config,
mgr->module_power.num_vreg, on);
if (ret) {
pr_warn("Rotator regulator failed to %s\n",
on ? "enable" : "disable");
return;
}
mgr->regulator_enable = on;
}
static int mdss_rotator_clk_ctrl(struct mdss_rot_mgr *mgr, int enable)
{
struct clk *clk;
int ret = 0;
int i, changed = 0;
mutex_lock(&mgr->clk_lock);
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 {
pr_err("Can not be turned off\n");
}
}
if (changed) {
pr_debug("Rotator clk %s\n", enable ? "enable" : "disable");
for (i = 0; i < MDSS_CLK_ROTATOR_END_IDX; i++) {
clk = mgr->rot_clk[i];
if (enable) {
ret = clk_prepare_enable(clk);
if (ret) {
pr_err("enable failed clk_idx %d\n", i);
goto error;
}
} else {
clk_disable_unprepare(clk);
}
}
mutex_lock(&mgr->bus_lock);
if (enable) {
/* Active+Sleep */
msm_bus_scale_client_update_context(
mgr->data_bus.bus_hdl, false,
mgr->data_bus.curr_bw_uc_idx);
trace_rotator_bw_ao_as_context(0);
} else {
/* Active Only */
msm_bus_scale_client_update_context(
mgr->data_bus.bus_hdl, true,
mgr->data_bus.curr_bw_uc_idx);
trace_rotator_bw_ao_as_context(1);
}
mutex_unlock(&mgr->bus_lock);
}
mutex_unlock(&mgr->clk_lock);
return ret;
error:
for (i--; i >= 0; i--)
clk_disable_unprepare(mgr->rot_clk[i]);
mutex_unlock(&mgr->clk_lock);
return ret;
}
int mdss_rotator_resource_ctrl(struct mdss_rot_mgr *mgr, int enable)
{
int changed = 0;
int ret = 0;
mutex_lock(&mgr->clk_lock);
if (enable) {
if (mgr->res_ref_cnt == 0)
changed++;
mgr->res_ref_cnt++;
} else {
if (mgr->res_ref_cnt) {
mgr->res_ref_cnt--;
if (mgr->res_ref_cnt == 0)
changed++;
} else {
pr_err("Rot resource already off\n");
}
}
pr_debug("%s: res_cnt=%d changed=%d enable=%d\n",
__func__, mgr->res_ref_cnt, changed, enable);
MDSS_XLOG(mgr->res_ref_cnt, changed, enable);
if (changed) {
if (enable)
mdss_rotator_footswitch_ctrl(mgr, true);
else
mdss_rotator_footswitch_ctrl(mgr, false);
}
mutex_unlock(&mgr->clk_lock);
return ret;
}
/* caller is expected to hold perf->work_dis_lock lock */
static bool mdss_rotator_is_work_pending(struct mdss_rot_mgr *mgr,
struct mdss_rot_perf *perf)
{
int i;
for (i = 0; i < mgr->queue_count; i++) {
if (perf->work_distribution[i]) {
pr_debug("Work is still scheduled to complete\n");
return true;
}
}
return false;
}
static int mdss_rotator_create_fence(struct mdss_rot_entry *entry)
{
int ret = 0, fd;
u32 val;
struct mdss_fence *fence;
struct mdss_rot_timeline *rot_timeline;
if (!entry->queue)
return -EINVAL;
rot_timeline = &entry->queue->timeline;
mutex_lock(&rot_timeline->lock);
val = rot_timeline->next_value + 1;
fence = mdss_get_sync_fence(rot_timeline->timeline,
rot_timeline->fence_name, NULL, val);
if (fence == NULL) {
pr_err("cannot create sync point\n");
goto sync_pt_create_err;
}
fd = mdss_get_sync_fence_fd(fence);
if (fd < 0) {
pr_err("get_unused_fd_flags failed error:0x%x\n", fd);
ret = fd;
goto get_fd_err;
}
rot_timeline->next_value++;
mutex_unlock(&rot_timeline->lock);
entry->output_fence_fd = fd;
entry->output_fence = fence;
pr_debug("output sync point created at %s:val=%u\n",
mdss_get_sync_fence_name(fence), val);
return 0;
get_fd_err:
mdss_put_sync_fence(fence);
sync_pt_create_err:
mutex_unlock(&rot_timeline->lock);
return ret;
}
static void mdss_rotator_clear_fence(struct mdss_rot_entry *entry)
{
struct mdss_rot_timeline *rot_timeline;
if (entry->input_fence) {
mdss_put_sync_fence(entry->input_fence);
entry->input_fence = NULL;
}
rot_timeline = &entry->queue->timeline;
/* fence failed to copy to user space */
if (entry->output_fence) {
mdss_put_sync_fence(entry->output_fence);
entry->output_fence = NULL;
put_unused_fd(entry->output_fence_fd);
mutex_lock(&rot_timeline->lock);
rot_timeline->next_value--;
mutex_unlock(&rot_timeline->lock);
}
}
static int mdss_rotator_signal_output(struct mdss_rot_entry *entry)
{
struct mdss_rot_timeline *rot_timeline;
if (!entry->queue)
return -EINVAL;
rot_timeline = &entry->queue->timeline;
if (entry->output_signaled) {
pr_debug("output already signaled\n");
return 0;
}
mutex_lock(&rot_timeline->lock);
mdss_inc_timeline(rot_timeline->timeline, 1);
mutex_unlock(&rot_timeline->lock);
entry->output_signaled = true;
return 0;
}
static int mdss_rotator_wait_for_input(struct mdss_rot_entry *entry)
{
int ret;
if (!entry->input_fence) {
pr_debug("invalid input fence, no wait\n");
return 0;
}
ret = mdss_wait_sync_fence(entry->input_fence, ROT_FENCE_WAIT_TIMEOUT);
mdss_put_sync_fence(entry->input_fence);
entry->input_fence = NULL;
return ret;
}
static int mdss_rotator_import_buffer(struct mdp_layer_buffer *buffer,
struct mdss_mdp_data *data, u32 flags, struct device *dev, bool input)
{
int i, ret = 0;
struct msmfb_data planes[MAX_PLANES];
int dir = DMA_TO_DEVICE;
if (!input)
dir = DMA_FROM_DEVICE;
memset(planes, 0, sizeof(planes));
if (buffer->plane_count > MAX_PLANES) {
pr_err("buffer plane_count exceeds MAX_PLANES limit:%d\n",
buffer->plane_count);
return -EINVAL;
}
for (i = 0; i < buffer->plane_count; i++) {
planes[i].memory_id = buffer->planes[i].fd;
planes[i].offset = buffer->planes[i].offset;
}
ret = mdss_mdp_data_get_and_validate_size(data, planes,
buffer->plane_count, flags, dev, true, dir, buffer);
data->state = MDP_BUF_STATE_READY;
data->last_alloc = local_clock();
return ret;
}
static int mdss_rotator_map_and_check_data(struct mdss_rot_entry *entry)
{
int ret;
struct mdp_layer_buffer *input;
struct mdp_layer_buffer *output;
struct mdss_mdp_format_params *fmt;
struct mdss_mdp_plane_sizes ps;
bool rotation;
input = &entry->item.input;
output = &entry->item.output;
rotation = (entry->item.flags & MDP_ROTATION_90) ? true : false;
ATRACE_BEGIN(__func__);
ret = mdss_iommu_ctrl(1);
if (IS_ERR_VALUE((unsigned long)ret)) {
ATRACE_END(__func__);
return ret;
}
/* if error during map, the caller will release the data */
entry->src_buf.state = MDP_BUF_STATE_ACTIVE;
ret = mdss_mdp_data_map(&entry->src_buf, true, DMA_TO_DEVICE);
if (ret) {
pr_err("source buffer mapping failed ret:%d\n", ret);
goto end;
}
entry->dst_buf.state = MDP_BUF_STATE_ACTIVE;
ret = mdss_mdp_data_map(&entry->dst_buf, true, DMA_FROM_DEVICE);
if (ret) {
pr_err("destination buffer mapping failed ret:%d\n", ret);
goto end;
}
fmt = mdss_mdp_get_format_params(input->format);
if (!fmt) {
pr_err("invalid input format:%d\n", input->format);
ret = -EINVAL;
goto end;
}
ret = mdss_mdp_get_plane_sizes(
fmt, input->width, input->height, &ps, 0, rotation);
if (ret) {
pr_err("fail to get input plane size ret=%d\n", ret);
goto end;
}
ret = mdss_mdp_data_check(&entry->src_buf, &ps, fmt);
if (ret) {
pr_err("fail to check input data ret=%d\n", ret);
goto end;
}
fmt = mdss_mdp_get_format_params(output->format);
if (!fmt) {
pr_err("invalid output format:%d\n", output->format);
ret = -EINVAL;
goto end;
}
ret = mdss_mdp_get_plane_sizes(
fmt, output->width, output->height, &ps, 0, rotation);
if (ret) {
pr_err("fail to get output plane size ret=%d\n", ret);
goto end;
}
ret = mdss_mdp_data_check(&entry->dst_buf, &ps, fmt);
if (ret) {
pr_err("fail to check output data ret=%d\n", ret);
goto end;
}
end:
mdss_iommu_ctrl(0);
ATRACE_END(__func__);
return ret;
}
static struct mdss_rot_perf *__mdss_rotator_find_session(
struct mdss_rot_file_private *private,
u32 session_id)
{
struct mdss_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 mdss_rot_perf *mdss_rotator_find_session(
struct mdss_rot_file_private *private,
u32 session_id)
{
struct mdss_rot_perf *perf;
mutex_lock(&private->perf_lock);
perf = __mdss_rotator_find_session(private, session_id);
mutex_unlock(&private->perf_lock);
return perf;
}
static void mdss_rotator_release_data(struct mdss_rot_entry *entry)
{
struct mdss_mdp_data *src_buf = &entry->src_buf;
struct mdss_mdp_data *dst_buf = &entry->dst_buf;
mdss_mdp_data_free(src_buf, true, DMA_TO_DEVICE);
src_buf->last_freed = local_clock();
src_buf->state = MDP_BUF_STATE_UNUSED;
mdss_mdp_data_free(dst_buf, true, DMA_FROM_DEVICE);
dst_buf->last_freed = local_clock();
dst_buf->state = MDP_BUF_STATE_UNUSED;
}
static int mdss_rotator_import_data(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry)
{
int ret;
struct mdp_layer_buffer *input;
struct mdp_layer_buffer *output;
u32 flag = 0;
input = &entry->item.input;
output = &entry->item.output;
if (entry->item.flags & MDP_ROTATION_SECURE)
flag = MDP_SECURE_OVERLAY_SESSION;
ret = mdss_rotator_import_buffer(input, &entry->src_buf, flag,
&mgr->pdev->dev, true);
if (ret) {
pr_err("fail to import input buffer\n");
return ret;
}
/*
* driver assumes output buffer is ready to be written
* immediately
*/
ret = mdss_rotator_import_buffer(output, &entry->dst_buf, flag,
&mgr->pdev->dev, false);
if (ret) {
pr_err("fail to import output buffer\n");
return ret;
}
return ret;
}
static struct mdss_rot_hw_resource *mdss_rotator_hw_alloc(
struct mdss_rot_mgr *mgr, u32 pipe_id, u32 wb_id)
{
struct mdss_rot_hw_resource *hw;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
u32 pipe_ndx, offset = mdss_mdp_get_wb_ctl_support(mdata, true);
int ret = 0;
hw = devm_kzalloc(&mgr->pdev->dev, sizeof(struct mdss_rot_hw_resource),
GFP_KERNEL);
if (!hw)
return ERR_PTR(-ENOMEM);
hw->ctl = mdss_mdp_ctl_alloc(mdata, offset);
if (IS_ERR_OR_NULL(hw->ctl)) {
pr_err("unable to allocate ctl\n");
ret = -ENODEV;
goto error;
}
if (wb_id == MDSS_ROTATION_HW_ANY)
hw->wb = mdss_mdp_wb_alloc(MDSS_MDP_WB_ROTATOR, hw->ctl->num);
else
hw->wb = mdss_mdp_wb_assign(wb_id, hw->ctl->num);
if (IS_ERR_OR_NULL(hw->wb)) {
pr_err("unable to allocate wb\n");
ret = -ENODEV;
goto error;
}
hw->ctl->wb = hw->wb;
hw->mixer = mdss_mdp_mixer_assign(hw->wb->num, true, true);
if (IS_ERR_OR_NULL(hw->mixer)) {
pr_err("unable to allocate wb mixer\n");
ret = -ENODEV;
goto error;
}
hw->ctl->mixer_left = hw->mixer;
hw->mixer->ctl = hw->ctl;
hw->mixer->rotator_mode = true;
switch (hw->mixer->num) {
case MDSS_MDP_WB_LAYERMIXER0:
hw->ctl->opmode = MDSS_MDP_CTL_OP_ROT0_MODE;
break;
case MDSS_MDP_WB_LAYERMIXER1:
hw->ctl->opmode = MDSS_MDP_CTL_OP_ROT1_MODE;
break;
default:
pr_err("invalid layer mixer=%d\n", hw->mixer->num);
ret = -EINVAL;
goto error;
}
hw->ctl->ops.start_fnc = mdss_mdp_writeback_start;
hw->ctl->power_state = MDSS_PANEL_POWER_ON;
hw->ctl->wb_type = MDSS_MDP_WB_CTL_TYPE_BLOCK;
if (hw->ctl->ops.start_fnc)
ret = hw->ctl->ops.start_fnc(hw->ctl);
if (ret)
goto error;
if (pipe_id >= mdata->ndma_pipes)
goto error;
pipe_ndx = mdata->dma_pipes[pipe_id].ndx;
hw->pipe = mdss_mdp_pipe_assign(mdata, hw->mixer,
pipe_ndx, MDSS_MDP_PIPE_RECT0);
if (IS_ERR_OR_NULL(hw->pipe)) {
pr_err("dma pipe allocation failed\n");
ret = -ENODEV;
goto error;
}
hw->pipe->mixer_left = hw->mixer;
hw->pipe_id = hw->wb->num;
hw->wb_id = hw->wb->num;
return hw;
error:
if (!IS_ERR_OR_NULL(hw->pipe))
mdss_mdp_pipe_destroy(hw->pipe);
if (!IS_ERR_OR_NULL(hw->ctl)) {
if (hw->ctl->ops.stop_fnc)
hw->ctl->ops.stop_fnc(hw->ctl, MDSS_PANEL_POWER_OFF);
mdss_mdp_ctl_free(hw->ctl);
}
devm_kfree(&mgr->pdev->dev, hw);
return ERR_PTR(ret);
}
static void mdss_rotator_free_hw(struct mdss_rot_mgr *mgr,
struct mdss_rot_hw_resource *hw)
{
struct mdss_mdp_mixer *mixer;
struct mdss_mdp_ctl *ctl;
mixer = hw->pipe->mixer_left;
mdss_mdp_pipe_destroy(hw->pipe);
ctl = mdss_mdp_ctl_mixer_switch(mixer->ctl,
MDSS_MDP_WB_CTL_TYPE_BLOCK);
if (ctl) {
if (ctl->ops.stop_fnc)
ctl->ops.stop_fnc(ctl, MDSS_PANEL_POWER_OFF);
mdss_mdp_ctl_free(ctl);
}
devm_kfree(&mgr->pdev->dev, hw);
}
struct mdss_rot_hw_resource *mdss_rotator_get_hw_resource(
struct mdss_rot_queue *queue, struct mdss_rot_entry *entry)
{
struct mdss_rot_hw_resource *hw = queue->hw;
if (!hw) {
pr_err("no hw in the queue\n");
return NULL;
}
mutex_lock(&queue->hw_lock);
if (hw->workload) {
hw = ERR_PTR(-EBUSY);
goto get_hw_resource_err;
}
hw->workload = entry;
get_hw_resource_err:
mutex_unlock(&queue->hw_lock);
return hw;
}
static void mdss_rotator_put_hw_resource(struct mdss_rot_queue *queue,
struct mdss_rot_hw_resource *hw)
{
mutex_lock(&queue->hw_lock);
hw->workload = NULL;
mutex_unlock(&queue->hw_lock);
}
/*
* caller will need to call mdss_rotator_deinit_queue when
* the function returns error
*/
static int mdss_rotator_init_queue(struct mdss_rot_mgr *mgr)
{
int i, size, ret = 0;
char name[32];
size = sizeof(struct mdss_rot_queue) * mgr->queue_count;
mgr->queues = devm_kzalloc(&mgr->pdev->dev, size, GFP_KERNEL);
if (!mgr->queues)
return -ENOMEM;
for (i = 0; i < mgr->queue_count; i++) {
snprintf(name, sizeof(name), "rot_workq_%d", i);
pr_debug("work queue name=%s\n", name);
mgr->queues[i].rot_work_queue = alloc_ordered_workqueue("%s",
WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, name);
if (!mgr->queues[i].rot_work_queue) {
ret = -EPERM;
break;
}
snprintf(name, sizeof(name), "rot_timeline_%d", i);
pr_debug("timeline name=%s\n", name);
mgr->queues[i].timeline.timeline =
mdss_create_timeline(name);
if (!mgr->queues[i].timeline.timeline) {
ret = -EPERM;
break;
}
size = sizeof(mgr->queues[i].timeline.fence_name);
snprintf(mgr->queues[i].timeline.fence_name, size,
"rot_fence_%d", i);
mutex_init(&mgr->queues[i].timeline.lock);
mutex_init(&mgr->queues[i].hw_lock);
}
return ret;
}
static void mdss_rotator_deinit_queue(struct mdss_rot_mgr *mgr)
{
int i;
if (!mgr->queues)
return;
for (i = 0; i < mgr->queue_count; i++) {
if (mgr->queues[i].rot_work_queue)
destroy_workqueue(mgr->queues[i].rot_work_queue);
if (mgr->queues[i].timeline.timeline) {
struct mdss_timeline *obj;
obj = (struct mdss_timeline *)
mgr->queues[i].timeline.timeline;
mdss_destroy_timeline(obj);
}
}
devm_kfree(&mgr->pdev->dev, mgr->queues);
mgr->queue_count = 0;
}
/*
* mdss_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 mdss_rotator_assign_queue(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry,
struct mdss_rot_file_private *private)
{
struct mdss_rot_perf *perf;
struct mdss_rot_queue *queue;
struct mdss_rot_hw_resource *hw;
struct mdp_rotation_item *item = &entry->item;
u32 wb_idx = item->wb_idx;
u32 pipe_idx = item->pipe_idx;
int ret = 0;
/*
* todo: instead of always assign writeback block 0, we can
* apply some load balancing logic in the future
*/
if (wb_idx == MDSS_ROTATION_HW_ANY) {
wb_idx = 0;
pipe_idx = 0;
}
if (wb_idx >= mgr->queue_count) {
pr_err("Invalid wb idx = %d\n", wb_idx);
return -EINVAL;
}
queue = mgr->queues + wb_idx;
mutex_lock(&queue->hw_lock);
if (!queue->hw) {
hw = mdss_rotator_hw_alloc(mgr, pipe_idx, wb_idx);
if (IS_ERR_OR_NULL(hw)) {
pr_err("fail to allocate hw\n");
ret = PTR_ERR(hw);
} else {
queue->hw = hw;
}
}
if (queue->hw) {
entry->queue = queue;
queue->hw->pending_count++;
}
mutex_unlock(&queue->hw_lock);
perf = mdss_rotator_find_session(private, item->session_id);
if (!perf) {
pr_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 mdss_rotator_unassign_queue(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry)
{
struct mdss_rot_queue *queue = entry->queue;
if (!queue)
return;
entry->queue = NULL;
mutex_lock(&queue->hw_lock);
if (!queue->hw) {
pr_err("entry assigned a queue with no hw\n");
mutex_unlock(&queue->hw_lock);
return;
}
queue->hw->pending_count--;
if (queue->hw->pending_count == 0) {
mdss_rotator_free_hw(mgr, queue->hw);
queue->hw = NULL;
}
mutex_unlock(&queue->hw_lock);
}
static void mdss_rotator_queue_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry_container *req)
{
struct mdss_rot_entry *entry;
struct mdss_rot_queue *queue;
unsigned long clk_rate;
u32 wb_idx;
int i;
for (i = 0; i < req->count; i++) {
entry = req->entries + i;
queue = entry->queue;
wb_idx = queue->hw->wb_id;
mutex_lock(&entry->perf->work_dis_lock);
entry->perf->work_distribution[wb_idx]++;
mutex_unlock(&entry->perf->work_dis_lock);
entry->work_assigned = true;
}
clk_rate = mdss_rotator_clk_rate_calc(mgr, private);
mdss_rotator_set_clk_rate(mgr, clk_rate, MDSS_CLK_ROTATOR_CORE);
for (i = 0; i < req->count; i++) {
entry = req->entries + i;
queue = entry->queue;
entry->output_fence = NULL;
queue_work(queue->rot_work_queue, &entry->commit_work);
}
}
static int mdss_rotator_calc_perf(struct mdss_rot_perf *perf)
{
struct mdp_rotation_config *config = &perf->config;
u32 read_bw, write_bw;
struct mdss_mdp_format_params *in_fmt, *out_fmt;
in_fmt = mdss_mdp_get_format_params(config->input.format);
if (!in_fmt) {
pr_err("invalid input format\n");
return -EINVAL;
}
out_fmt = mdss_mdp_get_format_params(config->output.format);
if (!out_fmt) {
pr_err("invalid output format\n");
return -EINVAL;
}
if (!config->input.width ||
(0xffffffff/config->input.width < config->input.height))
return -EINVAL;
perf->clk_rate = config->input.width * config->input.height;
if (!perf->clk_rate ||
(0xffffffff/perf->clk_rate < config->frame_rate))
return -EINVAL;
perf->clk_rate *= config->frame_rate;
/* rotator processes 4 pixels per clock */
perf->clk_rate /= 4;
read_bw = config->input.width * config->input.height *
config->frame_rate;
if (in_fmt->chroma_sample == MDSS_MDP_CHROMA_420)
read_bw = (read_bw * 3) / 2;
else
read_bw *= in_fmt->bpp;
write_bw = config->output.width * config->output.height *
config->frame_rate;
if (out_fmt->chroma_sample == MDSS_MDP_CHROMA_420)
write_bw = (write_bw * 3) / 2;
else
write_bw *= out_fmt->bpp;
read_bw = apply_comp_ratio_factor(read_bw, in_fmt,
&config->input.comp_ratio);
write_bw = apply_comp_ratio_factor(write_bw, out_fmt,
&config->output.comp_ratio);
perf->bw = read_bw + write_bw;
return 0;
}
static int mdss_rotator_update_perf(struct mdss_rot_mgr *mgr)
{
struct mdss_rot_file_private *priv;
struct mdss_rot_perf *perf;
int not_in_suspend_mode;
u64 total_bw = 0;
ATRACE_BEGIN(__func__);
not_in_suspend_mode = !atomic_read(&mgr->device_suspended);
if (not_in_suspend_mode) {
mutex_lock(&mgr->file_lock);
list_for_each_entry(priv, &mgr->file_list, list) {
mutex_lock(&priv->perf_lock);
list_for_each_entry(perf, &priv->perf_list, list) {
total_bw += perf->bw;
}
mutex_unlock(&priv->perf_lock);
}
mutex_unlock(&mgr->file_lock);
}
mutex_lock(&mgr->bus_lock);
total_bw += mgr->pending_close_bw_vote;
mdss_rotator_enable_reg_bus(mgr, total_bw);
mdss_rotator_bus_scale_set_quota(&mgr->data_bus, total_bw);
mutex_unlock(&mgr->bus_lock);
ATRACE_END(__func__);
return 0;
}
static void mdss_rotator_release_from_work_distribution(
struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry)
{
if (entry->work_assigned) {
bool free_perf = false;
u32 wb_idx = entry->queue->hw->wb_id;
mutex_lock(&mgr->lock);
mutex_lock(&entry->perf->work_dis_lock);
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;
}
mutex_unlock(&entry->perf->work_dis_lock);
entry->work_assigned = false;
if (free_perf) {
mutex_lock(&mgr->bus_lock);
mgr->pending_close_bw_vote -= entry->perf->bw;
mutex_unlock(&mgr->bus_lock);
mdss_rotator_resource_ctrl(mgr, false);
devm_kfree(&mgr->pdev->dev,
entry->perf->work_distribution);
devm_kfree(&mgr->pdev->dev, entry->perf);
mdss_rotator_update_perf(mgr);
mdss_rotator_clk_ctrl(mgr, false);
entry->perf = NULL;
}
mutex_unlock(&mgr->lock);
}
}
static void mdss_rotator_release_entry(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry)
{
mdss_rotator_release_from_work_distribution(mgr, entry);
mdss_rotator_clear_fence(entry);
mdss_rotator_release_data(entry);
mdss_rotator_unassign_queue(mgr, entry);
}
static int mdss_rotator_config_dnsc_factor(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry *entry)
{
int ret = 0;
u16 src_w, src_h, dst_w, dst_h, bit;
struct mdp_rotation_item *item = &entry->item;
struct mdss_mdp_format_params *fmt;
src_w = item->src_rect.w;
src_h = item->src_rect.h;
if (item->flags & MDP_ROTATION_90) {
dst_w = item->dst_rect.h;
dst_h = item->dst_rect.w;
} else {
dst_w = item->dst_rect.w;
dst_h = item->dst_rect.h;
}
if (!mgr->has_downscale &&
(src_w != dst_w || src_h != dst_h)) {
pr_err("rotator downscale not supported\n");
ret = -EINVAL;
goto dnsc_err;
}
entry->dnsc_factor_w = 0;
entry->dnsc_factor_h = 0;
if ((src_w != dst_w) || (src_h != dst_h)) {
if ((src_w % dst_w) || (src_h % dst_h)) {
ret = -EINVAL;
goto dnsc_err;
}
entry->dnsc_factor_w = src_w / dst_w;
bit = fls(entry->dnsc_factor_w);
/*
* New Chipsets supports downscale upto 1/64
* change the Bit check from 5 to 7 to support 1/64 down scale
*/
if ((entry->dnsc_factor_w & ~BIT(bit - 1)) || (bit > 7)) {
ret = -EINVAL;
goto dnsc_err;
}
entry->dnsc_factor_h = src_h / dst_h;
bit = fls(entry->dnsc_factor_h);
if ((entry->dnsc_factor_h & ~BIT(bit - 1)) || (bit > 7)) {
ret = -EINVAL;
goto dnsc_err;
}
}
fmt = mdss_mdp_get_format_params(item->output.format);
if (mdss_mdp_is_ubwc_format(fmt) &&
(entry->dnsc_factor_h || entry->dnsc_factor_w)) {
pr_err("ubwc not supported with downscale %d\n",
item->output.format);
ret = -EINVAL;
}
dnsc_err:
/* Downscaler does not support asymmetrical dnsc */
if (entry->dnsc_factor_w != entry->dnsc_factor_h)
ret = -EINVAL;
if (ret) {
pr_err("Invalid rotator downscale ratio %dx%d->%dx%d\n",
src_w, src_h, dst_w, dst_h);
entry->dnsc_factor_w = 0;
entry->dnsc_factor_h = 0;
}
return ret;
}
static bool mdss_rotator_verify_format(struct mdss_rot_mgr *mgr,
struct mdss_mdp_format_params *in_fmt,
struct mdss_mdp_format_params *out_fmt, bool rotation)
{
u8 in_v_subsample, in_h_subsample;
u8 out_v_subsample, out_h_subsample;
if (!mgr->has_ubwc && (mdss_mdp_is_ubwc_format(in_fmt) ||
mdss_mdp_is_ubwc_format(out_fmt))) {
pr_err("Rotator doesn't allow ubwc\n");
return -EINVAL;
}
if (!(out_fmt->flag & VALID_ROT_WB_FORMAT)) {
pr_err("Invalid output format\n");
return false;
}
if (in_fmt->is_yuv != out_fmt->is_yuv) {
pr_err("Rotator does not support CSC\n");
return false;
}
/* 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])) {
pr_err("Bit format does not match\n");
return false;
}
}
/* Need to make sure that sub-sampling persists through rotation */
if (rotation) {
mdss_mdp_get_v_h_subsample_rate(in_fmt->chroma_sample,
&in_v_subsample, &in_h_subsample);
mdss_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)) {
pr_err("Rotation has invalid subsampling\n");
return false;
}
} else {
if (in_fmt->chroma_sample != out_fmt->chroma_sample) {
pr_err("Format subsampling mismatch\n");
return false;
}
}
pr_debug("in_fmt=%0d, out_fmt=%d, has_ubwc=%d\n",
in_fmt->format, out_fmt->format, mgr->has_ubwc);
return true;
}
static int mdss_rotator_verify_config(struct mdss_rot_mgr *mgr,
struct mdp_rotation_config *config)
{
struct mdss_mdp_format_params *in_fmt, *out_fmt;
u8 in_v_subsample, in_h_subsample;
u8 out_v_subsample, out_h_subsample;
u32 input, output;
bool rotation;
input = config->input.format;
output = config->output.format;
rotation = (config->flags & MDP_ROTATION_90) ? true : false;
in_fmt = mdss_mdp_get_format_params(input);
if (!in_fmt) {
pr_err("Unrecognized input format:%u\n", input);
return -EINVAL;
}
out_fmt = mdss_mdp_get_format_params(output);
if (!out_fmt) {
pr_err("Unrecognized output format:%u\n", output);
return -EINVAL;
}
mdss_mdp_get_v_h_subsample_rate(in_fmt->chroma_sample,
&in_v_subsample, &in_h_subsample);
mdss_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->input.height % in_v_subsample) ||
(config->input.width % in_h_subsample)) {
pr_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 -EINVAL;
}
if ((config->output.height % out_v_subsample) ||
(config->output.width % out_h_subsample)) {
pr_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 -EINVAL;
}
if (!mdss_rotator_verify_format(mgr, in_fmt,
out_fmt, rotation)) {
pr_err("Rot format pairing invalid, in_fmt:%d, out_fmt:%d\n",
input, output);
return -EINVAL;
}
return 0;
}
static int mdss_rotator_validate_item_matches_session(
struct mdp_rotation_config *config, struct mdp_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;
}
static int mdss_rotator_validate_img_roi(struct mdp_rotation_item *item)
{
struct mdss_mdp_format_params *fmt;
uint32_t width, height;
int ret = 0;
width = item->input.width;
height = item->input.height;
if (item->flags & MDP_ROTATION_DEINTERLACE) {
width *= 2;
height /= 2;
}
/* Check roi bounds */
if (ROT_CHECK_BOUNDS(item->src_rect.x, item->src_rect.w, width) ||
ROT_CHECK_BOUNDS(item->src_rect.y, item->src_rect.h,
height)) {
pr_err("invalid src flag=%08x img wh=%dx%d rect=%d,%d,%d,%d\n",
item->flags, width, height, item->src_rect.x,
item->src_rect.y, item->src_rect.w, item->src_rect.h);
return -EINVAL;
}
if (ROT_CHECK_BOUNDS(item->dst_rect.x, item->dst_rect.w,
item->output.width) ||
ROT_CHECK_BOUNDS(item->dst_rect.y, item->dst_rect.h,
item->output.height)) {
pr_err("invalid dst img wh=%dx%d rect=%d,%d,%d,%d\n",
item->output.width, item->output.height,
item->dst_rect.x, item->dst_rect.y, item->dst_rect.w,
item->dst_rect.h);
return -EINVAL;
}
fmt = mdss_mdp_get_format_params(item->output.format);
if (!fmt) {
pr_err("invalid output format:%d\n", item->output.format);
return -EINVAL;
}
if (mdss_mdp_is_ubwc_format(fmt))
ret = mdss_mdp_validate_offset_for_ubwc_format(fmt,
item->dst_rect.x, item->dst_rect.y);
return ret;
}
static int mdss_rotator_validate_fmt_and_item_flags(
struct mdp_rotation_config *config, struct mdp_rotation_item *item)
{
struct mdss_mdp_format_params *fmt;
fmt = mdss_mdp_get_format_params(item->input.format);
if ((item->flags & MDP_ROTATION_DEINTERLACE) &&
mdss_mdp_is_ubwc_format(fmt)) {
pr_err("cannot perform mdp deinterlace on tiled formats\n");
return -EINVAL;
}
return 0;
}
static int mdss_rotator_validate_entry(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry *entry)
{
int ret;
struct mdp_rotation_item *item;
struct mdss_rot_perf *perf;
item = &entry->item;
if (item->wb_idx != item->pipe_idx) {
pr_err("invalid writeback and pipe idx\n");
return -EINVAL;
}
if (item->wb_idx != MDSS_ROTATION_HW_ANY &&
item->wb_idx > mgr->queue_count) {
pr_err("invalid writeback idx\n");
return -EINVAL;
}
perf = mdss_rotator_find_session(private, item->session_id);
if (!perf) {
pr_err("Could not find session:%u\n", item->session_id);
return -EINVAL;
}
ret = mdss_rotator_validate_item_matches_session(&perf->config, item);
if (ret) {
pr_err("Work item does not match session:%u\n",
item->session_id);
return ret;
}
ret = mdss_rotator_validate_img_roi(item);
if (ret) {
pr_err("Image roi is invalid\n");
return ret;
}
ret = mdss_rotator_validate_fmt_and_item_flags(&perf->config, item);
if (ret)
return ret;
ret = mdss_rotator_config_dnsc_factor(mgr, entry);
if (ret) {
pr_err("fail to configure downscale factor\n");
return ret;
}
return ret;
}
/*
* Upon failure from the function, caller needs to make sure
* to call mdss_rotator_remove_request to clean up resources.
*/
static int mdss_rotator_add_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry_container *req)
{
struct mdss_rot_entry *entry;
struct mdp_rotation_item *item;
u32 flag = 0;
int i, ret;
for (i = 0; i < req->count; i++) {
entry = req->entries + i;
item = &entry->item;
if (item->flags & MDP_ROTATION_SECURE)
flag = MDP_SECURE_OVERLAY_SESSION;
ret = mdss_rotator_validate_entry(mgr, private, entry);
if (ret) {
pr_err("fail to validate the entry\n");
return ret;
}
ret = mdss_rotator_import_data(mgr, entry);
if (ret) {
pr_err("fail to import the data\n");
return ret;
}
if (item->input.fence >= 0) {
entry->input_fence = mdss_get_fd_sync_fence(
item->input.fence);
if (!entry->input_fence) {
pr_err("invalid input fence fd\n");
return -EINVAL;
}
}
ret = mdss_rotator_assign_queue(mgr, entry, private);
if (ret) {
pr_err("fail to assign queue to entry\n");
return ret;
}
entry->request = req;
INIT_WORK(&entry->commit_work, mdss_rotator_wq_handler);
ret = mdss_rotator_create_fence(entry);
if (ret) {
pr_err("fail to create fence\n");
return ret;
}
item->output.fence = entry->output_fence_fd;
pr_debug("Entry added. wbidx=%u, src{%u,%u,%u,%u}f=%u\n"
"dst{%u,%u,%u,%u}f=%u 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);
}
mutex_lock(&private->req_lock);
list_add(&req->list, &private->req_list);
mutex_unlock(&private->req_lock);
return 0;
}
static void mdss_rotator_remove_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry_container *req)
{
int i;
mutex_lock(&private->req_lock);
for (i = 0; i < req->count; i++)
mdss_rotator_release_entry(mgr, req->entries + i);
list_del_init(&req->list);
mutex_unlock(&private->req_lock);
}
/* This function should be called with req_lock */
static void mdss_rotator_cancel_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_entry_container *req)
{
struct mdss_rot_entry *entry;
int i;
/*
* To avoid signal the rotation entry output fence in the wrong
* order, all the entries in the same request needs to be cancelled
* first, before signaling the output fence.
*/
for (i = req->count - 1; i >= 0; i--) {
entry = req->entries + i;
cancel_work_sync(&entry->commit_work);
}
for (i = req->count - 1; i >= 0; i--) {
entry = req->entries + i;
mdss_rotator_signal_output(entry);
mdss_rotator_release_entry(mgr, entry);
}
list_del_init(&req->list);
devm_kfree(&mgr->pdev->dev, req);
}
static void mdss_rotator_cancel_all_requests(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private)
{
struct mdss_rot_entry_container *req, *req_next;
pr_debug("Canceling all rotator requests\n");
mutex_lock(&private->req_lock);
list_for_each_entry_safe(req, req_next, &private->req_list, list)
mdss_rotator_cancel_request(mgr, req);
mutex_unlock(&private->req_lock);
}
static void mdss_rotator_free_competed_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private)
{
struct mdss_rot_entry_container *req, *req_next;
mutex_lock(&private->req_lock);
list_for_each_entry_safe(req, req_next, &private->req_list, list) {
if (atomic_read(&req->pending_count) == 0) {
list_del_init(&req->list);
devm_kfree(&mgr->pdev->dev, req);
}
}
mutex_unlock(&private->req_lock);
}
static void mdss_rotator_release_rotator_perf_session(
struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private)
{
struct mdss_rot_perf *perf, *perf_next;
pr_debug("Releasing all rotator request\n");
mdss_rotator_cancel_all_requests(mgr, private);
mutex_lock(&private->perf_lock);
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);
}
mutex_unlock(&private->perf_lock);
}
static void mdss_rotator_release_all(struct mdss_rot_mgr *mgr)
{
struct mdss_rot_file_private *priv, *priv_next;
mutex_lock(&mgr->file_lock);
list_for_each_entry_safe(priv, priv_next, &mgr->file_list, list) {
mdss_rotator_release_rotator_perf_session(mgr, priv);
mdss_rotator_resource_ctrl(mgr, false);
list_del_init(&priv->list);
priv->file->private_data = NULL;
devm_kfree(&mgr->pdev->dev, priv);
}
mutex_unlock(&rot_mgr->file_lock);
mdss_rotator_update_perf(mgr);
}
static int mdss_rotator_prepare_hw(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
struct mdss_mdp_pipe *pipe;
struct mdss_mdp_ctl *orig_ctl, *rot_ctl;
int ret;
pipe = hw->pipe;
orig_ctl = pipe->mixer_left->ctl;
if (orig_ctl->shared_lock)
mutex_lock(orig_ctl->shared_lock);
rot_ctl = mdss_mdp_ctl_mixer_switch(orig_ctl,
MDSS_MDP_WB_CTL_TYPE_BLOCK);
if (!rot_ctl) {
ret = -EINVAL;
goto error;
} else {
hw->ctl = rot_ctl;
pipe->mixer_left = rot_ctl->mixer_left;
}
return 0;
error:
if (orig_ctl->shared_lock)
mutex_unlock(orig_ctl->shared_lock);
return ret;
}
static void mdss_rotator_translate_rect(struct mdss_rect *dst,
struct mdp_rect *src)
{
dst->x = src->x;
dst->y = src->y;
dst->w = src->w;
dst->h = src->h;
}
static u32 mdss_rotator_translate_flags(u32 input)
{
u32 output = 0;
if (input & MDP_ROTATION_NOP)
output |= MDP_ROT_NOP;
if (input & MDP_ROTATION_FLIP_LR)
output |= MDP_FLIP_LR;
if (input & MDP_ROTATION_FLIP_UD)
output |= MDP_FLIP_UD;
if (input & MDP_ROTATION_90)
output |= MDP_ROT_90;
if (input & MDP_ROTATION_DEINTERLACE)
output |= MDP_DEINTERLACE;
if (input & MDP_ROTATION_SECURE)
output |= MDP_SECURE_OVERLAY_SESSION;
if (input & MDP_ROTATION_BWC_EN)
output |= MDP_BWC_EN;
return output;
}
static int mdss_rotator_config_hw(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
struct mdss_mdp_pipe *pipe;
struct mdp_rotation_item *item;
struct mdss_rot_perf *perf;
int ret;
ATRACE_BEGIN(__func__);
pipe = hw->pipe;
item = &entry->item;
perf = entry->perf;
pipe->flags = mdss_rotator_translate_flags(item->flags);
pipe->src_fmt = mdss_mdp_get_format_params(item->input.format);
pipe->img_width = item->input.width;
pipe->img_height = item->input.height;
mdss_rotator_translate_rect(&pipe->src, &item->src_rect);
mdss_rotator_translate_rect(&pipe->dst, &item->src_rect);
pipe->scaler.enable = 0;
pipe->frame_rate = perf->config.frame_rate;
pipe->params_changed++;
mdss_mdp_smp_release(pipe);
ret = mdss_mdp_smp_reserve(pipe);
if (ret) {
pr_err("unable to mdss_mdp_smp_reserve rot data\n");
goto done;
}
ret = mdss_mdp_overlay_setup_scaling(pipe);
if (ret) {
pr_err("scaling setup failed %d\n", ret);
goto done;
}
ret = mdss_mdp_pipe_queue_data(pipe, &entry->src_buf);
pr_debug("Config pipe. src{%u,%u,%u,%u}f=%u\n"
"dst{%u,%u,%u,%u}f=%u session_id=%u\n",
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);
MDSS_XLOG(item->input.format, pipe->img_width, pipe->img_height,
pipe->flags);
done:
ATRACE_END(__func__);
return ret;
}
static int mdss_rotator_kickoff_entry(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
int ret;
struct mdss_mdp_writeback_arg wb_args = {
.data = &entry->dst_buf,
.priv_data = entry,
};
ret = mdss_mdp_writeback_display_commit(hw->ctl, &wb_args);
return ret;
}
static int mdss_rotator_wait_for_entry(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
int ret;
struct mdss_mdp_ctl *ctl = hw->ctl;
ret = mdss_mdp_display_wait4comp(ctl);
if (ctl->shared_lock)
mutex_unlock(ctl->shared_lock);
return ret;
}
static int mdss_rotator_commit_entry(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
int ret;
ret = mdss_rotator_prepare_hw(hw, entry);
if (ret) {
pr_err("fail to prepare hw resource %d\n", ret);
return ret;
}
ret = mdss_rotator_config_hw(hw, entry);
if (ret) {
pr_err("fail to configure hw resource %d\n", ret);
return ret;
}
ret = mdss_rotator_kickoff_entry(hw, entry);
if (ret) {
pr_err("fail to do kickoff %d\n", ret);
return ret;
}
ret = mdss_rotator_wait_for_entry(hw, entry);
if (ret) {
pr_err("fail to wait for completion %d\n", ret);
return ret;
}
return ret;
}
static int mdss_rotator_handle_entry(struct mdss_rot_hw_resource *hw,
struct mdss_rot_entry *entry)
{
int ret;
ret = mdss_rotator_wait_for_input(entry);
if (ret) {
pr_err("wait for input buffer failed %d\n", ret);
return ret;
}
ret = mdss_rotator_map_and_check_data(entry);
if (ret) {
pr_err("fail to prepare input/output data %d\n", ret);
return ret;
}
ret = mdss_rotator_commit_entry(hw, entry);
if (ret)
pr_err("rotator commit failed %d\n", ret);
return ret;
}
static void mdss_rotator_wq_handler(struct work_struct *work)
{
struct mdss_rot_entry *entry;
struct mdss_rot_entry_container *request;
struct mdss_rot_hw_resource *hw;
int ret;
entry = container_of(work, struct mdss_rot_entry, commit_work);
request = entry->request;
if (!request) {
pr_err("fatal error, no request with entry\n");
return;
}
hw = mdss_rotator_get_hw_resource(entry->queue, entry);
if (!hw) {
pr_err("no hw for the queue\n");
goto get_hw_res_err;
}
ret = mdss_rotator_handle_entry(hw, entry);
if (ret) {
struct mdp_rotation_item *item = &entry->item;
pr_err("Rot req fail. src{%u,%u,%u,%u}f=%u\n"
"dst{%u,%u,%u,%u}f=%u session_id=%u, wbidx%d, pipe_id=%d\n",
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, item->wb_idx, item->pipe_idx);
}
mdss_rotator_put_hw_resource(entry->queue, hw);
get_hw_res_err:
mdss_rotator_signal_output(entry);
mdss_rotator_release_entry(rot_mgr, entry);
atomic_dec(&request->pending_count);
}
static int mdss_rotator_validate_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry_container *req)
{
int i, ret = 0;
struct mdss_rot_entry *entry;
for (i = 0; i < req->count; i++) {
entry = req->entries + i;
ret = mdss_rotator_validate_entry(mgr, private,
entry);
if (ret) {
pr_err("fail to validate the entry\n");
return ret;
}
}
return ret;
}
static u32 mdss_rotator_generator_session_id(struct mdss_rot_mgr *mgr)
{
u32 id;
mutex_lock(&mgr->lock);
id = mgr->session_id_generator++;
mutex_unlock(&mgr->lock);
return id;
}
static int mdss_rotator_open_session(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
struct mdp_rotation_config config;
struct mdss_rot_perf *perf;
int ret;
ret = copy_from_user(&config, (void __user *)arg, sizeof(config));
if (ret) {
pr_err("fail to copy session data\n");
return ret;
}
ret = mdss_rotator_verify_config(mgr, &config);
if (ret) {
pr_err("Rotator verify format failed\n");
return ret;
}
perf = devm_kzalloc(&mgr->pdev->dev, sizeof(*perf), GFP_KERNEL);
if (!perf)
return -ENOMEM;
ATRACE_BEGIN(__func__); /* Open session votes for bw */
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 = mdss_rotator_generator_session_id(mgr);
perf->config = config;
perf->last_wb_idx = -1;
mutex_init(&perf->work_dis_lock);
INIT_LIST_HEAD(&perf->list);
ret = mdss_rotator_calc_perf(perf);
if (ret) {
pr_err("error setting the session%d\n", ret);
goto copy_user_err;
}
ret = copy_to_user((void *)arg, &config, sizeof(config));
if (ret) {
pr_err("fail to copy to user\n");
goto copy_user_err;
}
mutex_lock(&private->perf_lock);
list_add(&perf->list, &private->perf_list);
mutex_unlock(&private->perf_lock);
ret = mdss_rotator_resource_ctrl(mgr, true);
if (ret) {
pr_err("Failed to aqcuire rotator resources\n");
goto resource_err;
}
mdss_rotator_clk_ctrl(rot_mgr, true);
ret = mdss_rotator_update_perf(mgr);
if (ret) {
pr_err("fail to open session, not enough clk/bw\n");
goto perf_err;
}
pr_debug("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;
perf_err:
mdss_rotator_clk_ctrl(rot_mgr, false);
mdss_rotator_resource_ctrl(mgr, false);
resource_err:
mutex_lock(&private->perf_lock);
list_del_init(&perf->list);
mutex_unlock(&private->perf_lock);
copy_user_err:
devm_kfree(&mgr->pdev->dev, perf->work_distribution);
alloc_err:
devm_kfree(&mgr->pdev->dev, perf);
done:
ATRACE_END(__func__);
return ret;
}
static int mdss_rotator_close_session(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
struct mdss_rot_perf *perf;
bool offload_release_work = false;
u32 id;
id = (u32)arg;
mutex_lock(&mgr->lock);
mutex_lock(&private->perf_lock);
perf = __mdss_rotator_find_session(private, id);
if (!perf) {
mutex_unlock(&private->perf_lock);
mutex_unlock(&mgr->lock);
pr_err("Trying to close session that does not exist\n");
return -EINVAL;
}
ATRACE_BEGIN(__func__);
mutex_lock(&perf->work_dis_lock);
if (mdss_rotator_is_work_pending(mgr, perf)) {
pr_debug("Work is still pending, offload free to wq\n");
mutex_lock(&mgr->bus_lock);
mgr->pending_close_bw_vote += perf->bw;
mutex_unlock(&mgr->bus_lock);
offload_release_work = true;
}
list_del_init(&perf->list);
mutex_unlock(&perf->work_dis_lock);
mutex_unlock(&private->perf_lock);
if (offload_release_work)
goto done;
mdss_rotator_resource_ctrl(mgr, false);
devm_kfree(&mgr->pdev->dev, perf->work_distribution);
devm_kfree(&mgr->pdev->dev, perf);
mdss_rotator_update_perf(mgr);
mdss_rotator_clk_ctrl(rot_mgr, false);
done:
pr_debug("Closed session id:%u", id);
ATRACE_END(__func__);
mutex_unlock(&mgr->lock);
return 0;
}
static int mdss_rotator_config_session(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
int ret = 0;
struct mdss_rot_perf *perf;
struct mdp_rotation_config config;
ret = copy_from_user(&config, (void __user *)arg,
sizeof(config));
if (ret) {
pr_err("fail to copy session data\n");
return ret;
}
ret = mdss_rotator_verify_config(mgr, &config);
if (ret) {
pr_err("Rotator verify format failed\n");
return ret;
}
mutex_lock(&mgr->lock);
perf = mdss_rotator_find_session(private, config.session_id);
if (!perf) {
pr_err("No session with id=%u could be found\n",
config.session_id);
mutex_unlock(&mgr->lock);
return -EINVAL;
}
ATRACE_BEGIN(__func__);
mutex_lock(&private->perf_lock);
perf->config = config;
ret = mdss_rotator_calc_perf(perf);
mutex_unlock(&private->perf_lock);
if (ret) {
pr_err("error in configuring the session %d\n", ret);
goto done;
}
ret = mdss_rotator_update_perf(mgr);
pr_debug("reconfig 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);
done:
ATRACE_END(__func__);
mutex_unlock(&mgr->lock);
return ret;
}
struct mdss_rot_entry_container *mdss_rotator_req_init(
struct mdss_rot_mgr *mgr, struct mdp_rotation_item *items,
u32 count, u32 flags)
{
struct mdss_rot_entry_container *req;
int size, i;
/*
* Check input and output plane_count from each given item
* are within the MAX_PLANES limit
*/
for (i = 0 ; i < count; i++) {
if ((items[i].input.plane_count > MAX_PLANES) ||
(items[i].output.plane_count > MAX_PLANES)) {
pr_err("Input/Output plane_count exceeds MAX_PLANES limit, input:%d, output:%d\n",
items[i].input.plane_count,
items[i].output.plane_count);
return ERR_PTR(-EINVAL);
}
}
size = sizeof(struct mdss_rot_entry_container);
size += sizeof(struct mdss_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 mdss_rot_entry *)
((void *)req + sizeof(struct mdss_rot_entry_container));
req->flags = flags;
atomic_set(&req->pending_count, count);
for (i = 0; i < count; i++)
req->entries[i].item = items[i];
return req;
}
static int mdss_rotator_handle_request_common(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private,
struct mdss_rot_entry_container *req,
struct mdp_rotation_item *items)
{
int i, ret;
mdss_rotator_free_competed_request(mgr, private);
ret = mdss_rotator_add_request(mgr, private, req);
if (ret) {
pr_err("fail to add rotation request\n");
mdss_rotator_remove_request(mgr, private, req);
return ret;
}
for (i = 0; i < req->count; i++)
items[i].output.fence =
req->entries[i].item.output.fence;
return ret;
}
static int mdss_rotator_handle_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
struct mdp_rotation_request user_req;
struct mdp_rotation_item *items = NULL;
struct mdss_rot_entry_container *req = NULL;
int size, ret;
uint32_t req_count;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (mdata->handoff_pending) {
pr_err("Rotator request failed. Handoff pending\n");
return -EPERM;
}
if (mdss_get_sd_client_cnt()) {
pr_err("rot request not permitted during secure display session\n");
return -EPERM;
}
ret = copy_from_user(&user_req, (void __user *)arg,
sizeof(user_req));
if (ret) {
pr_err("fail to copy rotation request\n");
return ret;
}
req_count = user_req.count;
if ((!req_count) || (req_count > MAX_LAYER_COUNT)) {
pr_err("invalid rotator req count :%d\n", req_count);
return -EINVAL;
}
/*
* here, we make a copy of the items so that we can copy
* all the output fences to the client in one call. Otherwise,
* we will have to call multiple copy_to_user
*/
size = sizeof(struct mdp_rotation_item) * req_count;
items = devm_kzalloc(&mgr->pdev->dev, size, GFP_KERNEL);
if (!items) {
pr_err("fail to allocate rotation items\n");
return -ENOMEM;
}
ret = copy_from_user(items, user_req.list, size);
if (ret) {
pr_err("fail to copy rotation items\n");
goto handle_request_err;
}
req = mdss_rotator_req_init(mgr, items, user_req.count, user_req.flags);
if (IS_ERR_OR_NULL(req)) {
pr_err("fail to allocate rotation request\n");
ret = PTR_ERR(req);
goto handle_request_err;
}
mutex_lock(&mgr->lock);
if (req->flags & MDSS_ROTATION_REQUEST_VALIDATE) {
ret = mdss_rotator_validate_request(mgr, private, req);
goto handle_request_err1;
}
ret = mdss_rotator_handle_request_common(mgr, private, req, items);
if (ret) {
pr_err("fail to handle request\n");
goto handle_request_err1;
}
ret = copy_to_user(user_req.list, items, size);
if (ret) {
pr_err("fail to copy output fence to user\n");
mdss_rotator_remove_request(mgr, private, req);
goto handle_request_err1;
}
mdss_rotator_queue_request(mgr, private, req);
mutex_unlock(&mgr->lock);
devm_kfree(&mgr->pdev->dev, items);
return ret;
handle_request_err1:
mutex_unlock(&mgr->lock);
handle_request_err:
devm_kfree(&mgr->pdev->dev, items);
devm_kfree(&mgr->pdev->dev, req);
return ret;
}
static int mdss_rotator_open(struct inode *inode, struct file *file)
{
struct mdss_rot_file_private *private;
if (!rot_mgr)
return -ENODEV;
if (atomic_read(&rot_mgr->device_suspended))
return -EPERM;
private = devm_kzalloc(&rot_mgr->pdev->dev, sizeof(*private),
GFP_KERNEL);
if (!private)
return -ENOMEM;
mutex_init(&private->req_lock);
mutex_init(&private->perf_lock);
INIT_LIST_HEAD(&private->req_list);
INIT_LIST_HEAD(&private->perf_list);
INIT_LIST_HEAD(&private->list);
mutex_lock(&rot_mgr->file_lock);
list_add(&private->list, &rot_mgr->file_list);
file->private_data = private;
private->file = file;
mutex_unlock(&rot_mgr->file_lock);
return 0;
}
static bool mdss_rotator_file_priv_allowed(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *priv)
{
struct mdss_rot_file_private *_priv, *_priv_next;
bool ret = false;
mutex_lock(&mgr->file_lock);
list_for_each_entry_safe(_priv, _priv_next, &mgr->file_list, list) {
if (_priv == priv) {
ret = true;
break;
}
}
mutex_unlock(&mgr->file_lock);
return ret;
}
static int mdss_rotator_close(struct inode *inode, struct file *file)
{
struct mdss_rot_file_private *private;
if (!rot_mgr)
return -ENODEV;
if (!file->private_data)
return -EINVAL;
private = (struct mdss_rot_file_private *)file->private_data;
if (!(mdss_rotator_file_priv_allowed(rot_mgr, private))) {
pr_err("Calling close with unrecognized rot_file_private\n");
return -EINVAL;
}
mdss_rotator_release_rotator_perf_session(rot_mgr, private);
mutex_lock(&rot_mgr->file_lock);
list_del_init(&private->list);
devm_kfree(&rot_mgr->pdev->dev, private);
file->private_data = NULL;
mutex_unlock(&rot_mgr->file_lock);
mdss_rotator_update_perf(rot_mgr);
return 0;
}
#ifdef CONFIG_COMPAT
static int mdss_rotator_handle_request32(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
struct mdp_rotation_request32 user_req32;
struct mdp_rotation_item *items = NULL;
struct mdss_rot_entry_container *req = NULL;
int size, ret;
uint32_t req_count;
if (mdss_get_sd_client_cnt()) {
pr_err("rot request not permitted during secure display session\n");
return -EPERM;
}
ret = copy_from_user(&user_req32, (void __user *)arg,
sizeof(user_req32));
if (ret) {
pr_err("fail to copy rotation request\n");
return ret;
}
req_count = user_req32.count;
if ((!req_count) || (req_count > MAX_LAYER_COUNT)) {
pr_err("invalid rotator req count :%d\n", req_count);
return -EINVAL;
}
size = sizeof(struct mdp_rotation_item) * req_count;
items = devm_kzalloc(&mgr->pdev->dev, size, GFP_KERNEL);
if (!items) {
pr_err("fail to allocate rotation items\n");
return -ENOMEM;
}
ret = copy_from_user(items, compat_ptr(user_req32.list), size);
if (ret) {
pr_err("fail to copy rotation items\n");
goto handle_request32_err;
}
req = mdss_rotator_req_init(mgr, items, user_req32.count,
user_req32.flags);
if (IS_ERR_OR_NULL(req)) {
pr_err("fail to allocate rotation request\n");
ret = PTR_ERR(req);
goto handle_request32_err;
}
mutex_lock(&mgr->lock);
if (req->flags & MDSS_ROTATION_REQUEST_VALIDATE) {
ret = mdss_rotator_validate_request(mgr, private, req);
goto handle_request32_err1;
}
ret = mdss_rotator_handle_request_common(mgr, private, req, items);
if (ret) {
pr_err("fail to handle request\n");
goto handle_request32_err1;
}
ret = copy_to_user(compat_ptr(user_req32.list), items, size);
if (ret) {
pr_err("fail to copy output fence to user\n");
mdss_rotator_remove_request(mgr, private, req);
goto handle_request32_err1;
}
mdss_rotator_queue_request(mgr, private, req);
mutex_unlock(&mgr->lock);
devm_kfree(&mgr->pdev->dev, items);
return ret;
handle_request32_err1:
mutex_unlock(&mgr->lock);
handle_request32_err:
devm_kfree(&mgr->pdev->dev, items);
devm_kfree(&mgr->pdev->dev, req);
return ret;
}
static unsigned int __do_compat_ioctl_rot(unsigned int cmd32)
{
unsigned int cmd;
switch (cmd32) {
case MDSS_ROTATION_REQUEST32:
cmd = MDSS_ROTATION_REQUEST;
break;
case MDSS_ROTATION_OPEN32:
cmd = MDSS_ROTATION_OPEN;
break;
case MDSS_ROTATION_CLOSE32:
cmd = MDSS_ROTATION_CLOSE;
break;
case MDSS_ROTATION_CONFIG32:
cmd = MDSS_ROTATION_CONFIG;
break;
default:
cmd = cmd32;
break;
}
return cmd;
}
static long mdss_rotator_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct mdss_rot_file_private *private;
int ret = -EINVAL;
if (!rot_mgr)
return -ENODEV;
if (atomic_read(&rot_mgr->device_suspended))
return -EPERM;
if (!file->private_data)
return -EINVAL;
private = (struct mdss_rot_file_private *)file->private_data;
if (!(mdss_rotator_file_priv_allowed(rot_mgr, private))) {
pr_err("Calling ioctl with unrecognized rot_file_private\n");
return -EINVAL;
}
cmd = __do_compat_ioctl_rot(cmd);
switch (cmd) {
case MDSS_ROTATION_REQUEST:
ATRACE_BEGIN("rotator_request32");
ret = mdss_rotator_handle_request32(rot_mgr, private, arg);
ATRACE_END("rotator_request32");
break;
case MDSS_ROTATION_OPEN:
ret = mdss_rotator_open_session(rot_mgr, private, arg);
break;
case MDSS_ROTATION_CLOSE:
ret = mdss_rotator_close_session(rot_mgr, private, arg);
break;
case MDSS_ROTATION_CONFIG:
ret = mdss_rotator_config_session(rot_mgr, private, arg);
break;
default:
pr_err("unexpected IOCTL %d\n", cmd);
}
if (ret)
pr_err("rotator ioctl=%d failed, err=%d\n", cmd, ret);
return ret;
}
#endif
static long mdss_rotator_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct mdss_rot_file_private *private;
int ret = -EINVAL;
if (!rot_mgr)
return -ENODEV;
if (atomic_read(&rot_mgr->device_suspended))
return -EPERM;
if (!file->private_data)
return -EINVAL;
private = (struct mdss_rot_file_private *)file->private_data;
if (!(mdss_rotator_file_priv_allowed(rot_mgr, private))) {
pr_err("Calling ioctl with unrecognized rot_file_private\n");
return -EINVAL;
}
switch (cmd) {
case MDSS_ROTATION_REQUEST:
ATRACE_BEGIN("rotator_request");
ret = mdss_rotator_handle_request(rot_mgr, private, arg);
ATRACE_END("rotator_request");
break;
case MDSS_ROTATION_OPEN:
ret = mdss_rotator_open_session(rot_mgr, private, arg);
break;
case MDSS_ROTATION_CLOSE:
ret = mdss_rotator_close_session(rot_mgr, private, arg);
break;
case MDSS_ROTATION_CONFIG:
ret = mdss_rotator_config_session(rot_mgr, private, arg);
break;
default:
pr_err("unexpected IOCTL %d\n", cmd);
}
if (ret)
pr_err("rotator ioctl=%d failed, err=%d\n", cmd, ret);
return ret;
}
static ssize_t mdss_rotator_show_capabilities(struct device *dev,
struct device_attribute *attr, char *buf)
{
size_t len = PAGE_SIZE;
int cnt = 0;
if (!rot_mgr)
return cnt;
#define SPRINT(fmt, ...) \
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
SPRINT("wb_count=%d\n", rot_mgr->queue_count);
SPRINT("downscale=%d\n", rot_mgr->has_downscale);
return cnt;
}
static DEVICE_ATTR(caps, 0444, mdss_rotator_show_capabilities, NULL);
static struct attribute *mdss_rotator_fs_attrs[] = {
&dev_attr_caps.attr,
NULL
};
static struct attribute_group mdss_rotator_fs_attr_group = {
.attrs = mdss_rotator_fs_attrs
};
static const struct file_operations mdss_rotator_fops = {
.owner = THIS_MODULE,
.open = mdss_rotator_open,
.release = mdss_rotator_close,
.unlocked_ioctl = mdss_rotator_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mdss_rotator_compat_ioctl,
#endif
};
static int mdss_rotator_parse_dt_bus(struct mdss_rot_mgr *mgr,
struct platform_device *dev)
{
struct device_node *node;
int ret = 0, i;
bool register_bus_needed;
int usecases;
mgr->data_bus.bus_scale_pdata = msm_bus_cl_get_pdata(dev);
if (IS_ERR_OR_NULL(mgr->data_bus.bus_scale_pdata)) {
ret = PTR_ERR(mgr->data_bus.bus_scale_pdata);
if (!ret) {
ret = -EINVAL;
pr_err("msm_bus_cl_get_pdata failed. ret=%d\n", ret);
mgr->data_bus.bus_scale_pdata = NULL;
}
}
register_bus_needed = of_property_read_bool(dev->dev.of_node,
"qcom,mdss-has-reg-bus");
if (register_bus_needed) {
node = of_get_child_by_name(
dev->dev.of_node, "qcom,mdss-rot-reg-bus");
if (!node) {
mgr->reg_bus.bus_scale_pdata = &rot_reg_bus_scale_table;
usecases = mgr->reg_bus.bus_scale_pdata->num_usecases;
for (i = 0; i < usecases; i++) {
rot_reg_bus_usecases[i].num_paths = 1;
rot_reg_bus_usecases[i].vectors =
&rot_reg_bus_vectors[i];
}
} else {
mgr->reg_bus.bus_scale_pdata =
msm_bus_pdata_from_node(dev, node);
if (IS_ERR_OR_NULL(mgr->reg_bus.bus_scale_pdata)) {
ret = PTR_ERR(mgr->reg_bus.bus_scale_pdata);
if (!ret)
ret = -EINVAL;
pr_err("reg_rot_bus failed rc=%d\n", ret);
mgr->reg_bus.bus_scale_pdata = NULL;
}
}
}
return ret;
}
static int mdss_rotator_parse_dt(struct mdss_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) {
pr_err("Error in device tree\n");
return ret;
}
if (data > ROT_MAX_HW_BLOCKS) {
pr_err("Err, num of wb block (%d) larger than sw max %d\n",
data, ROT_MAX_HW_BLOCKS);
return -EINVAL;
}
rot_mgr->queue_count = data;
rot_mgr->has_downscale = of_property_read_bool(dev->dev.of_node,
"qcom,mdss-has-downscale");
rot_mgr->has_ubwc = of_property_read_bool(dev->dev.of_node,
"qcom,mdss-has-ubwc");
ret = mdss_rotator_parse_dt_bus(mgr, dev);
if (ret)
pr_err("Failed to parse bus data\n");
return ret;
}
static void mdss_rotator_put_dt_vreg_data(struct device *dev,
struct dss_module_power *mp)
{
if (!mp) {
DEV_ERR("%s: invalid input\n", __func__);
return;
}
msm_dss_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 mdss_rotator_get_dt_vreg_data(struct device *dev,
struct dss_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) {
DEV_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) {
DEV_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 dss_vreg) *
dt_vreg_total, GFP_KERNEL);
if (!mp->vreg_config) {
DEV_ERR("%s: can't alloc vreg mem\n", __func__);
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) {
DEV_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);
}
msm_dss_config_vreg(dev, mp->vreg_config, mp->num_vreg, 1);
for (i = 0; i < dt_vreg_total; i++) {
DEV_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].load[DSS_REG_MODE_ENABLE],
mp->vreg_config[i].load[DSS_REG_MODE_DISABLE]);
}
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 mdss_rotator_bus_scale_unregister(struct mdss_rot_mgr *mgr)
{
pr_debug("unregister bus_hdl=%x, reg_bus_hdl=%x\n",
mgr->data_bus.bus_hdl, mgr->reg_bus.bus_hdl);
if (mgr->data_bus.bus_hdl)
msm_bus_scale_unregister_client(mgr->data_bus.bus_hdl);
if (mgr->reg_bus.bus_hdl)
msm_bus_scale_unregister_client(mgr->reg_bus.bus_hdl);
}
static int mdss_rotator_bus_scale_register(struct mdss_rot_mgr *mgr)
{
if (!mgr->data_bus.bus_scale_pdata) {
pr_err("Scale table is NULL\n");
return -EINVAL;
}
mgr->data_bus.bus_hdl =
msm_bus_scale_register_client(
mgr->data_bus.bus_scale_pdata);
if (!mgr->data_bus.bus_hdl) {
pr_err("bus_client register failed\n");
return -EINVAL;
}
pr_debug("registered bus_hdl=%x\n", mgr->data_bus.bus_hdl);
if (mgr->reg_bus.bus_scale_pdata) {
mgr->reg_bus.bus_hdl =
msm_bus_scale_register_client(
mgr->reg_bus.bus_scale_pdata);
if (!mgr->reg_bus.bus_hdl) {
pr_err("register bus_client register failed\n");
mdss_rotator_bus_scale_unregister(mgr);
return -EINVAL;
}
pr_debug("registered register bus_hdl=%x\n",
mgr->reg_bus.bus_hdl);
}
return 0;
}
static int mdss_rotator_clk_register(struct platform_device *pdev,
struct mdss_rot_mgr *mgr, char *clk_name, u32 clk_idx)
{
struct clk *tmp;
pr_debug("registered clk_reg\n");
if (clk_idx >= MDSS_CLK_ROTATOR_END_IDX) {
pr_err("invalid clk index %d\n", clk_idx);
return -EINVAL;
}
if (mgr->rot_clk[clk_idx]) {
pr_err("Stomping on clk prev registered:%d\n", clk_idx);
return -EINVAL;
}
tmp = devm_clk_get(&pdev->dev, clk_name);
if (IS_ERR(tmp)) {
pr_err("unable to get clk: %s\n", clk_name);
return PTR_ERR(tmp);
}
mgr->rot_clk[clk_idx] = tmp;
return 0;
}
static int mdss_rotator_res_init(struct platform_device *pdev,
struct mdss_rot_mgr *mgr)
{
int ret;
ret = mdss_rotator_get_dt_vreg_data(&pdev->dev, &mgr->module_power);
if (ret)
return ret;
ret = mdss_rotator_clk_register(pdev, mgr,
"iface_clk", MDSS_CLK_ROTATOR_AHB);
if (ret)
goto error;
ret = mdss_rotator_clk_register(pdev, mgr,
"rot_core_clk", MDSS_CLK_ROTATOR_CORE);
if (ret)
goto error;
ret = mdss_rotator_bus_scale_register(mgr);
if (ret)
goto error;
return 0;
error:
mdss_rotator_put_dt_vreg_data(&pdev->dev, &mgr->module_power);
return ret;
}
static int mdss_rotator_probe(struct platform_device *pdev)
{
int ret;
rot_mgr = devm_kzalloc(&pdev->dev, sizeof(struct mdss_rot_mgr),
GFP_KERNEL);
if (!rot_mgr)
return -ENOMEM;
rot_mgr->pdev = pdev;
ret = mdss_rotator_parse_dt(rot_mgr, pdev);
if (ret) {
pr_err("fail to parse the dt\n");
goto error_parse_dt;
}
mutex_init(&rot_mgr->lock);
mutex_init(&rot_mgr->clk_lock);
mutex_init(&rot_mgr->bus_lock);
atomic_set(&rot_mgr->device_suspended, 0);
ret = mdss_rotator_init_queue(rot_mgr);
if (ret) {
pr_err("fail to init queue\n");
goto error_get_dev_num;
}
mutex_init(&rot_mgr->file_lock);
INIT_LIST_HEAD(&rot_mgr->file_list);
platform_set_drvdata(pdev, rot_mgr);
ret = alloc_chrdev_region(&rot_mgr->dev_num, 0, 1, DRIVER_NAME);
if (ret < 0) {
pr_err("alloc_chrdev_region failed ret = %d\n", ret);
goto error_get_dev_num;
}
rot_mgr->class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(rot_mgr->class)) {
ret = PTR_ERR(rot_mgr->class);
pr_err("couldn't create class rc = %d\n", ret);
goto error_class_create;
}
rot_mgr->device = device_create(rot_mgr->class, NULL,
rot_mgr->dev_num, NULL, DRIVER_NAME);
if (IS_ERR(rot_mgr->device)) {
ret = PTR_ERR(rot_mgr->device);
pr_err("device_create failed %d\n", ret);
goto error_class_device_create;
}
cdev_init(&rot_mgr->cdev, &mdss_rotator_fops);
ret = cdev_add(&rot_mgr->cdev,
MKDEV(MAJOR(rot_mgr->dev_num), 0), 1);
if (ret < 0) {
pr_err("cdev_add failed %d\n", ret);
goto error_cdev_add;
}
ret = sysfs_create_group(&rot_mgr->device->kobj,
&mdss_rotator_fs_attr_group);
if (ret)
pr_err("unable to register rotator sysfs nodes\n");
ret = mdss_rotator_res_init(pdev, rot_mgr);
if (ret < 0) {
pr_err("res_init failed %d\n", ret);
goto error_res_init;
}
return 0;
error_res_init:
cdev_del(&rot_mgr->cdev);
error_cdev_add:
device_destroy(rot_mgr->class, rot_mgr->dev_num);
error_class_device_create:
class_destroy(rot_mgr->class);
error_class_create:
unregister_chrdev_region(rot_mgr->dev_num, 1);
error_get_dev_num:
mdss_rotator_deinit_queue(rot_mgr);
error_parse_dt:
devm_kfree(&pdev->dev, rot_mgr);
rot_mgr = NULL;
return ret;
}
static int mdss_rotator_remove(struct platform_device *dev)
{
struct mdss_rot_mgr *mgr;
mgr = (struct mdss_rot_mgr *)platform_get_drvdata(dev);
if (!mgr)
return -ENODEV;
sysfs_remove_group(&rot_mgr->device->kobj, &mdss_rotator_fs_attr_group);
mdss_rotator_release_all(mgr);
mdss_rotator_put_dt_vreg_data(&dev->dev, &mgr->module_power);
mdss_rotator_bus_scale_unregister(mgr);
cdev_del(&rot_mgr->cdev);
device_destroy(rot_mgr->class, rot_mgr->dev_num);
class_destroy(rot_mgr->class);
unregister_chrdev_region(rot_mgr->dev_num, 1);
mdss_rotator_deinit_queue(rot_mgr);
devm_kfree(&dev->dev, rot_mgr);
rot_mgr = NULL;
return 0;
}
static void mdss_rotator_suspend_cancel_rot_work(struct mdss_rot_mgr *mgr)
{
struct mdss_rot_file_private *priv, *priv_next;
mutex_lock(&mgr->file_lock);
list_for_each_entry_safe(priv, priv_next, &mgr->file_list, list) {
mdss_rotator_cancel_all_requests(mgr, priv);
}
mutex_unlock(&rot_mgr->file_lock);
}
#if defined(CONFIG_PM)
static int mdss_rotator_suspend(struct platform_device *dev, pm_message_t state)
{
struct mdss_rot_mgr *mgr;
mgr = (struct mdss_rot_mgr *)platform_get_drvdata(dev);
if (!mgr)
return -ENODEV;
atomic_inc(&mgr->device_suspended);
mdss_rotator_suspend_cancel_rot_work(mgr);
mdss_rotator_update_perf(mgr);
return 0;
}
static int mdss_rotator_resume(struct platform_device *dev)
{
struct mdss_rot_mgr *mgr;
mgr = (struct mdss_rot_mgr *)platform_get_drvdata(dev);
if (!mgr)
return -ENODEV;
atomic_dec(&mgr->device_suspended);
mdss_rotator_update_perf(mgr);
return 0;
}
#endif
static const struct of_device_id mdss_rotator_dt_match[] = {
{ .compatible = "qcom,mdss_rotator",},
{}
};
MODULE_DEVICE_TABLE(of, mdss_rotator_dt_match);
static struct platform_driver mdss_rotator_driver = {
.probe = mdss_rotator_probe,
.remove = mdss_rotator_remove,
#if defined(CONFIG_PM)
.suspend = mdss_rotator_suspend,
.resume = mdss_rotator_resume,
#endif
.driver = {
.name = "mdss_rotator",
.of_match_table = mdss_rotator_dt_match,
.pm = NULL,
}
};
static int __init mdss_rotator_init(void)
{
return platform_driver_register(&mdss_rotator_driver);
}
static void __exit mdss_rotator_exit(void)
{
return platform_driver_unregister(&mdss_rotator_driver);
}
module_init(mdss_rotator_init);
module_exit(mdss_rotator_exit);
MODULE_DESCRIPTION("MSM Rotator driver");
MODULE_LICENSE("GPL v2");