blob: e472e7fb5568b2ce6ad3c455ac31b3e396d641f5 [file] [log] [blame]
/*
* MDSS MDP Interface (used by framebuffer core)
*
* Copyright (c) 2007-2018, The Linux Foundation. All rights reserved.
* Copyright (C) 2007 Google Incorporated
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/rpm-smd-regulator.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
#include <linux/uaccess.h>
#include <linux/clk/msm-clk.h>
#include <linux/irqdomain.h>
#include <linux/irq.h>
#include <linux/msm-bus.h>
#include <linux/msm-bus-board.h>
#include <soc/qcom/scm.h>
#include <soc/qcom/rpm-smd.h>
#include "mdss.h"
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include "mdss_panel.h"
#include "mdss_debug.h"
#include "mdss_mdp_debug.h"
#include "mdss_smmu.h"
#include "mdss_mdp_trace.h"
#define AXI_HALT_TIMEOUT_US 0x4000
#define AUTOSUSPEND_TIMEOUT_MS 200
#define DEFAULT_MDP_PIPE_WIDTH 2048
#define RES_1080p (1088*1920)
#define RES_UHD (3840*2160)
struct mdss_data_type *mdss_res;
static u32 mem_protect_sd_ctrl_id;
static int mdss_fb_mem_get_iommu_domain(void)
{
return mdss_smmu_get_domain_id(MDSS_IOMMU_DOMAIN_UNSECURE);
}
struct msm_mdp_interface mdp5 = {
.init_fnc = mdss_mdp_overlay_init,
.fb_mem_get_iommu_domain = mdss_fb_mem_get_iommu_domain,
.fb_stride = mdss_mdp_fb_stride,
.check_dsi_status = mdss_check_dsi_ctrl_status,
.get_format_params = mdss_mdp_get_format_params,
};
#define IB_QUOTA 2000000000
#define AB_QUOTA 2000000000
#define MAX_AXI_PORT_COUNT 3
#define MEM_PROTECT_SD_CTRL 0xF
#define MEM_PROTECT_SD_CTRL_FLAT 0x14
static DEFINE_SPINLOCK(mdp_lock);
static DEFINE_SPINLOCK(mdss_mdp_intr_lock);
static DEFINE_MUTEX(mdp_clk_lock);
static DEFINE_MUTEX(mdp_iommu_ref_cnt_lock);
static DEFINE_MUTEX(mdp_fs_idle_pc_lock);
static struct mdss_panel_intf pan_types[] = {
{"dsi", MDSS_PANEL_INTF_DSI},
{"edp", MDSS_PANEL_INTF_EDP},
{"hdmi", MDSS_PANEL_INTF_HDMI},
};
static char mdss_mdp_panel[MDSS_MAX_PANEL_LEN];
struct mdss_hw mdss_mdp_hw = {
.hw_ndx = MDSS_HW_MDP,
.ptr = NULL,
.irq_handler = mdss_mdp_isr,
};
/* define for h/w block with external driver */
struct mdss_hw mdss_misc_hw = {
.hw_ndx = MDSS_HW_MISC,
.ptr = NULL,
.irq_handler = NULL,
};
#ifdef CONFIG_QCOM_BUS_SCALING
#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
#define BUS_VOTE_40_MHZ 320000000
#define BUS_VOTE_80_MHZ 640000000
static struct msm_bus_vectors mdp_reg_bus_vectors[] = {
MDP_REG_BUS_VECTOR_ENTRY(0, 0),
MDP_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_19_MHZ),
MDP_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_40_MHZ),
MDP_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_80_MHZ),
};
static struct msm_bus_paths mdp_reg_bus_usecases[ARRAY_SIZE(
mdp_reg_bus_vectors)];
static struct msm_bus_scale_pdata mdp_reg_bus_scale_table = {
.usecase = mdp_reg_bus_usecases,
.num_usecases = ARRAY_SIZE(mdp_reg_bus_usecases),
.name = "mdss_reg",
.active_only = true,
};
#endif
u32 invalid_mdp107_wb_output_fmts[] = {
MDP_XRGB_8888,
MDP_RGBX_8888,
MDP_BGRX_8888,
};
/*
* struct intr_call - array of intr handlers
* @func: intr handler
* @arg: requested argument to the handler
*/
struct intr_callback {
void (*func)(void *);
void *arg;
};
/*
* struct mdss_mdp_intr_reg - array of MDP intr register sets
* @clr_off: offset to CLEAR reg
* @en_off: offset to ENABLE reg
* @status_off: offset to STATUS reg
*/
struct mdss_mdp_intr_reg {
u32 clr_off;
u32 en_off;
u32 status_off;
};
/*
* struct mdss_mdp_irq - maps each irq with i/f
* @intr_type: type of interface
* @intf_num: i/f the irq is associated with
* @irq_mask: corresponding bit in the reg set
* @reg_idx: which reg set to program
*/
struct mdss_mdp_irq {
u32 intr_type;
u32 intf_num;
u32 irq_mask;
u32 reg_idx;
};
static struct mdss_mdp_intr_reg mdp_intr_reg[] = {
{ MDSS_MDP_REG_INTR_CLEAR, MDSS_MDP_REG_INTR_EN,
MDSS_MDP_REG_INTR_STATUS },
{ MDSS_MDP_REG_INTR2_CLEAR, MDSS_MDP_REG_INTR2_EN,
MDSS_MDP_REG_INTR2_STATUS }
};
static struct mdss_mdp_irq mdp_irq_map[] = {
{ MDSS_MDP_IRQ_TYPE_INTF_UNDER_RUN, 1,
MDSS_MDP_INTR_INTF_0_UNDERRUN, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_UNDER_RUN, 2,
MDSS_MDP_INTR_INTF_1_UNDERRUN, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_UNDER_RUN, 3,
MDSS_MDP_INTR_INTF_2_UNDERRUN, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_UNDER_RUN, 4,
MDSS_MDP_INTR_INTF_3_UNDERRUN, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_VSYNC, 1,
MDSS_MDP_INTR_INTF_0_VSYNC, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_VSYNC, 2,
MDSS_MDP_INTR_INTF_1_VSYNC, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_VSYNC, 3,
MDSS_MDP_INTR_INTF_2_VSYNC, 0},
{ MDSS_MDP_IRQ_TYPE_INTF_VSYNC, 4,
MDSS_MDP_INTR_INTF_3_VSYNC, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_COMP, 0,
MDSS_MDP_INTR_PING_PONG_0_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_COMP, 1,
MDSS_MDP_INTR_PING_PONG_1_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_COMP, 2,
MDSS_MDP_INTR_PING_PONG_2_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_COMP, 3,
MDSS_MDP_INTR_PING_PONG_3_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_RD_PTR, 0,
MDSS_MDP_INTR_PING_PONG_0_RD_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_RD_PTR, 1,
MDSS_MDP_INTR_PING_PONG_1_RD_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_RD_PTR, 2,
MDSS_MDP_INTR_PING_PONG_2_RD_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_RD_PTR, 3,
MDSS_MDP_INTR_PING_PONG_3_RD_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_WR_PTR, 0,
MDSS_MDP_INTR_PING_PONG_0_WR_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_WR_PTR, 1,
MDSS_MDP_INTR_PING_PONG_1_WR_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_WR_PTR, 2,
MDSS_MDP_INTR_PING_PONG_2_WR_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_WR_PTR, 3,
MDSS_MDP_INTR_PING_PONG_3_WR_PTR, 0},
{ MDSS_MDP_IRQ_TYPE_WB_ROT_COMP, 0,
MDSS_MDP_INTR_WB_0_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_WB_ROT_COMP, 1,
MDSS_MDP_INTR_WB_1_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_WB_WFD_COMP, 0,
MDSS_MDP_INTR_WB_2_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_AUTO_REF, 0,
MDSS_MDP_INTR_PING_PONG_0_AUTOREFRESH_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_AUTO_REF, 1,
MDSS_MDP_INTR_PING_PONG_1_AUTOREFRESH_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_AUTO_REF, 2,
MDSS_MDP_INTR_PING_PONG_2_AUTOREFRESH_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_PING_PONG_AUTO_REF, 3,
MDSS_MDP_INTR_PING_PONG_3_AUTOREFRESH_DONE, 0},
{ MDSS_MDP_IRQ_TYPE_CWB_OVERFLOW, 2,
MDSS_MDP_INTR2_PING_PONG_2_CWB_OVERFLOW, 1},
{ MDSS_MDP_IRQ_TYPE_CWB_OVERFLOW, 3,
MDSS_MDP_INTR2_PING_PONG_2_CWB_OVERFLOW, 1}
};
static struct intr_callback *mdp_intr_cb;
static void mdss_mdp_footswitch_ctrl(struct mdss_data_type *mdata, int on);
static int mdss_mdp_parse_dt(struct platform_device *pdev);
static int mdss_mdp_parse_dt_pipe(struct platform_device *pdev);
static int mdss_mdp_parse_dt_mixer(struct platform_device *pdev);
static int mdss_mdp_parse_dt_wb(struct platform_device *pdev);
static int mdss_mdp_parse_dt_ctl(struct platform_device *pdev);
static int mdss_mdp_parse_dt_video_intf(struct platform_device *pdev);
static int mdss_mdp_parse_dt_handler(struct platform_device *pdev,
char *prop_name, u32 *offsets, int len);
static int mdss_mdp_parse_dt_prop_len(struct platform_device *pdev,
char *prop_name);
static int mdss_mdp_parse_dt_smp(struct platform_device *pdev);
static int mdss_mdp_parse_dt_prefill(struct platform_device *pdev);
static int mdss_mdp_parse_dt_misc(struct platform_device *pdev);
static int mdss_mdp_parse_dt_ad_cfg(struct platform_device *pdev);
static int mdss_mdp_parse_dt_bus_scale(struct platform_device *pdev);
static int mdss_mdp_parse_dt_ppb_off(struct platform_device *pdev);
static int mdss_mdp_parse_dt_cdm(struct platform_device *pdev);
static int mdss_mdp_parse_dt_dsc(struct platform_device *pdev);
static inline u32 is_mdp_irq_enabled(void)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int i;
for (i = 0; i < ARRAY_SIZE(mdp_intr_reg); i++)
if (mdata->mdp_irq_mask[i] != 0)
return 1;
if (mdata->mdp_hist_irq_mask)
return 1;
if (mdata->mdp_intf_irq_mask)
return 1;
return 0;
}
u32 mdss_mdp_fb_stride(u32 fb_index, u32 xres, int bpp)
{
/* The adreno GPU hardware requires that the pitch be aligned to
* 32 pixels for color buffers, so for the cases where the GPU
* is writing directly to fb0, the framebuffer pitch
* also needs to be 32 pixel aligned
*/
if (fb_index == 0)
return ALIGN(xres, 32) * bpp;
else
return xres * bpp;
}
static void mdss_irq_mask(struct irq_data *data)
{
struct mdss_data_type *mdata = irq_data_get_irq_chip_data(data);
unsigned long irq_flags;
if (!mdata)
return;
pr_debug("irq_domain_mask %lu\n", data->hwirq);
if (data->hwirq < 32) {
spin_lock_irqsave(&mdp_lock, irq_flags);
mdata->mdss_util->disable_irq(&mdss_misc_hw);
spin_unlock_irqrestore(&mdp_lock, irq_flags);
}
}
static void mdss_irq_unmask(struct irq_data *data)
{
struct mdss_data_type *mdata = irq_data_get_irq_chip_data(data);
unsigned long irq_flags;
if (!mdata)
return;
pr_debug("irq_domain_unmask %lu\n", data->hwirq);
if (data->hwirq < 32) {
spin_lock_irqsave(&mdp_lock, irq_flags);
mdata->mdss_util->enable_irq(&mdss_misc_hw);
spin_unlock_irqrestore(&mdp_lock, irq_flags);
}
}
static struct irq_chip mdss_irq_chip = {
.name = "mdss",
.irq_mask = mdss_irq_mask,
.irq_unmask = mdss_irq_unmask,
};
static int mdss_irq_domain_map(struct irq_domain *d,
unsigned int virq, irq_hw_number_t hw)
{
struct mdss_data_type *mdata = d->host_data;
/* check here if virq is a valid interrupt line */
irq_set_chip_and_handler(virq, &mdss_irq_chip, handle_level_irq);
irq_set_chip_data(virq, mdata);
return 0;
}
const struct irq_domain_ops mdss_irq_domain_ops = {
.map = mdss_irq_domain_map,
.xlate = irq_domain_xlate_onecell,
};
static irqreturn_t mdss_irq_handler(int irq, void *ptr)
{
struct mdss_data_type *mdata = ptr;
u32 intr;
if (!mdata)
return IRQ_NONE;
else if (!mdss_get_irq_enable_state(&mdss_mdp_hw))
return IRQ_HANDLED;
intr = MDSS_REG_READ(mdata, MDSS_REG_HW_INTR_STATUS);
mdss_mdp_hw.irq_info->irq_buzy = true;
if (intr & MDSS_INTR_MDP) {
spin_lock(&mdp_lock);
mdata->mdss_util->irq_dispatch(MDSS_HW_MDP, irq, ptr);
spin_unlock(&mdp_lock);
intr &= ~MDSS_INTR_MDP;
}
if (intr & MDSS_INTR_DSI0) {
mdata->mdss_util->irq_dispatch(MDSS_HW_DSI0, irq, ptr);
intr &= ~MDSS_INTR_DSI0;
}
if (intr & MDSS_INTR_DSI1) {
mdata->mdss_util->irq_dispatch(MDSS_HW_DSI1, irq, ptr);
intr &= ~MDSS_INTR_DSI1;
}
if (intr & MDSS_INTR_EDP) {
mdata->mdss_util->irq_dispatch(MDSS_HW_EDP, irq, ptr);
intr &= ~MDSS_INTR_EDP;
}
if (intr & MDSS_INTR_HDMI) {
mdata->mdss_util->irq_dispatch(MDSS_HW_HDMI, irq, ptr);
intr &= ~MDSS_INTR_HDMI;
}
/* route misc. interrupts to external drivers */
while (intr) {
irq_hw_number_t hwirq = fls(intr) - 1;
generic_handle_irq(irq_find_mapping(
mdata->irq_domain, hwirq));
intr &= ~(1 << hwirq);
}
mdss_mdp_hw.irq_info->irq_buzy = false;
return IRQ_HANDLED;
}
#ifdef CONFIG_QCOM_BUS_SCALING
static int mdss_mdp_bus_scale_register(struct mdss_data_type *mdata)
{
struct msm_bus_scale_pdata *reg_bus_pdata;
int i, rc;
if (!mdata->bus_hdl) {
rc = mdss_mdp_parse_dt_bus_scale(mdata->pdev);
if (rc) {
pr_err("Error in device tree : bus scale\n");
return rc;
}
mdata->bus_hdl =
msm_bus_scale_register_client(mdata->bus_scale_table);
if (!mdata->bus_hdl) {
pr_err("bus_client register failed\n");
return -EINVAL;
}
pr_debug("register bus_hdl=%x\n", mdata->bus_hdl);
}
if (!mdata->reg_bus_scale_table) {
reg_bus_pdata = &mdp_reg_bus_scale_table;
for (i = 0; i < reg_bus_pdata->num_usecases; i++) {
mdp_reg_bus_usecases[i].num_paths = 1;
mdp_reg_bus_usecases[i].vectors =
&mdp_reg_bus_vectors[i];
}
mdata->reg_bus_scale_table = reg_bus_pdata;
}
if (!mdata->reg_bus_hdl) {
mdata->reg_bus_hdl =
msm_bus_scale_register_client(
mdata->reg_bus_scale_table);
if (!mdata->reg_bus_hdl)
/* Continue without reg_bus scaling */
pr_warn("reg_bus_client register failed\n");
else
pr_debug("register reg_bus_hdl=%x\n",
mdata->reg_bus_hdl);
}
if (mdata->hw_rt_bus_scale_table && !mdata->hw_rt_bus_hdl) {
mdata->hw_rt_bus_hdl =
msm_bus_scale_register_client(
mdata->hw_rt_bus_scale_table);
if (!mdata->hw_rt_bus_hdl)
/* Continue without reg_bus scaling */
pr_warn("hw_rt_bus client register failed\n");
else
pr_debug("register hw_rt_bus=%x\n",
mdata->hw_rt_bus_hdl);
}
/*
* Following call will not result in actual vote rather update the
* current index and ab/ib value. When continuous splash is enabled,
* actual vote will happen when splash handoff is done.
*/
return mdss_bus_scale_set_quota(MDSS_MDP_RT, AB_QUOTA, IB_QUOTA);
}
static void mdss_mdp_bus_scale_unregister(struct mdss_data_type *mdata)
{
pr_debug("unregister bus_hdl=%x\n", mdata->bus_hdl);
if (mdata->bus_hdl)
msm_bus_scale_unregister_client(mdata->bus_hdl);
pr_debug("unregister reg_bus_hdl=%x\n", mdata->reg_bus_hdl);
if (mdata->reg_bus_hdl) {
msm_bus_scale_unregister_client(mdata->reg_bus_hdl);
mdata->reg_bus_hdl = 0;
}
if (mdata->hw_rt_bus_hdl) {
msm_bus_scale_unregister_client(mdata->hw_rt_bus_hdl);
mdata->hw_rt_bus_hdl = 0;
}
}
/*
* Caller needs to hold mdata->bus_lock lock before calling this function.
*/
static int mdss_mdp_bus_scale_set_quota(u64 ab_quota_rt, u64 ab_quota_nrt,
u64 ib_quota_rt, u64 ib_quota_nrt)
{
int new_uc_idx;
u64 ab_quota[MAX_AXI_PORT_COUNT] = {0, 0};
u64 ib_quota[MAX_AXI_PORT_COUNT] = {0, 0};
int rc;
if (mdss_res->bus_hdl < 1) {
pr_err("invalid bus handle %d\n", mdss_res->bus_hdl);
return -EINVAL;
}
if (!ab_quota_rt && !ab_quota_nrt && !ib_quota_rt && !ib_quota_nrt) {
new_uc_idx = 0;
} else {
int i;
struct msm_bus_vectors *vect = NULL;
struct msm_bus_scale_pdata *bw_table =
mdss_res->bus_scale_table;
u32 nrt_axi_port_cnt = mdss_res->nrt_axi_port_cnt;
u32 total_axi_port_cnt = mdss_res->axi_port_cnt;
u32 rt_axi_port_cnt = total_axi_port_cnt - nrt_axi_port_cnt;
int match_cnt = 0;
if (!bw_table || !total_axi_port_cnt ||
total_axi_port_cnt > MAX_AXI_PORT_COUNT) {
pr_err("invalid input\n");
return -EINVAL;
}
if (mdss_res->bus_channels) {
ib_quota_rt = div_u64(ib_quota_rt,
mdss_res->bus_channels);
ib_quota_nrt = div_u64(ib_quota_nrt,
mdss_res->bus_channels);
}
if (mdss_res->has_fixed_qos_arbiter_enabled ||
nrt_axi_port_cnt) {
ab_quota_rt = div_u64(ab_quota_rt, rt_axi_port_cnt);
ab_quota_nrt = div_u64(ab_quota_nrt, nrt_axi_port_cnt);
for (i = 0; i < total_axi_port_cnt; i++) {
if (i < rt_axi_port_cnt) {
ab_quota[i] = ab_quota_rt;
ib_quota[i] = ib_quota_rt;
} else {
ab_quota[i] = ab_quota_nrt;
ib_quota[i] = ib_quota_nrt;
}
}
} else {
ab_quota[0] = div_u64(ab_quota_rt + ab_quota_nrt,
total_axi_port_cnt);
ib_quota[0] = ib_quota_rt + ib_quota_nrt;
for (i = 1; i < total_axi_port_cnt; i++) {
ab_quota[i] = ab_quota[0];
ib_quota[i] = ib_quota[0];
}
}
for (i = 0; i < total_axi_port_cnt; i++) {
vect = &bw_table->usecase
[mdss_res->curr_bw_uc_idx].vectors[i];
/* avoid performing updates for small changes */
if ((ab_quota[i] == vect->ab) &&
(ib_quota[i] == vect->ib))
match_cnt++;
}
if (match_cnt == total_axi_port_cnt) {
pr_debug("skip BW vote\n");
return 0;
}
new_uc_idx = (mdss_res->curr_bw_uc_idx %
(bw_table->num_usecases - 1)) + 1;
for (i = 0; i < total_axi_port_cnt; i++) {
vect = &bw_table->usecase[new_uc_idx].vectors[i];
vect->ab = ab_quota[i];
vect->ib = ib_quota[i];
pr_debug("uc_idx=%d %s path idx=%d ab=%llu ib=%llu\n",
new_uc_idx, (i < rt_axi_port_cnt) ? "rt" : "nrt"
, i, vect->ab, vect->ib);
}
}
mdss_res->curr_bw_uc_idx = new_uc_idx;
mdss_res->ao_bw_uc_idx = new_uc_idx;
if ((mdss_res->bus_ref_cnt == 0) && mdss_res->curr_bw_uc_idx) {
rc = 0;
} else { /* vote BW if bus_bw_cnt > 0 or uc_idx is zero */
ATRACE_BEGIN("msm_bus_scale_req");
rc = msm_bus_scale_client_update_request(mdss_res->bus_hdl,
new_uc_idx);
ATRACE_END("msm_bus_scale_req");
}
return rc;
}
struct reg_bus_client *mdss_reg_bus_vote_client_create(char *client_name)
{
struct reg_bus_client *client;
static u32 id;
if (client_name == NULL) {
pr_err("client name is null\n");
return ERR_PTR(-EINVAL);
}
client = kcalloc(1, sizeof(struct reg_bus_client), GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
mutex_lock(&mdss_res->reg_bus_lock);
strlcpy(client->name, client_name, MAX_CLIENT_NAME_LEN);
client->usecase_ndx = VOTE_INDEX_DISABLE;
client->id = id;
pr_debug("bus vote client %s created:%pK id :%d\n", client_name,
client, id);
id++;
list_add(&client->list, &mdss_res->reg_bus_clist);
mutex_unlock(&mdss_res->reg_bus_lock);
return client;
}
void mdss_reg_bus_vote_client_destroy(struct reg_bus_client *client)
{
if (!client) {
pr_err("reg bus vote: invalid client handle\n");
} else {
pr_debug("bus vote client %s destroyed:%pK id:%u\n",
client->name, client, client->id);
mutex_lock(&mdss_res->reg_bus_lock);
list_del_init(&client->list);
mutex_unlock(&mdss_res->reg_bus_lock);
kfree(client);
}
}
int mdss_update_reg_bus_vote(struct reg_bus_client *bus_client, u32 usecase_ndx)
{
int ret = 0;
bool changed = false;
u32 max_usecase_ndx = VOTE_INDEX_DISABLE;
struct reg_bus_client *client, *temp_client;
if (!mdss_res || !mdss_res->reg_bus_hdl || !bus_client)
return 0;
mutex_lock(&mdss_res->reg_bus_lock);
bus_client->usecase_ndx = usecase_ndx;
list_for_each_entry_safe(client, temp_client, &mdss_res->reg_bus_clist,
list) {
if (client->usecase_ndx < VOTE_INDEX_MAX &&
client->usecase_ndx > max_usecase_ndx)
max_usecase_ndx = client->usecase_ndx;
}
if (mdss_res->reg_bus_usecase_ndx != max_usecase_ndx) {
changed = true;
mdss_res->reg_bus_usecase_ndx = max_usecase_ndx;
}
pr_debug("%pS: changed=%d current idx=%d request client %s id:%u idx:%d\n",
__builtin_return_address(0), changed, max_usecase_ndx,
bus_client->name, bus_client->id, usecase_ndx);
MDSS_XLOG(changed, max_usecase_ndx, bus_client->id, usecase_ndx);
if (changed)
ret = msm_bus_scale_client_update_request(mdss_res->reg_bus_hdl,
max_usecase_ndx);
mutex_unlock(&mdss_res->reg_bus_lock);
return ret;
}
int mdss_bus_scale_set_quota(int client, u64 ab_quota, u64 ib_quota)
{
int rc = 0;
int i;
u64 total_ab_rt = 0, total_ib_rt = 0;
u64 total_ab_nrt = 0, total_ib_nrt = 0;
mutex_lock(&mdss_res->bus_lock);
mdss_res->ab[client] = ab_quota;
mdss_res->ib[client] = ib_quota;
trace_mdp_perf_update_bus(client, ab_quota, ib_quota);
for (i = 0; i < MDSS_MAX_BUS_CLIENTS; i++) {
if (i == MDSS_MDP_NRT) {
total_ab_nrt = mdss_res->ab[i];
total_ib_nrt = mdss_res->ib[i];
} else {
total_ab_rt += mdss_res->ab[i];
total_ib_rt = max(total_ib_rt, mdss_res->ib[i]);
}
}
rc = mdss_mdp_bus_scale_set_quota(total_ab_rt, total_ab_nrt,
total_ib_rt, total_ib_nrt);
mutex_unlock(&mdss_res->bus_lock);
return rc;
}
#else
static int mdss_mdp_bus_scale_register(struct mdss_data_type *mdata)
{
return 0;
}
static void mdss_mdp_bus_scale_unregister(struct mdss_data_type *mdata)
{
}
int mdss_bus_scale_set_quota(int client, u64 ab_quota, u64 ib_quota)
{
pr_debug("No bus scaling! client=%d ab=%llu ib=%llu\n",
client, ab_quota, ib_quota);
return 0;
}
struct reg_bus_client *mdss_reg_bus_vote_client_create(char *client_name)
{
return NULL;
}
void mdss_reg_bus_vote_client_destroy(struct reg_bus_client *client)
{
}
int mdss_update_reg_bus_vote(struct reg_bus_client *bus_client, u32 usecase_ndx)
{
pr_debug("%pS: No reg scaling! usecase=%u\n",
__builtin_return_address(0), usecase_ndx);
return 0;
}
#endif
static int mdss_mdp_intr2index(u32 intr_type, u32 intf_num)
{
int i;
for (i = 0; i < ARRAY_SIZE(mdp_irq_map); i++) {
if (intr_type == mdp_irq_map[i].intr_type &&
intf_num == mdp_irq_map[i].intf_num)
return i;
}
return -EINVAL;
}
u32 mdss_mdp_get_irq_mask(u32 intr_type, u32 intf_num)
{
int idx = mdss_mdp_intr2index(intr_type, intf_num);
return (idx < 0) ? 0 : mdp_irq_map[idx].irq_mask;
}
void mdss_mdp_enable_hw_irq(struct mdss_data_type *mdata)
{
mdata->mdss_util->enable_irq(&mdss_mdp_hw);
}
void mdss_mdp_disable_hw_irq(struct mdss_data_type *mdata)
{
if (!is_mdp_irq_enabled())
mdata->mdss_util->disable_irq(&mdss_mdp_hw);
}
/* function assumes that mdp is clocked to access hw registers */
void mdss_mdp_irq_clear(struct mdss_data_type *mdata,
u32 intr_type, u32 intf_num)
{
unsigned long irq_flags;
int irq_idx;
struct mdss_mdp_intr_reg reg;
struct mdss_mdp_irq irq;
irq_idx = mdss_mdp_intr2index(intr_type, intf_num);
if (irq_idx < 0) {
pr_err("invalid irq request\n");
return;
}
irq = mdp_irq_map[irq_idx];
reg = mdp_intr_reg[irq.reg_idx];
pr_debug("clearing mdp irq mask=%x\n", irq.irq_mask);
spin_lock_irqsave(&mdp_lock, irq_flags);
writel_relaxed(irq.irq_mask, mdata->mdp_base + reg.clr_off);
spin_unlock_irqrestore(&mdp_lock, irq_flags);
}
int mdss_mdp_irq_enable(u32 intr_type, u32 intf_num)
{
int irq_idx;
unsigned long irq_flags;
int ret = 0;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_mdp_intr_reg reg;
struct mdss_mdp_irq irq;
irq_idx = mdss_mdp_intr2index(intr_type, intf_num);
if (irq_idx < 0) {
pr_err("invalid irq request\n");
return -EINVAL;
}
irq = mdp_irq_map[irq_idx];
reg = mdp_intr_reg[irq.reg_idx];
spin_lock_irqsave(&mdp_lock, irq_flags);
if (mdata->mdp_irq_mask[irq.reg_idx] & irq.irq_mask) {
pr_warn("MDSS MDP IRQ-0x%x is already set, mask=%x\n",
irq.irq_mask, mdata->mdp_irq_mask[irq.reg_idx]);
ret = -EBUSY;
} else {
pr_debug("MDP IRQ mask old=%x new=%x\n",
mdata->mdp_irq_mask[irq.reg_idx], irq.irq_mask);
mdata->mdp_irq_mask[irq.reg_idx] |= irq.irq_mask;
writel_relaxed(irq.irq_mask, mdata->mdp_base + reg.clr_off);
writel_relaxed(mdata->mdp_irq_mask[irq.reg_idx],
mdata->mdp_base + reg.en_off);
mdata->mdss_util->enable_irq(&mdss_mdp_hw);
}
spin_unlock_irqrestore(&mdp_lock, irq_flags);
return ret;
}
int mdss_mdp_hist_irq_enable(u32 irq)
{
int ret = 0;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (mdata->mdp_hist_irq_mask & irq) {
pr_warn("MDSS MDP Hist IRQ-0x%x is already set, mask=%x\n",
irq, mdata->mdp_hist_irq_mask);
ret = -EBUSY;
} else {
pr_debug("mask old=%x new=%x\n",
mdata->mdp_hist_irq_mask, irq);
mdata->mdp_hist_irq_mask |= irq;
writel_relaxed(irq, mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_CLEAR);
writel_relaxed(mdata->mdp_hist_irq_mask, mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_EN);
mdata->mdss_util->enable_irq(&mdss_mdp_hw);
}
return ret;
}
void mdss_mdp_irq_disable(u32 intr_type, u32 intf_num)
{
int irq_idx;
unsigned long irq_flags;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_mdp_intr_reg reg;
struct mdss_mdp_irq irq;
irq_idx = mdss_mdp_intr2index(intr_type, intf_num);
if (irq_idx < 0) {
pr_err("invalid irq request\n");
return;
}
irq = mdp_irq_map[irq_idx];
reg = mdp_intr_reg[irq.reg_idx];
spin_lock_irqsave(&mdp_lock, irq_flags);
if (!(mdata->mdp_irq_mask[irq.reg_idx] & irq.irq_mask)) {
pr_warn("MDSS MDP IRQ-%x is NOT set, mask=%x\n",
irq.irq_mask, mdata->mdp_irq_mask[irq.reg_idx]);
} else {
mdata->mdp_irq_mask[irq.reg_idx] &= ~irq.irq_mask;
writel_relaxed(mdata->mdp_irq_mask[irq.reg_idx],
mdata->mdp_base + reg.en_off);
if (!is_mdp_irq_enabled())
mdata->mdss_util->disable_irq(&mdss_mdp_hw);
}
spin_unlock_irqrestore(&mdp_lock, irq_flags);
}
/* This function is used to check and clear the status of MDP interrupts */
void mdss_mdp_intr_check_and_clear(u32 intr_type, u32 intf_num)
{
u32 status;
int irq_idx;
unsigned long irq_flags;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_mdp_intr_reg reg;
struct mdss_mdp_irq irq;
irq_idx = mdss_mdp_intr2index(intr_type, intf_num);
if (irq_idx < 0) {
pr_err("invalid irq request\n");
return;
}
irq = mdp_irq_map[irq_idx];
reg = mdp_intr_reg[irq.reg_idx];
spin_lock_irqsave(&mdp_lock, irq_flags);
status = irq.irq_mask & readl_relaxed(mdata->mdp_base +
reg.status_off);
if (status) {
pr_debug("clearing irq: intr_type:%d, intf_num:%d\n",
intr_type, intf_num);
writel_relaxed(irq.irq_mask, mdata->mdp_base + reg.clr_off);
}
spin_unlock_irqrestore(&mdp_lock, irq_flags);
}
void mdss_mdp_hist_irq_disable(u32 irq)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (!(mdata->mdp_hist_irq_mask & irq)) {
pr_warn("MDSS MDP IRQ-%x is NOT set, mask=%x\n",
irq, mdata->mdp_hist_irq_mask);
} else {
mdata->mdp_hist_irq_mask &= ~irq;
writel_relaxed(mdata->mdp_hist_irq_mask, mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_EN);
if (!is_mdp_irq_enabled())
mdata->mdss_util->disable_irq(&mdss_mdp_hw);
}
}
/**
* mdss_mdp_irq_disable_nosync() - disable mdp irq
* @intr_type: mdp interface type
* @intf_num: mdp interface num
*
* This function is called from interrupt context
* mdp_lock is already held at up stream (mdss_irq_handler)
* therefore spin_lock(&mdp_lock) is not allowed here
*
*/
void mdss_mdp_irq_disable_nosync(u32 intr_type, u32 intf_num)
{
int irq_idx;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_mdp_intr_reg reg;
struct mdss_mdp_irq irq;
irq_idx = mdss_mdp_intr2index(intr_type, intf_num);
if (irq_idx < 0) {
pr_err("invalid irq request\n");
return;
}
irq = mdp_irq_map[irq_idx];
reg = mdp_intr_reg[irq.reg_idx];
if (!(mdata->mdp_irq_mask[irq.reg_idx] & irq.irq_mask)) {
pr_warn("MDSS MDP IRQ-%x is NOT set, mask=%x\n",
irq.irq_mask, mdata->mdp_irq_mask[irq.reg_idx]);
} else {
mdata->mdp_irq_mask[irq.reg_idx] &= ~irq.irq_mask;
writel_relaxed(mdata->mdp_irq_mask[irq.reg_idx],
mdata->mdp_base + reg.en_off);
if (!is_mdp_irq_enabled())
mdata->mdss_util->disable_irq_nosync(&mdss_mdp_hw);
}
}
int mdss_mdp_set_intr_callback(u32 intr_type, u32 intf_num,
void (*fnc_ptr)(void *), void *arg)
{
unsigned long flags;
int index;
index = mdss_mdp_intr2index(intr_type, intf_num);
if (index < 0) {
pr_warn("invalid intr type=%u intf_numf_num=%u\n",
intr_type, intf_num);
return -EINVAL;
}
spin_lock_irqsave(&mdss_mdp_intr_lock, flags);
WARN(mdp_intr_cb[index].func && fnc_ptr,
"replacing current intr callback for ndx=%d\n", index);
mdp_intr_cb[index].func = fnc_ptr;
mdp_intr_cb[index].arg = arg;
spin_unlock_irqrestore(&mdss_mdp_intr_lock, flags);
return 0;
}
int mdss_mdp_set_intr_callback_nosync(u32 intr_type, u32 intf_num,
void (*fnc_ptr)(void *), void *arg)
{
int index;
index = mdss_mdp_intr2index(intr_type, intf_num);
if (index < 0) {
pr_warn("invalid intr Typee=%u intf_num=%u\n",
intr_type, intf_num);
return -EINVAL;
}
WARN(mdp_intr_cb[index].func && fnc_ptr,
"replacing current intr callbackack for ndx=%d\n",
index);
mdp_intr_cb[index].func = fnc_ptr;
mdp_intr_cb[index].arg = arg;
return 0;
}
static inline void mdss_mdp_intr_done(int index)
{
void (*fnc)(void *);
void *arg;
spin_lock(&mdss_mdp_intr_lock);
fnc = mdp_intr_cb[index].func;
arg = mdp_intr_cb[index].arg;
spin_unlock(&mdss_mdp_intr_lock);
if (fnc)
fnc(arg);
}
irqreturn_t mdss_mdp_isr(int irq, void *ptr)
{
struct mdss_data_type *mdata = ptr;
u32 isr, mask, hist_isr, hist_mask;
int i, j;
if (!mdata->clk_ena)
return IRQ_HANDLED;
for (i = 0; i < ARRAY_SIZE(mdp_intr_reg); i++) {
struct mdss_mdp_intr_reg reg = mdp_intr_reg[i];
isr = readl_relaxed(mdata->mdp_base + reg.status_off);
if (isr == 0)
continue;
mask = readl_relaxed(mdata->mdp_base + reg.en_off);
writel_relaxed(isr, mdata->mdp_base + reg.clr_off);
pr_debug("%s: reg:%d isr=%x mask=%x\n",
__func__, i+1, isr, mask);
isr &= mask;
if (isr == 0)
continue;
for (j = 0; j < ARRAY_SIZE(mdp_irq_map); j++)
if (mdp_irq_map[j].reg_idx == i &&
(isr & mdp_irq_map[j].irq_mask))
mdss_mdp_intr_done(j);
if (!i) {
if (isr & MDSS_MDP_INTR_PING_PONG_0_DONE)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI0,
false);
if (isr & MDSS_MDP_INTR_PING_PONG_1_DONE)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI1,
false);
if (isr & MDSS_MDP_INTR_INTF_0_VSYNC)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_EDP,
true);
if (isr & MDSS_MDP_INTR_INTF_1_VSYNC)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI0,
true);
if (isr & MDSS_MDP_INTR_INTF_2_VSYNC)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI1,
true);
if (isr & MDSS_MDP_INTR_INTF_3_VSYNC)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_HDMI,
true);
if (isr & MDSS_MDP_INTR_WB_0_DONE)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP,
true);
if (isr & MDSS_MDP_INTR_WB_1_DONE)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP,
true);
if (isr & MDSS_MDP_INTR_WB_2_DONE)
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP,
true);
}
}
hist_isr = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_STATUS);
if (hist_isr != 0) {
hist_mask = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_EN);
writel_relaxed(hist_isr, mdata->mdp_base +
MDSS_MDP_REG_HIST_INTR_CLEAR);
hist_isr &= hist_mask;
if (hist_isr != 0)
mdss_mdp_hist_intr_done(hist_isr);
}
mdss_mdp_video_isr(mdata->video_intf, mdata->nintf);
return IRQ_HANDLED;
}
static int mdss_mdp_clk_update(u32 clk_idx, u32 enable)
{
int ret = -ENODEV;
struct clk *clk = mdss_mdp_get_clk(clk_idx);
if (clk) {
pr_debug("clk=%d en=%d\n", clk_idx, enable);
if (enable) {
if (clk_idx == MDSS_CLK_MDP_VSYNC)
clk_set_rate(clk, 19200000);
ret = clk_prepare_enable(clk);
} else {
clk_disable_unprepare(clk);
ret = 0;
}
}
return ret;
}
int mdss_mdp_vsync_clk_enable(int enable, bool locked)
{
int ret = 0;
pr_debug("clk enable=%d\n", enable);
if (!locked)
mutex_lock(&mdp_clk_lock);
if (mdss_res->vsync_ena != enable) {
mdss_res->vsync_ena = enable;
ret = mdss_mdp_clk_update(MDSS_CLK_MDP_VSYNC, enable);
}
if (!locked)
mutex_unlock(&mdp_clk_lock);
return ret;
}
void mdss_mdp_set_clk_rate(unsigned long rate)
{
struct mdss_data_type *mdata = mdss_res;
unsigned long clk_rate;
struct clk *clk = mdss_mdp_get_clk(MDSS_CLK_MDP_CORE);
unsigned long min_clk_rate;
min_clk_rate = max(rate, mdata->perf_tune.min_mdp_clk);
if (clk) {
mutex_lock(&mdp_clk_lock);
if (min_clk_rate < mdata->max_mdp_clk_rate)
clk_rate = clk_round_rate(clk, min_clk_rate);
else
clk_rate = mdata->max_mdp_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)) {
if (IS_ERR_VALUE((unsigned long)
clk_set_rate(clk, clk_rate)))
pr_err("clk_set_rate failed\n");
else
pr_debug("mdp clk rate=%lu\n", clk_rate);
}
mutex_unlock(&mdp_clk_lock);
} else {
pr_err("mdp src clk not setup properly\n");
}
}
unsigned long mdss_mdp_get_clk_rate(u32 clk_idx, bool locked)
{
unsigned long clk_rate = 0;
struct clk *clk = mdss_mdp_get_clk(clk_idx);
if (clk) {
if (!locked)
mutex_lock(&mdp_clk_lock);
clk_rate = clk_get_rate(clk);
if (!locked)
mutex_unlock(&mdp_clk_lock);
}
return clk_rate;
}
/**
* mdss_bus_rt_bw_vote() -- place bus bandwidth request
* @enable: value of enable or disable
*
* hw_rt table has two entries, 0 and Min Vote (1Mhz)
* while attaching SMMU and for few TZ operations which
* happen at very early stage, we will request Min Vote
* thru this handle.
*
*/
static int mdss_bus_rt_bw_vote(bool enable)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int rc = 0;
bool changed = false;
if (!mdata->hw_rt_bus_hdl || mdata->handoff_pending)
return 0;
if (enable) {
if (mdata->hw_rt_bus_ref_cnt == 0)
changed = true;
mdata->hw_rt_bus_ref_cnt++;
} else {
if (mdata->hw_rt_bus_ref_cnt != 0) {
mdata->hw_rt_bus_ref_cnt--;
if (mdata->hw_rt_bus_ref_cnt == 0)
changed = true;
} else {
pr_warn("%s: bus bw votes are not balanced\n",
__func__);
}
}
pr_debug("%pS: task:%s bw_cnt=%d changed=%d enable=%d\n",
__builtin_return_address(0), current->group_leader->comm,
mdata->hw_rt_bus_ref_cnt, changed, enable);
if (changed) {
rc = msm_bus_scale_client_update_request(mdata->hw_rt_bus_hdl,
enable ? 1 : 0);
if (rc)
pr_err("%s: Bus bandwidth vote failed\n", __func__);
}
return rc;
}
/**
* __mdss_mdp_reg_access_clk_enable - Enable minimum MDSS clocks required
* for register access
*/
static inline void __mdss_mdp_reg_access_clk_enable(
struct mdss_data_type *mdata, bool enable)
{
if (enable) {
mdss_update_reg_bus_vote(mdata->reg_bus_clt,
VOTE_INDEX_LOW);
mdss_bus_rt_bw_vote(true);
mdss_mdp_clk_update(MDSS_CLK_MNOC_AHB, 1);
mdss_mdp_clk_update(MDSS_CLK_AHB, 1);
mdss_mdp_clk_update(MDSS_CLK_AXI, 1);
mdss_mdp_clk_update(MDSS_CLK_MDP_CORE, 1);
} else {
mdss_mdp_clk_update(MDSS_CLK_MDP_CORE, 0);
mdss_mdp_clk_update(MDSS_CLK_AXI, 0);
mdss_mdp_clk_update(MDSS_CLK_AHB, 0);
mdss_bus_rt_bw_vote(false);
mdss_mdp_clk_update(MDSS_CLK_MNOC_AHB, 0);
mdss_update_reg_bus_vote(mdata->reg_bus_clt,
VOTE_INDEX_DISABLE);
}
}
int __mdss_mdp_vbif_halt(struct mdss_data_type *mdata, bool is_nrt)
{
int rc = 0;
void __iomem *base;
u32 halt_ack_mask = BIT(0), status;
/* if not real time vbif */
if (is_nrt)
base = mdata->vbif_nrt_io.base;
else
base = mdata->vbif_io.base;
if (!base) {
/* some targets might not have a nrt port */
goto vbif_done;
}
/* force vbif clock on */
MDSS_VBIF_WRITE(mdata, MMSS_VBIF_CLKON, 1, is_nrt);
/* request halt */
MDSS_VBIF_WRITE(mdata, MMSS_VBIF_AXI_HALT_CTRL0, 1, is_nrt);
rc = readl_poll_timeout(base +
MMSS_VBIF_AXI_HALT_CTRL1, status, (status &
halt_ack_mask),
1000, AXI_HALT_TIMEOUT_US);
if (rc == -ETIMEDOUT) {
pr_err("VBIF axi is not halting. TIMEDOUT.\n");
goto vbif_done;
}
pr_debug("VBIF axi is halted\n");
vbif_done:
return rc;
}
/**
* mdss_mdp_vbif_axi_halt() - Halt MDSS AXI ports
* @mdata: pointer to the global mdss data structure.
*
* This function can be called during deep suspend, display off or for
* debugging purposes. On success it should be assumed that AXI ports connected
* to RT VBIF are in idle state and would not fetch any more data.
*/
static void mdss_mdp_vbif_axi_halt(struct mdss_data_type *mdata)
{
__mdss_mdp_reg_access_clk_enable(mdata, true);
/* real time ports */
__mdss_mdp_vbif_halt(mdata, false);
/* non-real time ports */
__mdss_mdp_vbif_halt(mdata, true);
__mdss_mdp_reg_access_clk_enable(mdata, false);
}
int mdss_iommu_ctrl(int enable)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int rc = 0;
mutex_lock(&mdp_iommu_ref_cnt_lock);
pr_debug("%pS: enable:%d ref_cnt:%d attach:%d hoff:%d\n",
__builtin_return_address(0), enable, mdata->iommu_ref_cnt,
mdata->iommu_attached, mdata->handoff_pending);
if (enable) {
/*
* delay iommu attach until continuous splash screen has
* finished handoff, as it may still be working with phys addr
*/
if (!mdata->iommu_attached && !mdata->handoff_pending) {
mdss_bus_rt_bw_vote(true);
rc = mdss_smmu_attach(mdata);
}
mdata->iommu_ref_cnt++;
} else {
if (mdata->iommu_ref_cnt) {
mdata->iommu_ref_cnt--;
if (mdata->iommu_ref_cnt == 0) {
rc = mdss_smmu_detach(mdata);
mdss_bus_rt_bw_vote(false);
}
} else {
pr_err("unbalanced iommu ref\n");
}
}
mutex_unlock(&mdp_iommu_ref_cnt_lock);
if (IS_ERR_VALUE((unsigned long)rc))
return rc;
else
return mdata->iommu_ref_cnt;
}
static void mdss_mdp_memory_retention_enter(void)
{
struct clk *mdss_mdp_clk = NULL;
struct clk *mdp_vote_clk = mdss_mdp_get_clk(MDSS_CLK_MDP_CORE);
if (mdp_vote_clk) {
mdss_mdp_clk = clk_get_parent(mdp_vote_clk);
if (mdss_mdp_clk) {
clk_set_flags(mdss_mdp_clk, CLKFLAG_RETAIN_MEM);
clk_set_flags(mdss_mdp_clk, CLKFLAG_PERIPH_OFF_SET);
clk_set_flags(mdss_mdp_clk, CLKFLAG_NORETAIN_PERIPH);
}
}
}
static void mdss_mdp_memory_retention_exit(void)
{
struct clk *mdss_mdp_clk = NULL;
struct clk *mdp_vote_clk = mdss_mdp_get_clk(MDSS_CLK_MDP_CORE);
if (mdp_vote_clk) {
mdss_mdp_clk = clk_get_parent(mdp_vote_clk);
if (mdss_mdp_clk) {
clk_set_flags(mdss_mdp_clk, CLKFLAG_RETAIN_MEM);
clk_set_flags(mdss_mdp_clk, CLKFLAG_RETAIN_PERIPH);
clk_set_flags(mdss_mdp_clk, CLKFLAG_PERIPH_OFF_CLEAR);
}
}
}
/**
* mdss_mdp_idle_pc_restore() - Restore MDSS settings when exiting idle pc
*
* MDSS GDSC can be voted off during idle-screen usecase for MIPI DSI command
* mode displays, referred to as MDSS idle power collapse. Upon subsequent
* frame update, MDSS GDSC needs to turned back on and hw state needs to be
* restored.
*/
static int mdss_mdp_idle_pc_restore(void)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int rc = 0;
mutex_lock(&mdp_fs_idle_pc_lock);
if (!mdata->idle_pc) {
pr_debug("no idle pc, no need to restore\n");
goto end;
}
pr_debug("called from %pS\n", __builtin_return_address(0));
rc = mdss_iommu_ctrl(1);
if (IS_ERR_VALUE((unsigned long)rc)) {
pr_err("mdss iommu attach failed rc=%d\n", rc);
goto end;
}
mdss_hw_init(mdata);
mdss_iommu_ctrl(0);
/**
* sleep 10 microseconds to make sure AD auto-reinitialization
* is done
*/
udelay(10);
mdss_mdp_memory_retention_exit();
mdss_mdp_ctl_restore(true);
mdata->idle_pc = false;
end:
mutex_unlock(&mdp_fs_idle_pc_lock);
return rc;
}
/**
* mdss_bus_bandwidth_ctrl() -- place bus bandwidth request
* @enable: value of enable or disable
*
* Function place bus bandwidth request to allocate saved bandwidth
* if enabled or free bus bandwidth allocation if disabled.
* Bus bandwidth is required by mdp.For dsi, it only requires to send
* dcs coammnd. It returns error if bandwidth request fails.
*/
void mdss_bus_bandwidth_ctrl(int enable)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int changed = 0;
mutex_lock(&mdata->bus_lock);
if (enable) {
if (mdata->bus_ref_cnt == 0)
changed++;
mdata->bus_ref_cnt++;
} else {
if (mdata->bus_ref_cnt) {
mdata->bus_ref_cnt--;
if (mdata->bus_ref_cnt == 0)
changed++;
} else {
pr_err("Can not be turned off\n");
}
}
pr_debug("%pS: task:%s bw_cnt=%d changed=%d enable=%d\n",
__builtin_return_address(0), current->group_leader->comm,
mdata->bus_ref_cnt, changed, enable);
if (changed) {
MDSS_XLOG(mdata->bus_ref_cnt, enable);
if (!enable) {
if (!mdata->handoff_pending) {
msm_bus_scale_client_update_request(
mdata->bus_hdl, 0);
mdata->ao_bw_uc_idx = 0;
}
pm_runtime_mark_last_busy(&mdata->pdev->dev);
pm_runtime_put_autosuspend(&mdata->pdev->dev);
} else {
pm_runtime_get_sync(&mdata->pdev->dev);
msm_bus_scale_client_update_request(
mdata->bus_hdl, mdata->curr_bw_uc_idx);
}
}
mutex_unlock(&mdata->bus_lock);
}
EXPORT_SYMBOL(mdss_bus_bandwidth_ctrl);
void mdss_mdp_clk_ctrl(int enable)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
static int mdp_clk_cnt;
unsigned long flags;
int changed = 0;
int rc = 0;
mutex_lock(&mdp_clk_lock);
if (enable) {
if (mdp_clk_cnt == 0)
changed++;
mdp_clk_cnt++;
} else {
if (mdp_clk_cnt) {
mdp_clk_cnt--;
if (mdp_clk_cnt == 0)
changed++;
} else {
pr_err("Can not be turned off\n");
}
}
if (changed)
MDSS_XLOG(mdp_clk_cnt, enable, current->pid);
pr_debug("%pS: task:%s clk_cnt=%d changed=%d enable=%d\n",
__builtin_return_address(0), current->group_leader->comm,
mdata->bus_ref_cnt, changed, enable);
if (changed) {
if (enable) {
pm_runtime_get_sync(&mdata->pdev->dev);
mdss_update_reg_bus_vote(mdata->reg_bus_clt,
VOTE_INDEX_LOW);
rc = mdss_iommu_ctrl(1);
if (IS_ERR_VALUE((unsigned long)rc))
pr_err("IOMMU attach failed\n");
/* Active+Sleep */
msm_bus_scale_client_update_context(mdata->bus_hdl,
false, mdata->curr_bw_uc_idx);
}
spin_lock_irqsave(&mdp_lock, flags);
mdata->clk_ena = enable;
spin_unlock_irqrestore(&mdp_lock, flags);
mdss_mdp_clk_update(MDSS_CLK_MNOC_AHB, enable);
mdss_mdp_clk_update(MDSS_CLK_AHB, enable);
mdss_mdp_clk_update(MDSS_CLK_AXI, enable);
mdss_mdp_clk_update(MDSS_CLK_MDP_CORE, enable);
mdss_mdp_clk_update(MDSS_CLK_MDP_LUT, enable);
if (mdata->vsync_ena)
mdss_mdp_clk_update(MDSS_CLK_MDP_VSYNC, enable);
if (!enable) {
/* release iommu control */
mdss_iommu_ctrl(0);
/* Active-Only */
msm_bus_scale_client_update_context(mdata->bus_hdl,
true, mdata->ao_bw_uc_idx);
mdss_update_reg_bus_vote(mdata->reg_bus_clt,
VOTE_INDEX_DISABLE);
pm_runtime_mark_last_busy(&mdata->pdev->dev);
pm_runtime_put_autosuspend(&mdata->pdev->dev);
}
}
if (enable && changed)
mdss_mdp_idle_pc_restore();
mutex_unlock(&mdp_clk_lock);
}
static inline int mdss_mdp_irq_clk_register(struct mdss_data_type *mdata,
char *clk_name, int clk_idx)
{
struct clk *tmp;
if (clk_idx >= MDSS_MAX_CLK) {
pr_err("invalid clk index %d\n", clk_idx);
return -EINVAL;
}
tmp = devm_clk_get(&mdata->pdev->dev, clk_name);
if (IS_ERR(tmp)) {
pr_err("unable to get clk: %s\n", clk_name);
return PTR_ERR(tmp);
}
mdata->mdp_clk[clk_idx] = tmp;
return 0;
}
#define SEC_DEVICE_MDSS 1
static void __mdss_restore_sec_cfg(struct mdss_data_type *mdata)
{
int ret, scm_ret = 0;
if (test_bit(MDSS_CAPS_SCM_RESTORE_NOT_REQUIRED, mdata->mdss_caps_map))
return;
pr_debug("restoring mdss secure config\n");
__mdss_mdp_reg_access_clk_enable(mdata, true);
ret = scm_restore_sec_cfg(SEC_DEVICE_MDSS, 0, &scm_ret);
if (ret || scm_ret)
pr_warn("scm_restore_sec_cfg failed %d %d\n",
ret, scm_ret);
__mdss_mdp_reg_access_clk_enable(mdata, false);
}
static int mdss_mdp_gdsc_notifier_call(struct notifier_block *self,
unsigned long event, void *data)
{
struct mdss_data_type *mdata;
mdata = container_of(self, struct mdss_data_type, gdsc_cb);
if (event & REGULATOR_EVENT_ENABLE) {
/*
* As SMMU in low tier targets is not power collapsible,
* hence we don't need to restore sec configuration.
*/
if (!mdss_mdp_req_init_restore_cfg(mdata))
__mdss_restore_sec_cfg(mdata);
} else if (event & REGULATOR_EVENT_PRE_DISABLE) {
pr_debug("mdss gdsc is getting disabled\n");
/* halt the vbif transactions */
mdss_mdp_vbif_axi_halt(mdata);
}
return NOTIFY_OK;
}
static int mdss_mdp_irq_clk_setup(struct mdss_data_type *mdata)
{
int ret;
ret = of_property_read_u32(mdata->pdev->dev.of_node,
"qcom,max-clk-rate", &mdata->max_mdp_clk_rate);
if (ret) {
pr_err("failed to get max mdp clock rate\n");
return ret;
}
pr_debug("max mdp clk rate=%d\n", mdata->max_mdp_clk_rate);
ret = devm_request_irq(&mdata->pdev->dev, mdss_mdp_hw.irq_info->irq,
mdss_irq_handler, 0, "MDSS", mdata);
if (ret) {
pr_err("mdp request_irq() failed!\n");
return ret;
}
disable_irq(mdss_mdp_hw.irq_info->irq);
mdata->fs = devm_regulator_get(&mdata->pdev->dev, "vdd");
if (IS_ERR_OR_NULL(mdata->fs)) {
mdata->fs = NULL;
pr_err("unable to get gdsc regulator\n");
return -EINVAL;
}
mdata->venus = devm_regulator_get_optional(&mdata->pdev->dev,
"gdsc-venus");
if (IS_ERR_OR_NULL(mdata->venus)) {
mdata->venus = NULL;
pr_debug("unable to get venus gdsc regulator\n");
}
mdata->fs_ena = false;
mdata->gdsc_cb.notifier_call = mdss_mdp_gdsc_notifier_call;
mdata->gdsc_cb.priority = 5;
if (regulator_register_notifier(mdata->fs, &(mdata->gdsc_cb)))
pr_warn("GDSC notification registration failed!\n");
else
mdata->regulator_notif_register = true;
mdata->vdd_cx = devm_regulator_get_optional(&mdata->pdev->dev,
"vdd-cx");
if (IS_ERR_OR_NULL(mdata->vdd_cx)) {
pr_debug("unable to get CX reg. rc=%d\n",
PTR_RET(mdata->vdd_cx));
mdata->vdd_cx = NULL;
}
mdata->reg_bus_clt = mdss_reg_bus_vote_client_create("mdp\0");
if (IS_ERR(mdata->reg_bus_clt)) {
pr_err("bus client register failed\n");
return PTR_ERR(mdata->reg_bus_clt);
}
if (mdss_mdp_irq_clk_register(mdata, "bus_clk", MDSS_CLK_AXI) ||
mdss_mdp_irq_clk_register(mdata, "iface_clk", MDSS_CLK_AHB) ||
mdss_mdp_irq_clk_register(mdata, "core_clk",
MDSS_CLK_MDP_CORE))
return -EINVAL;
/* lut_clk is not present on all MDSS revisions */
mdss_mdp_irq_clk_register(mdata, "lut_clk", MDSS_CLK_MDP_LUT);
/* vsync_clk is optional for non-smart panels */
mdss_mdp_irq_clk_register(mdata, "vsync_clk", MDSS_CLK_MDP_VSYNC);
/* this clk is not present on all MDSS revisions */
mdss_mdp_irq_clk_register(mdata, "mnoc_clk", MDSS_CLK_MNOC_AHB);
/* Setting the default clock rate to the max supported.*/
mdss_mdp_set_clk_rate(mdata->max_mdp_clk_rate);
pr_debug("mdp clk rate=%ld\n",
mdss_mdp_get_clk_rate(MDSS_CLK_MDP_CORE, false));
return 0;
}
static void mdss_debug_enable_clock(int on)
{
if (on)
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
else
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
}
static int mdss_mdp_debug_init(struct platform_device *pdev,
struct mdss_data_type *mdata)
{
int rc;
struct mdss_debug_base *dbg_blk;
mdata->debug_inf.debug_enable_clock = mdss_debug_enable_clock;
rc = mdss_debugfs_init(mdata);
if (rc)
return rc;
rc = mdss_mdp_debugfs_init(mdata);
if (rc) {
mdss_debugfs_remove(mdata);
return rc;
}
mdss_debug_register_io("mdp", &mdata->mdss_io, &dbg_blk);
mdss_debug_register_dump_range(pdev, dbg_blk, "qcom,regs-dump-mdp",
"qcom,regs-dump-names-mdp", "qcom,regs-dump-xin-id-mdp");
if (mdata->vbif_io.base)
mdss_debug_register_io("vbif", &mdata->vbif_io, NULL);
if (mdata->vbif_nrt_io.base)
mdss_debug_register_io("vbif_nrt", &mdata->vbif_nrt_io, NULL);
return 0;
}
static u32 mdss_get_props(void)
{
u32 props = 0;
void __iomem *props_base = ioremap(0xFC4B8114, 4);
if (props_base) {
props = readl_relaxed(props_base);
iounmap(props_base);
}
return props;
}
void mdss_mdp_init_default_prefill_factors(struct mdss_data_type *mdata)
{
mdata->prefill_data.prefill_factors.fmt_mt_nv12_factor = 8;
mdata->prefill_data.prefill_factors.fmt_mt_factor = 4;
mdata->prefill_data.prefill_factors.fmt_linear_factor = 1;
mdata->prefill_data.prefill_factors.scale_factor = 1;
mdata->prefill_data.prefill_factors.xtra_ff_factor = 2;
if (test_bit(MDSS_QOS_TS_PREFILL, mdata->mdss_qos_map)) {
mdata->prefill_data.ts_threshold = 25;
mdata->prefill_data.ts_end = 8;
mdata->prefill_data.ts_rate.numer = 1;
mdata->prefill_data.ts_rate.denom = 4;
mdata->prefill_data.ts_overhead = 2;
}
}
static void mdss_mdp_hw_rev_caps_init(struct mdss_data_type *mdata)
{
mdata->per_pipe_ib_factor.numer = 0;
mdata->per_pipe_ib_factor.denom = 0;
mdata->apply_post_scale_bytes = true;
mdata->hflip_buffer_reused = true;
/* prevent disable of prefill calculations */
mdata->min_prefill_lines = 0xffff;
/* clock gating feature is disabled by default */
mdata->enable_gate = false;
mdata->pixel_ram_size = 0;
mem_protect_sd_ctrl_id = MEM_PROTECT_SD_CTRL_FLAT;
mdss_mdp_hw_rev_debug_caps_init(mdata);
switch (mdata->mdp_rev) {
case MDSS_MDP_HW_REV_107:
mdss_set_quirk(mdata, MDSS_QUIRK_ROTCDP);
case MDSS_MDP_HW_REV_107_1:
mdss_mdp_format_flag_removal(invalid_mdp107_wb_output_fmts,
ARRAY_SIZE(invalid_mdp107_wb_output_fmts),
VALID_MDP_WB_INTF_FORMAT);
/* fall-through */
case MDSS_MDP_HW_REV_107_2:
mdata->max_target_zorder = 7; /* excluding base layer */
mdata->max_cursor_size = 128;
mdata->per_pipe_ib_factor.numer = 8;
mdata->per_pipe_ib_factor.denom = 5;
mdata->apply_post_scale_bytes = false;
mdata->hflip_buffer_reused = false;
mdata->min_prefill_lines = 21;
mdata->has_ubwc = true;
mdata->pixel_ram_size = 50 * 1024;
set_bit(MDSS_QOS_PER_PIPE_IB, mdata->mdss_qos_map);
set_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map);
set_bit(MDSS_QOS_CDP, mdata->mdss_qos_map);
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
set_bit(MDSS_QOS_PER_PIPE_LUT, mdata->mdss_qos_map);
set_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_CAPS_YUV_CONFIG, mdata->mdss_caps_map);
set_bit(MDSS_CAPS_SCM_RESTORE_NOT_REQUIRED,
mdata->mdss_caps_map);
set_bit(MDSS_CAPS_3D_MUX_UNDERRUN_RECOVERY_SUPPORTED,
mdata->mdss_caps_map);
mdss_mdp_init_default_prefill_factors(mdata);
mdss_set_quirk(mdata, MDSS_QUIRK_DSC_RIGHT_ONLY_PU);
mdss_set_quirk(mdata, MDSS_QUIRK_DSC_2SLICE_PU_THRPUT);
mdss_set_quirk(mdata, MDSS_QUIRK_HDR_SUPPORT_ENABLED);
break;
case MDSS_MDP_HW_REV_105:
case MDSS_MDP_HW_REV_109:
mdss_set_quirk(mdata, MDSS_QUIRK_BWCPANIC);
mdata->max_target_zorder = 7; /* excluding base layer */
mdata->max_cursor_size = 128;
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
set_bit(MDSS_CAPS_3D_MUX_UNDERRUN_RECOVERY_SUPPORTED,
mdata->mdss_caps_map);
break;
case MDSS_MDP_HW_REV_110:
mdss_set_quirk(mdata, MDSS_QUIRK_BWCPANIC);
mdata->max_target_zorder = 4; /* excluding base layer */
mdata->max_cursor_size = 128;
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
mdata->min_prefill_lines = 12;
mdata->props = mdss_get_props();
break;
case MDSS_MDP_HW_REV_112:
mdata->max_target_zorder = 4; /* excluding base layer */
mdata->max_cursor_size = 64;
mdata->min_prefill_lines = 12;
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
break;
case MDSS_MDP_HW_REV_114:
/* disable ECG for 28nm PHY platform */
mdata->enable_gate = false;
case MDSS_MDP_HW_REV_116:
mdata->max_target_zorder = 4; /* excluding base layer */
mdata->max_cursor_size = 128;
mdata->min_prefill_lines = 14;
mdata->has_ubwc = true;
mdata->pixel_ram_size = 40 * 1024;
mdata->apply_post_scale_bytes = false;
mdata->hflip_buffer_reused = false;
mem_protect_sd_ctrl_id = MEM_PROTECT_SD_CTRL;
set_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map);
set_bit(MDSS_QOS_PER_PIPE_LUT, mdata->mdss_qos_map);
set_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_CAPS_YUV_CONFIG, mdata->mdss_caps_map);
mdss_mdp_init_default_prefill_factors(mdata);
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
mdss_set_quirk(mdata, MDSS_QUIRK_DMA_BI_DIR);
mdss_set_quirk(mdata, MDSS_QUIRK_NEED_SECURE_MAP);
break;
case MDSS_MDP_HW_REV_115:
mdata->max_target_zorder = 4; /* excluding base layer */
mdata->max_cursor_size = 128;
mdata->min_prefill_lines = 14;
mdata->has_ubwc = false;
mdata->pixel_ram_size = 16 * 1024;
mdata->apply_post_scale_bytes = false;
mdata->hflip_buffer_reused = false;
/* disable ECG for 28nm PHY platform */
mdata->enable_gate = false;
mem_protect_sd_ctrl_id = MEM_PROTECT_SD_CTRL;
set_bit(MDSS_QOS_PER_PIPE_LUT, mdata->mdss_qos_map);
set_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_CAPS_YUV_CONFIG, mdata->mdss_caps_map);
set_bit(MDSS_CAPS_MIXER_1_FOR_WB, mdata->mdss_caps_map);
mdss_mdp_init_default_prefill_factors(mdata);
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
mdss_set_quirk(mdata, MDSS_QUIRK_DMA_BI_DIR);
mdss_set_quirk(mdata, MDSS_QUIRK_NEED_SECURE_MAP);
break;
case MDSS_MDP_HW_REV_300:
case MDSS_MDP_HW_REV_301:
mdata->max_target_zorder = 7; /* excluding base layer */
mdata->max_cursor_size = 384;
mdata->per_pipe_ib_factor.numer = 8;
mdata->per_pipe_ib_factor.denom = 5;
mdata->apply_post_scale_bytes = false;
mdata->hflip_buffer_reused = false;
mdata->min_prefill_lines = 25;
mdata->has_ubwc = true;
mdata->pixel_ram_size = 50 * 1024;
mdata->rects_per_sspp[MDSS_MDP_PIPE_TYPE_DMA] = 2;
set_bit(MDSS_QOS_PER_PIPE_IB, mdata->mdss_qos_map);
set_bit(MDSS_QOS_TS_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map);
set_bit(MDSS_QOS_CDP, mdata->mdss_qos_map);
set_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map);
set_bit(MDSS_QOS_PER_PIPE_LUT, mdata->mdss_qos_map);
set_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_QOS_TS_PREFILL, mdata->mdss_qos_map);
set_bit(MDSS_QOS_IB_NOCR, mdata->mdss_qos_map);
set_bit(MDSS_CAPS_YUV_CONFIG, mdata->mdss_caps_map);
set_bit(MDSS_CAPS_SCM_RESTORE_NOT_REQUIRED,
mdata->mdss_caps_map);
set_bit(MDSS_CAPS_3D_MUX_UNDERRUN_RECOVERY_SUPPORTED,
mdata->mdss_caps_map);
set_bit(MDSS_CAPS_QSEED3, mdata->mdss_caps_map);
set_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map);
mdss_mdp_init_default_prefill_factors(mdata);
mdss_set_quirk(mdata, MDSS_QUIRK_DSC_RIGHT_ONLY_PU);
mdss_set_quirk(mdata, MDSS_QUIRK_DSC_2SLICE_PU_THRPUT);
mdss_set_quirk(mdata, MDSS_QUIRK_SRC_SPLIT_ALWAYS);
mdata->has_wb_ubwc = true;
set_bit(MDSS_CAPS_10_BIT_SUPPORTED, mdata->mdss_caps_map);
break;
default:
mdata->max_target_zorder = 4; /* excluding base layer */
mdata->max_cursor_size = 64;
}
if (mdata->mdp_rev < MDSS_MDP_HW_REV_103)
mdss_set_quirk(mdata, MDSS_QUIRK_DOWNSCALE_HANG);
if (mdata->mdp_rev < MDSS_MDP_HW_REV_102 ||
mdata->mdp_rev == MDSS_MDP_HW_REV_200)
mdss_set_quirk(mdata, MDSS_QUIRK_FMT_PACK_PATTERN);
}
static void mdss_hw_rev_init(struct mdss_data_type *mdata)
{
if (mdata->mdp_rev)
return;
mdata->mdp_rev = MDSS_REG_READ(mdata, MDSS_REG_HW_VERSION);
mdss_mdp_hw_rev_caps_init(mdata);
}
/**
* mdss_hw_init() - Initialize MDSS target specific register settings
* @mdata: MDP private data
*
* Initialize basic MDSS hardware settings based on the board specific
* parameters. This function does not explicitly turn on the MDP clocks
* and so it must be called with the MDP clocks already enabled.
*/
void mdss_hw_init(struct mdss_data_type *mdata)
{
struct mdss_mdp_pipe *vig;
mdss_hw_rev_init(mdata);
/* Disable hw underrun recovery only for older mdp reversions. */
if (mdata->mdp_rev < MDSS_MDP_HW_REV_105)
writel_relaxed(0x0, mdata->mdp_base +
MDSS_MDP_REG_VIDEO_INTF_UNDERFLOW_CTL);
if (mdata->hw_settings) {
struct mdss_hw_settings *hws = mdata->hw_settings;
while (hws->reg) {
writel_relaxed(hws->val, hws->reg);
hws++;
}
}
vig = mdata->vig_pipes;
mdata->nmax_concurrent_ad_hw =
(mdata->mdp_rev < MDSS_MDP_HW_REV_103) ? 1 : 2;
pr_debug("MDP hw init done\n");
}
static u32 mdss_mdp_res_init(struct mdss_data_type *mdata)
{
u32 rc = 0;
if (mdata->res_init) {
pr_err("mdss resources already initialized\n");
return -EPERM;
}
mdata->res_init = true;
mdata->clk_ena = false;
mdss_mdp_hw.irq_info->irq_mask = MDSS_MDP_DEFAULT_INTR_MASK;
mdss_mdp_hw.irq_info->irq_ena = false;
rc = mdss_mdp_irq_clk_setup(mdata);
if (rc)
return rc;
mdata->hist_intr.req = 0;
mdata->hist_intr.curr = 0;
mdata->hist_intr.state = 0;
spin_lock_init(&mdata->hist_intr.lock);
mdata->iclient = msm_ion_client_create(mdata->pdev->name);
if (IS_ERR_OR_NULL(mdata->iclient)) {
pr_err("msm_ion_client_create() return error (%pK)\n",
mdata->iclient);
mdata->iclient = NULL;
}
return rc;
}
static u32 mdss_mdp_scaler_init(struct mdss_data_type *mdata,
struct device *dev)
{
int ret;
struct device_node *node;
u32 prop_val;
if (!dev)
return -EPERM;
node = of_get_child_by_name(dev->of_node, "qcom,mdss-scaler-offsets");
if (!node)
return 0;
if (mdata->scaler_off)
return -EFAULT;
mdata->scaler_off = devm_kzalloc(&mdata->pdev->dev,
sizeof(*mdata->scaler_off), GFP_KERNEL);
if (!mdata->scaler_off)
return -ENOMEM;
ret = of_property_read_u32(node,
"qcom,mdss-vig-scaler-off",
&prop_val);
if (ret) {
pr_err("read property %s failed ret %d\n",
"qcom,mdss-vig-scaler-off", ret);
return -EINVAL;
}
mdata->scaler_off->vig_scaler_off = prop_val;
ret = of_property_read_u32(node,
"qcom,mdss-vig-scaler-lut-off",
&prop_val);
if (ret) {
pr_err("read property %s failed ret %d\n",
"qcom,mdss-vig-scaler-lut-off", ret);
return -EINVAL;
}
mdata->scaler_off->vig_scaler_lut_off = prop_val;
mdata->scaler_off->has_dest_scaler =
of_property_read_bool(mdata->pdev->dev.of_node,
"qcom,mdss-has-dest-scaler");
if (mdata->scaler_off->has_dest_scaler) {
ret = of_property_read_u32(node,
"qcom,mdss-dest-block-off",
&prop_val);
if (ret) {
pr_err("read property %s failed ret %d\n",
"qcom,mdss-dest-block-off", ret);
return -EINVAL;
}
mdata->scaler_off->dest_base = mdata->mdss_io.base +
prop_val;
mdata->scaler_off->ndest_scalers =
mdss_mdp_parse_dt_prop_len(mdata->pdev,
"qcom,mdss-dest-scalers-off");
mdata->scaler_off->dest_scaler_off =
devm_kzalloc(&mdata->pdev->dev, sizeof(u32) *
mdata->scaler_off->ndest_scalers,
GFP_KERNEL);
if (!mdata->scaler_off->dest_scaler_off)
return -ENOMEM;
ret = mdss_mdp_parse_dt_handler(mdata->pdev,
"qcom,mdss-dest-scaler-off",
mdata->scaler_off->dest_scaler_off,
mdata->scaler_off->ndest_scalers);
if (ret)
return -EINVAL;
mdata->scaler_off->dest_scaler_lut_off =
devm_kzalloc(&mdata->pdev->dev, sizeof(u32) *
mdata->scaler_off->ndest_scalers,
GFP_KERNEL);
if (!mdata->scaler_off->dest_scaler_lut_off)
return -ENOMEM;
ret = mdss_mdp_parse_dt_handler(mdata->pdev,
"qcom,mdss-dest-scalers-lut-off",
mdata->scaler_off->dest_scaler_lut_off,
mdata->scaler_off->ndest_scalers);
if (ret)
return -EINVAL;
}
mutex_init(&mdata->scaler_off->scaler_lock);
return 0;
}
/**
* mdss_mdp_footswitch_ctrl_splash() - clocks handoff for cont. splash screen
* @on: 1 to start handoff, 0 to complete the handoff after first frame update
*
* MDSS Clocks and GDSC are already on during continuous splash screen, but
* increasing ref count will keep clocks from being turned off until handoff
* has properly happened after frame update.
*/
void mdss_mdp_footswitch_ctrl_splash(int on)
{
int ret;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (mdata != NULL) {
if (on) {
mdata->handoff_pending = true;
pr_debug("Enable MDP FS for splash.\n");
if (mdata->venus) {
ret = regulator_enable(mdata->venus);
if (ret)
pr_err("venus failed to enable\n");
}
ret = regulator_enable(mdata->fs);
if (ret)
pr_err("Footswitch failed to enable\n");
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
mdss_bus_bandwidth_ctrl(true);
} else {
pr_debug("Disable MDP FS for splash.\n");
mdss_bus_bandwidth_ctrl(false);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
regulator_disable(mdata->fs);
if (mdata->venus)
regulator_disable(mdata->venus);
mdata->handoff_pending = false;
}
} else {
pr_warn("mdss mdata not initialized\n");
}
}
static int mdss_mdp_get_pan_intf(const char *pan_intf)
{
int i, rc = MDSS_PANEL_INTF_INVALID;
if (!pan_intf)
return rc;
for (i = 0; i < ARRAY_SIZE(pan_types); i++) {
if (!strcmp(pan_intf, pan_types[i].name)) {
rc = pan_types[i].type;
break;
}
}
return rc;
}
static int mdss_mdp_get_pan_cfg(struct mdss_panel_cfg *pan_cfg)
{
char *t = NULL;
char pan_intf_str[MDSS_MAX_PANEL_LEN];
int rc, i, panel_len;
char pan_name[MDSS_MAX_PANEL_LEN] = {'\0'};
if (!pan_cfg)
return -EINVAL;
if (mdss_mdp_panel[0] == '0') {
pr_debug("panel name is not set\n");
pan_cfg->lk_cfg = false;
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
} else if (mdss_mdp_panel[0] == '1') {
pan_cfg->lk_cfg = true;
} else {
/* read from dt */
pan_cfg->lk_cfg = true;
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
/* skip lk cfg and delimiter; ex: "1:" */
strlcpy(pan_name, &mdss_mdp_panel[2], MDSS_MAX_PANEL_LEN);
t = strnstr(pan_name, ":", MDSS_MAX_PANEL_LEN);
if (!t) {
pr_err("pan_name=[%s] invalid\n", pan_name);
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
for (i = 0; ((pan_name + i) < t) && (i < 4); i++)
pan_intf_str[i] = *(pan_name + i);
pan_intf_str[i] = 0;
pr_debug("%d panel intf %s\n", __LINE__, pan_intf_str);
/* point to the start of panel name */
t = t + 1;
strlcpy(&pan_cfg->arg_cfg[0], t, sizeof(pan_cfg->arg_cfg));
pr_debug("%d: t=[%s] panel name=[%s]\n", __LINE__,
t, pan_cfg->arg_cfg);
panel_len = strlen(pan_cfg->arg_cfg);
if (!panel_len) {
pr_err("Panel name is invalid\n");
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
rc = mdss_mdp_get_pan_intf(pan_intf_str);
pan_cfg->pan_intf = (rc < 0) ? MDSS_PANEL_INTF_INVALID : rc;
return 0;
}
static int mdss_mdp_parse_dt_pan_intf(struct platform_device *pdev)
{
int rc;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
const char *prim_intf = NULL;
rc = of_property_read_string(pdev->dev.of_node,
"qcom,mdss-pref-prim-intf", &prim_intf);
if (rc)
return -ENODEV;
rc = mdss_mdp_get_pan_intf(prim_intf);
if (rc < 0) {
mdata->pan_cfg.pan_intf = MDSS_PANEL_INTF_INVALID;
} else {
mdata->pan_cfg.pan_intf = rc;
rc = 0;
}
return rc;
}
static int mdss_mdp_get_cmdline_config(struct platform_device *pdev)
{
int rc, len = 0;
int *intf_type;
char *panel_name;
struct mdss_panel_cfg *pan_cfg;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->pan_cfg.arg_cfg[MDSS_MAX_PANEL_LEN] = 0;
pan_cfg = &mdata->pan_cfg;
panel_name = &pan_cfg->arg_cfg[0];
intf_type = &pan_cfg->pan_intf;
/* reads from dt by default */
pan_cfg->lk_cfg = true;
len = strlen(mdss_mdp_panel);
if (len > 0) {
rc = mdss_mdp_get_pan_cfg(pan_cfg);
if (!rc) {
pan_cfg->init_done = true;
return rc;
}
}
rc = mdss_mdp_parse_dt_pan_intf(pdev);
/* if pref pan intf is not present */
if (rc)
pr_warn("unable to parse device tree for pan intf\n");
pan_cfg->init_done = true;
return 0;
}
static void __update_sspp_info(struct mdss_mdp_pipe *pipe,
int pipe_cnt, char *type, char *buf, int *cnt)
{
int i;
int j;
size_t len = PAGE_SIZE;
int num_bytes = BITS_TO_BYTES(MDP_IMGTYPE_LIMIT1);
#define SPRINT(fmt, ...) \
(*cnt += scnprintf(buf + *cnt, len - *cnt, fmt, ##__VA_ARGS__))
for (i = 0; i < pipe_cnt && pipe; i++) {
SPRINT("pipe_num:%d pipe_type:%s pipe_ndx:%d rects:%d pipe_is_handoff:%d display_id:%d ",
pipe->num, type, pipe->ndx, pipe->multirect.max_rects,
pipe->is_handed_off, mdss_mdp_get_display_id(pipe));
SPRINT("fmts_supported:");
for (j = 0; j < num_bytes; j++)
SPRINT("%d,", pipe->supported_formats[j]);
SPRINT("\n");
pipe += pipe->multirect.max_rects;
}
#undef SPRINT
}
static void mdss_mdp_update_sspp_info(struct mdss_data_type *mdata,
char *buf, int *cnt)
{
__update_sspp_info(mdata->vig_pipes, mdata->nvig_pipes,
"vig", buf, cnt);
__update_sspp_info(mdata->rgb_pipes, mdata->nrgb_pipes,
"rgb", buf, cnt);
__update_sspp_info(mdata->dma_pipes, mdata->ndma_pipes,
"dma", buf, cnt);
__update_sspp_info(mdata->cursor_pipes, mdata->ncursor_pipes,
"cursor", buf, cnt);
}
static void mdss_mdp_update_wb_info(struct mdss_data_type *mdata,
char *buf, int *cnt)
{
#define SPRINT(fmt, ...) \
(*cnt += scnprintf(buf + *cnt, len - *cnt, fmt, ##__VA_ARGS__))
size_t len = PAGE_SIZE;
int i;
int num_bytes = BITS_TO_BYTES(MDP_IMGTYPE_LIMIT1);
SPRINT("rot_input_fmts=");
for (i = 0; i < num_bytes && mdata->wb; i++)
SPRINT("%d ", mdata->wb->supported_input_formats[i]);
SPRINT("\nrot_output_fmts=");
for (i = 0; i < num_bytes && mdata->wb; i++)
SPRINT("%d ", mdata->wb->supported_input_formats[i]);
SPRINT("\nwb_output_fmts=");
for (i = 0; i < num_bytes && mdata->wb; i++)
SPRINT("%d ", mdata->wb->supported_output_formats[i]);
SPRINT("\n");
#undef SPRINT
}
ssize_t mdss_mdp_show_capabilities(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
size_t len = PAGE_SIZE;
int cnt = 0;
#define SPRINT(fmt, ...) \
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
SPRINT("mdp_version=5\n");
SPRINT("hw_rev=%d\n", mdata->mdp_rev);
SPRINT("pipe_count:%d\n", mdata->nvig_pipes + mdata->nrgb_pipes +
mdata->ndma_pipes + mdata->ncursor_pipes);
mdss_mdp_update_sspp_info(mdata, buf, &cnt);
mdss_mdp_update_wb_info(mdata, buf, &cnt);
/* TODO : need to remove num pipes info */
SPRINT("rgb_pipes=%d\n", mdata->nrgb_pipes);
SPRINT("vig_pipes=%d\n", mdata->nvig_pipes);
SPRINT("dma_pipes=%d\n", mdata->ndma_pipes);
SPRINT("blending_stages=%d\n", mdata->max_target_zorder);
SPRINT("cursor_pipes=%d\n", mdata->ncursor_pipes);
SPRINT("max_cursor_size=%d\n", mdata->max_cursor_size);
SPRINT("smp_count=%d\n", mdata->smp_mb_cnt);
SPRINT("smp_size=%d\n", mdata->smp_mb_size);
SPRINT("smp_mb_per_pipe=%d\n", mdata->smp_mb_per_pipe);
SPRINT("max_downscale_ratio=%d\n", MAX_DOWNSCALE_RATIO);
SPRINT("max_upscale_ratio=%d\n", MAX_UPSCALE_RATIO);
if (mdata->nwb)
SPRINT("wb_intf_index=%d\n", mdata->nwb - 1);
if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) {
SPRINT("fmt_mt_nv12_factor=%d\n",
mdata->prefill_data.prefill_factors.fmt_mt_nv12_factor);
SPRINT("fmt_mt_factor=%d\n",
mdata->prefill_data.prefill_factors.fmt_mt_factor);
SPRINT("fmt_linear_factor=%d\n",
mdata->prefill_data.prefill_factors.fmt_linear_factor);
SPRINT("scale_factor=%d\n",
mdata->prefill_data.prefill_factors.scale_factor);
SPRINT("xtra_ff_factor=%d\n",
mdata->prefill_data.prefill_factors.xtra_ff_factor);
}
if (test_bit(MDSS_QOS_TS_PREFILL, mdata->mdss_qos_map)) {
SPRINT("amortizable_threshold=%d\n",
mdata->prefill_data.ts_threshold);
SPRINT("system_overhead_lines=%d\n",
mdata->prefill_data.ts_overhead);
}
if (mdata->props)
SPRINT("props=%d\n", mdata->props);
if (mdata->max_bw_low)
SPRINT("max_bandwidth_low=%u\n", mdata->max_bw_low);
if (mdata->max_bw_high)
SPRINT("max_bandwidth_high=%u\n", mdata->max_bw_high);
if (mdata->max_pipe_width)
SPRINT("max_pipe_width=%d\n", mdata->max_pipe_width);
if (mdata->max_mixer_width)
SPRINT("max_mixer_width=%d\n", mdata->max_mixer_width);
if (mdata->max_bw_per_pipe)
SPRINT("max_pipe_bw=%u\n", mdata->max_bw_per_pipe);
if (mdata->max_mdp_clk_rate)
SPRINT("max_mdp_clk=%u\n", mdata->max_mdp_clk_rate);
if (mdata->clk_factor.numer)
SPRINT("clk_fudge_factor=%u,%u\n", mdata->clk_factor.numer,
mdata->clk_factor.denom);
if (mdata->has_rot_dwnscale) {
if (mdata->rot_dwnscale_min)
SPRINT("rot_dwnscale_min=%u\n",
mdata->rot_dwnscale_min);
if (mdata->rot_dwnscale_max)
SPRINT("rot_dwnscale_max=%u\n",
mdata->rot_dwnscale_max);
}
SPRINT("features=");
if (mdata->has_bwc)
SPRINT(" bwc");
if (mdata->has_ubwc)
SPRINT(" ubwc");
if (mdata->has_wb_ubwc)
SPRINT(" wb_ubwc");
if (mdata->has_decimation)
SPRINT(" decimation");
if (mdata->highest_bank_bit && !mdss_mdp_is_ubwc_supported(mdata))
SPRINT(" tile_format");
if (mdata->has_non_scalar_rgb)
SPRINT(" non_scalar_rgb");
if (mdata->has_src_split)
SPRINT(" src_split");
if (mdata->has_rot_dwnscale)
SPRINT(" rotator_downscale");
if (mdata->max_bw_settings_cnt)
SPRINT(" dynamic_bw_limit");
if (test_bit(MDSS_CAPS_QSEED3, mdata->mdss_caps_map))
SPRINT(" qseed3");
if (test_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map))
SPRINT(" dest_scaler");
if (mdata->has_separate_rotator)
SPRINT(" separate_rotator");
if (mdss_has_quirk(mdata, MDSS_QUIRK_HDR_SUPPORT_ENABLED))
SPRINT(" hdr");
SPRINT("\n");
#undef SPRINT
return cnt;
}
static ssize_t mdss_mdp_read_max_limit_bw(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
size_t len = PAGE_SIZE;
u32 cnt = 0;
int i;
char bw_names[4][8] = {"default", "camera", "hflip", "vflip"};
char pipe_bw_names[4][16] = {"default_pipe", "camera_pipe",
"hflip_pipe", "vflip_pipe"};
struct mdss_max_bw_settings *bw_settings;
struct mdss_max_bw_settings *pipe_bw_settings;
bw_settings = mdata->max_bw_settings;
pipe_bw_settings = mdata->max_per_pipe_bw_settings;
#define SPRINT(fmt, ...) \
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
SPRINT("bw_mode_bitmap=%d\n", mdata->bw_mode_bitmap);
SPRINT("bw_limit_pending=%d\n", mdata->bw_limit_pending);
for (i = 0; i < mdata->max_bw_settings_cnt; i++) {
SPRINT("%s=%d\n", bw_names[i], bw_settings->mdss_max_bw_val);
bw_settings++;
}
for (i = 0; i < mdata->mdss_per_pipe_bw_cnt; i++) {
SPRINT("%s=%d\n", pipe_bw_names[i],
pipe_bw_settings->mdss_max_bw_val);
pipe_bw_settings++;
}
return cnt;
}
static ssize_t mdss_mdp_store_max_limit_bw(struct device *dev,
struct device_attribute *attr, const char *buf, size_t len)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
u32 data = 0;
if (kstrtouint(buf, 0, &data)) {
pr_info("Not able scan to bw_mode_bitmap\n");
} else {
mdata->bw_mode_bitmap = data;
mdata->bw_limit_pending = true;
pr_debug("limit use case, bw_mode_bitmap = %d\n", data);
}
return len;
}
static DEVICE_ATTR(caps, 0444, mdss_mdp_show_capabilities, NULL);
static DEVICE_ATTR(bw_mode_bitmap, 0664,
mdss_mdp_read_max_limit_bw, mdss_mdp_store_max_limit_bw);
static struct attribute *mdp_fs_attrs[] = {
&dev_attr_caps.attr,
&dev_attr_bw_mode_bitmap.attr,
NULL
};
static struct attribute_group mdp_fs_attr_group = {
.attrs = mdp_fs_attrs
};
static int mdss_mdp_register_sysfs(struct mdss_data_type *mdata)
{
struct device *dev = &mdata->pdev->dev;
int rc;
rc = sysfs_create_group(&dev->kobj, &mdp_fs_attr_group);
return rc;
}
int mdss_panel_get_intf_status(u32 disp_num, u32 intf_type)
{
int rc, intf_status = 0;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (!mdss_res || !mdss_res->pan_cfg.init_done)
return -EPROBE_DEFER;
if (mdss_res->handoff_pending) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
intf_status = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_DISP_INTF_SEL);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
if (intf_type == MDSS_PANEL_INTF_DSI) {
if (disp_num == DISPLAY_1)
rc = (intf_status & MDSS_MDP_INTF_DSI0_SEL);
else if (disp_num == DISPLAY_2)
rc = (intf_status & MDSS_MDP_INTF_DSI1_SEL);
else
rc = 0;
} else if (intf_type == MDSS_PANEL_INTF_EDP) {
intf_status &= MDSS_MDP_INTF_EDP_SEL;
rc = (intf_status == MDSS_MDP_INTF_EDP_SEL);
} else if (intf_type == MDSS_PANEL_INTF_HDMI) {
intf_status &= MDSS_MDP_INTF_HDMI_SEL;
rc = (intf_status == MDSS_MDP_INTF_HDMI_SEL);
} else {
rc = 0;
}
} else {
rc = 0;
}
return rc;
}
static int mdss_mdp_probe(struct platform_device *pdev)
{
struct resource *res;
int rc;
struct mdss_data_type *mdata;
uint32_t intf_sel = 0;
uint32_t split_display = 0;
int num_of_display_on = 0;
int i = 0;
if (!pdev->dev.of_node) {
pr_err("MDP driver only supports device tree probe\n");
return -ENOTSUPP;
}
if (mdss_res) {
pr_err("MDP already initialized\n");
return -EINVAL;
}
mdata = devm_kzalloc(&pdev->dev, sizeof(*mdata), GFP_KERNEL);
if (mdata == NULL)
return -ENOMEM;
pdev->id = 0;
mdata->pdev = pdev;
platform_set_drvdata(pdev, mdata);
mdss_res = mdata;
mutex_init(&mdata->reg_lock);
mutex_init(&mdata->reg_bus_lock);
mutex_init(&mdata->bus_lock);
INIT_LIST_HEAD(&mdata->reg_bus_clist);
atomic_set(&mdata->sd_client_count, 0);
atomic_set(&mdata->active_intf_cnt, 0);
mdss_res->mdss_util = mdss_get_util_intf();
if (mdss_res->mdss_util == NULL) {
pr_err("Failed to get mdss utility functions\n");
return -ENODEV;
}
mdss_res->mdss_util->get_iommu_domain = mdss_smmu_get_domain_id;
mdss_res->mdss_util->iommu_attached = is_mdss_iommu_attached;
mdss_res->mdss_util->iommu_ctrl = mdss_iommu_ctrl;
mdss_res->mdss_util->bus_scale_set_quota = mdss_bus_scale_set_quota;
mdss_res->mdss_util->bus_bandwidth_ctrl = mdss_bus_bandwidth_ctrl;
mdss_res->mdss_util->panel_intf_type = mdss_panel_intf_type;
mdss_res->mdss_util->panel_intf_status = mdss_panel_get_intf_status;
rc = msm_mdss_ioremap_byname(pdev, &mdata->mdss_io, "mdp_phys");
if (rc) {
pr_err("unable to map MDP base\n");
goto probe_done;
}
pr_debug("MDSS HW Base addr=0x%x len=0x%x\n",
(int) (unsigned long) mdata->mdss_io.base,
mdata->mdss_io.len);
rc = msm_mdss_ioremap_byname(pdev, &mdata->vbif_io, "vbif_phys");
if (rc) {
pr_err("unable to map MDSS VBIF base\n");
goto probe_done;
}
pr_debug("MDSS VBIF HW Base addr=0x%x len=0x%x\n",
(int) (unsigned long) mdata->vbif_io.base,
mdata->vbif_io.len);
rc = msm_mdss_ioremap_byname(pdev, &mdata->vbif_nrt_io,
"vbif_nrt_phys");
if (rc)
pr_debug("unable to map MDSS VBIF non-realtime base\n");
else
pr_debug("MDSS VBIF NRT HW Base addr=%pK len=0x%x\n",
mdata->vbif_nrt_io.base, mdata->vbif_nrt_io.len);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
pr_err("unable to get MDSS irq\n");
rc = -ENOMEM;
goto probe_done;
}
mdss_mdp_hw.irq_info = kcalloc(1, sizeof(struct irq_info), GFP_KERNEL);
if (!mdss_mdp_hw.irq_info)
return -ENOMEM;
mdss_mdp_hw.irq_info->irq = res->start;
mdss_mdp_hw.ptr = mdata;
/* export misc. interrupts to external driver */
mdata->irq_domain = irq_domain_add_linear(pdev->dev.of_node, 32,
&mdss_irq_domain_ops, mdata);
if (!mdata->irq_domain) {
pr_err("unable to add linear domain\n");
rc = -ENOMEM;
goto probe_done;
}
mdss_misc_hw.irq_info = mdss_intr_line();
rc = mdss_res->mdss_util->register_irq(&mdss_misc_hw);
if (rc)
pr_err("mdss_register_irq failed.\n");
rc = mdss_mdp_res_init(mdata);
if (rc) {
pr_err("unable to initialize mdss mdp resources\n");
goto probe_done;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT_MS);
if (mdata->idle_pc_enabled)
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev))
mdss_mdp_footswitch_ctrl(mdata, true);
rc = mdss_mdp_bus_scale_register(mdata);
if (rc) {
pr_err("unable to register bus scaling\n");
goto probe_done;
}
/*
* enable clocks and read mdp_rev as soon as possible once
* kernel is up.
*/
mdss_mdp_footswitch_ctrl_splash(true);
mdss_hw_rev_init(mdata);
/*populate hw iomem base info from device tree*/
rc = mdss_mdp_parse_dt(pdev);
if (rc) {
pr_err("unable to parse device tree\n");
goto probe_done;
}
rc = mdss_mdp_get_cmdline_config(pdev);
if (rc) {
pr_err("Error in panel override:rc=[%d]\n", rc);
goto probe_done;
}
rc = mdss_mdp_debug_init(pdev, mdata);
if (rc) {
pr_err("unable to initialize mdp debugging\n");
goto probe_done;
}
rc = mdss_mdp_scaler_init(mdata, &pdev->dev);
if (rc)
goto probe_done;
rc = mdss_mdp_register_sysfs(mdata);
if (rc)
pr_err("unable to register mdp sysfs nodes\n");
rc = mdss_fb_register_mdp_instance(&mdp5);
if (rc)
pr_err("unable to register mdp instance\n");
rc = mdss_res->mdss_util->register_irq(&mdss_mdp_hw);
if (rc)
pr_err("mdss_register_irq failed.\n");
rc = mdss_smmu_init(mdata, &pdev->dev);
if (rc)
pr_err("mdss smmu init failed\n");
mdss_mdp_set_supported_formats(mdata);
mdss_res->mdss_util->mdp_probe_done = true;
mdss_hw_init(mdata);
rc = mdss_mdp_pp_init(&pdev->dev);
if (rc)
pr_err("unable to initialize mdss pp resources\n");
/* Restoring Secure configuration during boot-up */
if (mdss_mdp_req_init_restore_cfg(mdata))
__mdss_restore_sec_cfg(mdata);
if (mdss_has_quirk(mdata, MDSS_QUIRK_BWCPANIC)) {
mdata->default_panic_lut0 = readl_relaxed(mdata->mdp_base +
MMSS_MDP_PANIC_LUT0);
mdata->default_panic_lut1 = readl_relaxed(mdata->mdp_base +
MMSS_MDP_PANIC_LUT1);
mdata->default_robust_lut = readl_relaxed(mdata->mdp_base +
MMSS_MDP_ROBUST_LUT);
}
/*
* Read the DISP_INTF_SEL register to check if display was enabled in
* bootloader or not. If yes, let handoff handle removing the extra
* clk/regulator votes else turn off clk/regulators because purpose
* here is to get mdp_rev.
*/
intf_sel = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_DISP_INTF_SEL);
split_display = readl_relaxed(mdata->mdp_base +
MDSS_MDP_REG_SPLIT_DISPLAY_EN);
mdata->splash_intf_sel = intf_sel;
mdata->splash_split_disp = split_display;
if (intf_sel != 0) {
for (i = 0; i < 4; i++)
if ((intf_sel >> i*8) & 0x000000FF)
num_of_display_on++;
/*
* For split display enabled - DSI0, DSI1 interfaces are
* considered as single display. So decrement
* 'num_of_display_on' by 1
*/
if (split_display)
num_of_display_on--;
}
if (!num_of_display_on) {
mdss_mdp_footswitch_ctrl_splash(false);
msm_bus_scale_client_update_request(
mdata->bus_hdl, 0);
mdata->ao_bw_uc_idx = 0;
} else {
mdata->handoff_pending = true;
/*
* If multiple displays are enabled in LK, ctrl_splash off will
* be called multiple times during splash_cleanup. Need to
* enable it symmetrically
*/
for (i = 1; i < num_of_display_on; i++)
mdss_mdp_footswitch_ctrl_splash(true);
}
mdp_intr_cb = kcalloc(ARRAY_SIZE(mdp_irq_map),
sizeof(struct intr_callback), GFP_KERNEL);
if (mdp_intr_cb == NULL)
return -ENOMEM;
mdss_res->mdp_irq_mask = kcalloc(ARRAY_SIZE(mdp_intr_reg),
sizeof(u32), GFP_KERNEL);
if (mdss_res->mdp_irq_mask == NULL)
return -ENOMEM;
pr_info("mdss version = 0x%x, bootloader display is %s, num %d, intf_sel=0x%08x\n",
mdata->mdp_rev, num_of_display_on ? "on" : "off",
num_of_display_on, intf_sel);
probe_done:
if (IS_ERR_VALUE((unsigned long)rc)) {
if (!num_of_display_on)
mdss_mdp_footswitch_ctrl_splash(false);
if (mdata->regulator_notif_register)
regulator_unregister_notifier(mdata->fs,
&(mdata->gdsc_cb));
mdss_mdp_hw.ptr = NULL;
mdss_mdp_pp_term(&pdev->dev);
mutex_destroy(&mdata->reg_lock);
mdss_res = NULL;
}
return rc;
}
static void mdss_mdp_parse_dt_regs_array(const u32 *arr,
struct mdss_io_data *io, struct mdss_hw_settings *hws, int count)
{
u32 len, reg;
int i;
if (!arr)
return;
for (i = 0, len = count * 2; i < len; i += 2) {
reg = be32_to_cpu(arr[i]);
if (reg >= io->len)
continue;
hws->reg = io->base + reg;
hws->val = be32_to_cpu(arr[i + 1]);
pr_debug("reg: 0x%04x=0x%08x\n", reg, hws->val);
hws++;
}
}
int mdss_mdp_parse_dt_hw_settings(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
struct mdss_hw_settings *hws;
const u32 *vbif_arr, *mdp_arr, *vbif_nrt_arr;
int vbif_len, mdp_len, vbif_nrt_len;
vbif_arr = of_get_property(pdev->dev.of_node, "qcom,vbif-settings",
&vbif_len);
if (!vbif_arr || (vbif_len & 1)) {
pr_debug("MDSS VBIF settings not found\n");
vbif_len = 0;
}
vbif_len /= 2 * sizeof(u32);
vbif_nrt_arr = of_get_property(pdev->dev.of_node,
"qcom,vbif-nrt-settings", &vbif_nrt_len);
if (!vbif_nrt_arr || (vbif_nrt_len & 1)) {
pr_debug("MDSS VBIF non-realtime settings not found\n");
vbif_nrt_len = 0;
}
vbif_nrt_len /= 2 * sizeof(u32);
mdp_arr = of_get_property(pdev->dev.of_node, "qcom,mdp-settings",
&mdp_len);
if (!mdp_arr || (mdp_len & 1)) {
pr_debug("MDSS MDP settings not found\n");
mdp_len = 0;
}
mdp_len /= 2 * sizeof(u32);
if (!(mdp_len + vbif_len + vbif_nrt_len))
return 0;
hws = devm_kzalloc(&pdev->dev, sizeof(*hws) * (vbif_len + mdp_len +
vbif_nrt_len + 1), GFP_KERNEL);
if (!hws)
return -ENOMEM;
mdss_mdp_parse_dt_regs_array(vbif_arr, &mdata->vbif_io,
hws, vbif_len);
mdss_mdp_parse_dt_regs_array(vbif_nrt_arr, &mdata->vbif_nrt_io,
hws, vbif_nrt_len);
mdss_mdp_parse_dt_regs_array(mdp_arr, &mdata->mdss_io,
hws + vbif_len, mdp_len);
mdata->hw_settings = hws;
return 0;
}
static int mdss_mdp_parse_dt(struct platform_device *pdev)
{
int rc, data;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
rc = mdss_mdp_parse_dt_hw_settings(pdev);
if (rc) {
pr_err("Error in device tree : hw settings\n");
return rc;
}
rc = mdss_mdp_parse_dt_pipe(pdev);
if (rc) {
pr_err("Error in device tree : pipes\n");
return rc;
}
rc = mdss_mdp_parse_dt_mixer(pdev);
if (rc) {
pr_err("Error in device tree : mixers\n");
return rc;
}
rc = mdss_mdp_parse_dt_misc(pdev);
if (rc) {
pr_err("Error in device tree : misc\n");
return rc;
}
rc = mdss_mdp_parse_dt_wb(pdev);
if (rc) {
pr_err("Error in device tree : wb\n");
return rc;
}
rc = mdss_mdp_parse_dt_ctl(pdev);
if (rc) {
pr_err("Error in device tree : ctl\n");
return rc;
}
rc = mdss_mdp_parse_dt_video_intf(pdev);
if (rc) {
pr_err("Error in device tree : ctl\n");
return rc;
}
rc = mdss_mdp_parse_dt_smp(pdev);
if (rc) {
pr_err("Error in device tree : smp\n");
return rc;
}
rc = mdss_mdp_parse_dt_prefill(pdev);
if (rc) {
pr_err("Error in device tree : prefill\n");
return rc;
}
rc = mdss_mdp_parse_dt_ad_cfg(pdev);
if (rc) {
pr_err("Error in device tree : ad\n");
return rc;
}
rc = mdss_mdp_parse_dt_cdm(pdev);
if (rc)
pr_debug("CDM offset not found in device tree\n");
rc = mdss_mdp_parse_dt_dsc(pdev);
if (rc)
pr_debug("DSC offset not found in device tree\n");
/* Parse the mdp specific register base offset*/
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-mdp-reg-offset", &data);
if (rc) {
pr_err("Error in device tree : mdp reg base\n");
return rc;
}
mdata->mdp_base = mdata->mdss_io.base + data;
return 0;
}
static void mdss_mdp_parse_dt_pipe_sw_reset(struct platform_device *pdev,
u32 reg_off, char *prop_name, struct mdss_mdp_pipe *pipe_list,
u32 npipes)
{
int len;
const u32 *arr;
arr = of_get_property(pdev->dev.of_node, prop_name, &len);
if (arr) {
int i;
len /= sizeof(u32);
if (len != npipes) {
pr_err("%s: invalid sw_reset entries req:%d found:%d\n",
prop_name, len, npipes);
return;
}
for (i = 0; i < len; i++) {
pipe_list[i].sw_reset.reg_off = reg_off;
pipe_list[i].sw_reset.bit_off = be32_to_cpu(arr[i]);
pr_debug("%s[%d]: sw_reset: reg_off:0x%x bit_off:%d\n",
prop_name, i, reg_off, be32_to_cpu(arr[i]));
}
}
}
static int mdss_mdp_parse_dt_pipe_clk_ctrl(struct platform_device *pdev,
char *prop_name, struct mdss_mdp_pipe *pipe_list, u32 npipes)
{
int rc = 0, len;
const u32 *arr;
arr = of_get_property(pdev->dev.of_node, prop_name, &len);
if (arr) {
int i, j;
len /= sizeof(u32);
for (i = 0, j = 0; i < len; j++) {
struct mdss_mdp_pipe *pipe = NULL;
if (j >= npipes) {
pr_err("invalid clk ctrl enries for prop: %s\n",
prop_name);
return -EINVAL;
}
pipe = &pipe_list[j];
pipe->clk_ctrl.reg_off = be32_to_cpu(arr[i++]);
pipe->clk_ctrl.bit_off = be32_to_cpu(arr[i++]);
/* status register is next in line to ctrl register */
pipe->clk_status.reg_off = pipe->clk_ctrl.reg_off + 4;
pipe->clk_status.bit_off = be32_to_cpu(arr[i++]);
pr_debug("%s[%d]: ctrl: reg_off: 0x%x bit_off: %d\n",
prop_name, j, pipe->clk_ctrl.reg_off,
pipe->clk_ctrl.bit_off);
pr_debug("%s[%d]: status: reg_off: 0x%x bit_off: %d\n",
prop_name, j, pipe->clk_status.reg_off,
pipe->clk_status.bit_off);
}
if (j != npipes) {
pr_err("%s: %d entries found. required %d\n",
prop_name, j, npipes);
for (i = 0; i < npipes; i++) {
memset(&pipe_list[i].clk_ctrl, 0,
sizeof(pipe_list[i].clk_ctrl));
memset(&pipe_list[i].clk_status, 0,
sizeof(pipe_list[i].clk_status));
}
rc = -EINVAL;
}
} else {
pr_err("error mandatory property '%s' not found\n", prop_name);
rc = -EINVAL;
}
return rc;
}
static void mdss_mdp_parse_dt_pipe_panic_ctrl(struct platform_device *pdev,
char *prop_name, struct mdss_mdp_pipe *pipe_list, u32 npipes)
{
int i, j;
int len;
const u32 *arr;
struct mdss_mdp_pipe *pipe = NULL;
arr = of_get_property(pdev->dev.of_node, prop_name, &len);
if (arr) {
len /= sizeof(u32);
for (i = 0, j = 0; i < len; j++) {
if (j >= npipes) {
pr_err("invalid panic ctrl enries for prop: %s\n",
prop_name);
return;
}
pipe = &pipe_list[j];
pipe->panic_ctrl_ndx = be32_to_cpu(arr[i++]);
}
if (j != npipes)
pr_err("%s: %d entries found. required %d\n",
prop_name, j, npipes);
} else {
pr_debug("panic ctrl enabled but property '%s' not found\n",
prop_name);
}
}
static int mdss_mdp_parse_dt_pipe_helper(struct platform_device *pdev,
u32 ptype, char *ptypestr,
struct mdss_mdp_pipe **out_plist,
size_t len,
u8 priority_base)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 offsets[MDSS_MDP_MAX_SSPP];
u32 ftch_id[MDSS_MDP_MAX_SSPP];
u32 xin_id[MDSS_MDP_MAX_SSPP];
u32 pnums[MDSS_MDP_MAX_SSPP];
struct mdss_mdp_pipe *pipe_list;
char prop_name[64];
int i, cnt, rc;
u32 rects_per_sspp;
if (!out_plist)
return -EINVAL;
for (i = 0, cnt = 0; i < MDSS_MDP_MAX_SSPP && cnt < len; i++) {
if (ptype == get_pipe_type_from_num(i)) {
pnums[cnt] = i;
cnt++;
}
}
if (cnt < len)
pr_warn("Invalid %s pipe count: %zu, max supported: %d\n",
ptypestr, len, cnt);
if (cnt == 0) {
*out_plist = NULL;
return 0;
}
/* by default works in single rect mode unless otherwise noted */
rects_per_sspp = mdata->rects_per_sspp[ptype] ? : 1;
pipe_list = devm_kzalloc(&pdev->dev,
(sizeof(struct mdss_mdp_pipe) * cnt * rects_per_sspp),
GFP_KERNEL);
if (!pipe_list)
return -ENOMEM;
if (mdata->has_pixel_ram || (ptype == MDSS_MDP_PIPE_TYPE_CURSOR)) {
for (i = 0; i < cnt; i++)
ftch_id[i] = -1;
} else {
snprintf(prop_name, sizeof(prop_name),
"qcom,mdss-pipe-%s-fetch-id", ptypestr);
rc = mdss_mdp_parse_dt_handler(pdev, prop_name, ftch_id,
cnt);
if (rc)
goto parse_fail;
}
snprintf(prop_name, sizeof(prop_name),
"qcom,mdss-pipe-%s-xin-id", ptypestr);
rc = mdss_mdp_parse_dt_handler(pdev, prop_name, xin_id, cnt);
if (rc)
goto parse_fail;
snprintf(prop_name, sizeof(prop_name),
"qcom,mdss-pipe-%s-off", ptypestr);
rc = mdss_mdp_parse_dt_handler(pdev, prop_name, offsets, cnt);
if (rc)
goto parse_fail;
rc = mdss_mdp_pipe_addr_setup(mdata, pipe_list, offsets, ftch_id,
xin_id, ptype, pnums, cnt, rects_per_sspp,
priority_base);
if (rc)
goto parse_fail;
snprintf(prop_name, sizeof(prop_name),
"qcom,mdss-pipe-%s-clk-ctrl-offsets", ptypestr);
rc = mdss_mdp_parse_dt_pipe_clk_ctrl(pdev, prop_name,
pipe_list, cnt);
if (rc)
goto parse_fail;
*out_plist = pipe_list;
return cnt;
parse_fail:
devm_kfree(&pdev->dev, pipe_list);
return rc;
}
static int mdss_mdp_parse_dt_pipe(struct platform_device *pdev)
{
int rc = 0;
u32 nfids = 0, len, nxids = 0, npipes = 0;
u32 sw_reset_offset = 0;
u32 data[4];
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->has_pixel_ram = !mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-smp-data");
mdata->nvig_pipes = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-vig-off");
mdata->nrgb_pipes = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-rgb-off");
mdata->ndma_pipes = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-dma-off");
mdata->ncursor_pipes = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-cursor-off");
npipes = mdata->nvig_pipes + mdata->nrgb_pipes + mdata->ndma_pipes;
if (!mdata->has_pixel_ram) {
nfids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-vig-fetch-id");
nfids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-rgb-fetch-id");
nfids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-dma-fetch-id");
if (npipes != nfids) {
pr_err("device tree err: unequal number of pipes and smp ids");
return -EINVAL;
}
}
if (mdata->nvig_pipes)
nxids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-vig-xin-id");
if (mdata->nrgb_pipes)
nxids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-rgb-xin-id");
if (mdata->ndma_pipes)
nxids += mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pipe-dma-xin-id");
if (npipes != nxids) {
pr_err("device tree err: unequal number of pipes and xin ids\n");
return -EINVAL;
}
rc = mdss_mdp_parse_dt_pipe_helper(pdev, MDSS_MDP_PIPE_TYPE_VIG, "vig",
&mdata->vig_pipes, mdata->nvig_pipes, 0);
if (IS_ERR_VALUE((unsigned long)rc))
goto parse_fail;
mdata->nvig_pipes = rc;
rc = mdss_mdp_parse_dt_pipe_helper(pdev, MDSS_MDP_PIPE_TYPE_RGB, "rgb",
&mdata->rgb_pipes, mdata->nrgb_pipes,
mdata->nvig_pipes);
if (IS_ERR_VALUE((unsigned long)rc))
goto parse_fail;
mdata->nrgb_pipes = rc;
rc = mdss_mdp_parse_dt_pipe_helper(pdev, MDSS_MDP_PIPE_TYPE_DMA, "dma",
&mdata->dma_pipes, mdata->ndma_pipes,
mdata->nvig_pipes + mdata->nrgb_pipes);
if (IS_ERR_VALUE((unsigned long)rc))
goto parse_fail;
mdata->ndma_pipes = rc;
rc = mdss_mdp_parse_dt_pipe_helper(pdev, MDSS_MDP_PIPE_TYPE_CURSOR,
"cursor", &mdata->cursor_pipes, mdata->ncursor_pipes,
0);
if (IS_ERR_VALUE((unsigned long)rc))
goto parse_fail;
mdata->ncursor_pipes = rc;
rc = 0;
mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-pipe-sw-reset-off",
&sw_reset_offset, 1);
if (sw_reset_offset) {
if (mdata->vig_pipes)
mdss_mdp_parse_dt_pipe_sw_reset(pdev, sw_reset_offset,
"qcom,mdss-pipe-vig-sw-reset-map",
mdata->vig_pipes, mdata->nvig_pipes);
if (mdata->rgb_pipes)
mdss_mdp_parse_dt_pipe_sw_reset(pdev, sw_reset_offset,
"qcom,mdss-pipe-rgb-sw-reset-map",
mdata->rgb_pipes, mdata->nrgb_pipes);
if (mdata->dma_pipes)
mdss_mdp_parse_dt_pipe_sw_reset(pdev, sw_reset_offset,
"qcom,mdss-pipe-dma-sw-reset-map",
mdata->dma_pipes, mdata->ndma_pipes);
}
mdata->has_panic_ctrl = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-panic-ctrl");
if (mdata->has_panic_ctrl) {
if (mdata->vig_pipes)
mdss_mdp_parse_dt_pipe_panic_ctrl(pdev,
"qcom,mdss-pipe-vig-panic-ctrl-offsets",
mdata->vig_pipes, mdata->nvig_pipes);
if (mdata->rgb_pipes)
mdss_mdp_parse_dt_pipe_panic_ctrl(pdev,
"qcom,mdss-pipe-rgb-panic-ctrl-offsets",
mdata->rgb_pipes, mdata->nrgb_pipes);
if (mdata->dma_pipes)
mdss_mdp_parse_dt_pipe_panic_ctrl(pdev,
"qcom,mdss-pipe-dma-panic-ctrl-offsets",
mdata->dma_pipes, mdata->ndma_pipes);
}
len = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-per-pipe-panic-luts");
if (len != 4) {
pr_debug("Unable to read per-pipe-panic-luts\n");
} else {
rc = mdss_mdp_parse_dt_handler(pdev,
"qcom,mdss-per-pipe-panic-luts", data, len);
mdata->default_panic_lut_per_pipe_linear = data[0];
mdata->default_panic_lut_per_pipe_tile = data[1];
mdata->default_robust_lut_per_pipe_linear = data[2];
mdata->default_robust_lut_per_pipe_tile = data[3];
pr_debug("per pipe panic lut [0]:0x%x [1]:0x%x [2]:0x%x [3]:0x%x\n",
data[0], data[1], data[2], data[3]);
}
parse_fail:
return rc;
}
static int mdss_mdp_parse_dt_mixer(struct platform_device *pdev)
{
u32 nmixers, npingpong;
int rc = 0;
u32 *mixer_offsets = NULL, *dspp_offsets = NULL,
*pingpong_offsets = NULL;
u32 is_virtual_mixer_req = false;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->nmixers_intf = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-mixer-intf-off");
mdata->nmixers_wb = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-mixer-wb-off");
mdata->ndspp = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-dspp-off");
npingpong = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-pingpong-off");
nmixers = mdata->nmixers_intf + mdata->nmixers_wb;
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,max-mixer-width", &mdata->max_mixer_width);
if (rc) {
pr_err("device tree err: failed to get max mixer width\n");
return -EINVAL;
}
if (mdata->nmixers_intf < mdata->ndspp) {
pr_err("device tree err: no of dspp are greater than intf mixers\n");
return -EINVAL;
}
if (mdata->nmixers_intf != npingpong) {
pr_err("device tree err: unequal no of pingpong and intf mixers\n");
return -EINVAL;
}
mixer_offsets = kcalloc(nmixers, sizeof(u32), GFP_KERNEL);
if (!mixer_offsets)
return -ENOMEM;
dspp_offsets = kcalloc(mdata->ndspp, sizeof(u32), GFP_KERNEL);
if (!dspp_offsets) {
rc = -ENOMEM;
goto dspp_alloc_fail;
}
pingpong_offsets = kcalloc(npingpong, sizeof(u32), GFP_KERNEL);
if (!pingpong_offsets) {
rc = -ENOMEM;
goto pingpong_alloc_fail;
}
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-mixer-intf-off",
mixer_offsets, mdata->nmixers_intf);
if (rc)
goto parse_done;
mdata->has_separate_rotator = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-separate-rotator");
if (mdata->nmixers_wb) {
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-mixer-wb-off",
mixer_offsets + mdata->nmixers_intf,
mdata->nmixers_wb);
if (rc)
goto parse_done;
} else if (!mdata->has_separate_rotator) {
/*
* If writeback mixers are not available, put the number of
* writeback mixers equal to number of DMA pipes so that
* later same number of virtual writeback mixers can be
* allocated.
*/
mdata->nmixers_wb = mdata->ndma_pipes;
is_virtual_mixer_req = true;
}
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-dspp-off",
dspp_offsets, mdata->ndspp);
if (rc)
goto parse_done;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-pingpong-off",
pingpong_offsets, npingpong);
if (rc)
goto parse_done;
rc = mdss_mdp_mixer_addr_setup(mdata, mixer_offsets,
dspp_offsets, pingpong_offsets,
MDSS_MDP_MIXER_TYPE_INTF, mdata->nmixers_intf);
if (rc)
goto parse_done;
if (mdata->nmixers_wb) {
if (is_virtual_mixer_req) {
/*
* Replicate last interface mixers based on number of
* dma pipes available as virtual writeback mixers.
*/
rc = mdss_mdp_mixer_addr_setup(mdata, mixer_offsets +
mdata->nmixers_intf - mdata->ndma_pipes,
NULL, NULL, MDSS_MDP_MIXER_TYPE_WRITEBACK,
mdata->nmixers_wb);
if (rc)
goto parse_done;
} else {
rc = mdss_mdp_mixer_addr_setup(mdata, mixer_offsets +
mdata->nmixers_intf, NULL, NULL,
MDSS_MDP_MIXER_TYPE_WRITEBACK,
mdata->nmixers_wb);
if (rc)
goto parse_done;
}
}
parse_done:
kfree(pingpong_offsets);
pingpong_alloc_fail:
kfree(dspp_offsets);
dspp_alloc_fail:
kfree(mixer_offsets);
return rc;
}
static int mdss_mdp_cdm_addr_setup(struct mdss_data_type *mdata,
u32 *cdm_offsets, u32 len)
{
struct mdss_mdp_cdm *head;
u32 i = 0;
head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_cdm) *
len, GFP_KERNEL);
if (!head)
return -ENOMEM;
for (i = 0; i < len; i++) {
head[i].num = i;
head[i].base = (mdata->mdss_io.base) + cdm_offsets[i];
atomic_set(&head[i].kref.refcount, 0);
mutex_init(&head[i].lock);
init_completion(&head[i].free_comp);
pr_debug("%s: cdm off (%d) = %pK\n", __func__, i, head[i].base);
}
mdata->cdm_off = head;
mutex_init(&mdata->cdm_lock);
return 0;
}
static int mdss_mdp_parse_dt_cdm(struct platform_device *pdev)
{
int rc = 0;
u32 *cdm_offsets = NULL;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->ncdm = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-cdm-off");
if (!mdata->ncdm) {
rc = 0;
pr_debug("%s: No CDM offsets present in DT\n", __func__);
goto end;
}
pr_debug("%s: cdm len == %d\n", __func__, mdata->ncdm);
cdm_offsets = kcalloc(mdata->ncdm, sizeof(u32), GFP_KERNEL);
if (!cdm_offsets) {
rc = -ENOMEM;
mdata->ncdm = 0;
goto end;
}
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-cdm-off", cdm_offsets,
mdata->ncdm);
if (rc) {
pr_err("device tree err: failed to get cdm offsets\n");
goto fail;
}
rc = mdss_mdp_cdm_addr_setup(mdata, cdm_offsets, mdata->ncdm);
if (rc) {
pr_err("%s: CDM address setup failed\n", __func__);
goto fail;
}
fail:
kfree(cdm_offsets);
if (rc)
mdata->ncdm = 0;
end:
return rc;
}
static int mdss_mdp_dsc_addr_setup(struct mdss_data_type *mdata,
u32 *dsc_offsets, u32 len)
{
struct mdss_mdp_dsc *head;
u32 i = 0;
head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_dsc) *
len, GFP_KERNEL);
if (!head)
return -ENOMEM;
for (i = 0; i < len; i++) {
head[i].num = i;
head[i].base = (mdata->mdss_io.base) + dsc_offsets[i];
pr_debug("dsc off (%d) = %pK\n", i, head[i].base);
}
mdata->dsc_off = head;
return 0;
}
static int mdss_mdp_parse_dt_dsc(struct platform_device *pdev)
{
int rc = 0;
u32 *dsc_offsets = NULL;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->ndsc = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-dsc-off");
if (!mdata->ndsc) {
rc = 0;
pr_debug("No DSC offsets present in DT\n");
goto end;
}
pr_debug("dsc len == %d\n", mdata->ndsc);
dsc_offsets = kcalloc(mdata->ndsc, sizeof(u32), GFP_KERNEL);
if (!dsc_offsets) {
rc = -ENOMEM;
mdata->ndsc = 0;
goto end;
}
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-dsc-off", dsc_offsets,
mdata->ndsc);
if (rc) {
pr_err("device tree err: failed to get cdm offsets\n");
goto fail;
}
rc = mdss_mdp_dsc_addr_setup(mdata, dsc_offsets, mdata->ndsc);
if (rc) {
pr_err("%s: DSC address setup failed\n", __func__);
goto fail;
}
fail:
kfree(dsc_offsets);
if (rc)
mdata->ndsc = 0;
end:
return rc;
}
static int mdss_mdp_parse_dt_wb(struct platform_device *pdev)
{
int rc = 0;
u32 *wb_offsets = NULL;
u32 num_wb_mixer, nwb_offsets, num_intf_wb = 0;
const char *wfd_data;
struct mdss_data_type *mdata;
mdata = platform_get_drvdata(pdev);
num_wb_mixer = mdata->nmixers_wb;
wfd_data = of_get_property(pdev->dev.of_node,
"qcom,mdss-wfd-mode", NULL);
if (wfd_data && strcmp(wfd_data, "shared") != 0)
num_intf_wb = 1;
nwb_offsets = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-wb-off");
wb_offsets = kcalloc(nwb_offsets, sizeof(u32), GFP_KERNEL);
if (!wb_offsets)
return -ENOMEM;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-wb-off",
wb_offsets, nwb_offsets);
if (rc)
goto wb_parse_done;
rc = mdss_mdp_wb_addr_setup(mdata, num_wb_mixer, num_intf_wb);
if (rc)
goto wb_parse_done;
mdata->nwb_offsets = nwb_offsets;
mdata->wb_offsets = wb_offsets;
return 0;
wb_parse_done:
kfree(wb_offsets);
return rc;
}
static int mdss_mdp_parse_dt_ctl(struct platform_device *pdev)
{
int rc = 0;
u32 *ctl_offsets = NULL;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
mdata->nctl = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-ctl-off");
if (mdata->nctl < mdata->nwb) {
pr_err("device tree err: number of ctl greater than wb\n");
rc = -EINVAL;
goto parse_done;
}
ctl_offsets = kcalloc(mdata->nctl, sizeof(u32), GFP_KERNEL);
if (!ctl_offsets)
return -ENOMEM;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-ctl-off",
ctl_offsets, mdata->nctl);
if (rc)
goto parse_done;
rc = mdss_mdp_ctl_addr_setup(mdata, ctl_offsets, mdata->nctl);
if (rc)
goto parse_done;
parse_done:
kfree(ctl_offsets);
return rc;
}
static int mdss_mdp_parse_dt_video_intf(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 count;
u32 *offsets;
int rc;
count = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-intf-off");
if (count == 0)
return -EINVAL;
offsets = kcalloc(count, sizeof(u32), GFP_KERNEL);
if (!offsets)
return -ENOMEM;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-intf-off",
offsets, count);
if (rc)
goto parse_fail;
rc = mdss_mdp_video_addr_setup(mdata, offsets, count);
if (rc)
pr_err("unable to setup video interfaces\n");
parse_fail:
kfree(offsets);
return rc;
}
static int mdss_mdp_update_smp_map(struct platform_device *pdev,
const u32 *data, int len, int pipe_cnt,
struct mdss_mdp_pipe *pipes)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
int i, j, k;
u32 cnt, mmb;
len /= sizeof(u32);
for (i = 0, k = 0; i < len; k++) {
struct mdss_mdp_pipe *pipe = NULL;
if (k >= pipe_cnt) {
pr_err("invalid fixed mmbs\n");
return -EINVAL;
}
pipe = &pipes[k];
cnt = be32_to_cpu(data[i++]);
if (cnt == 0)
continue;
for (j = 0; j < cnt; j++) {
mmb = be32_to_cpu(data[i++]);
if (mmb > mdata->smp_mb_cnt) {
pr_err("overflow mmb:%d pipe:%d: max:%d\n",
mmb, k, mdata->smp_mb_cnt);
return -EINVAL;
}
set_bit(mmb, pipe->smp_map[0].fixed);
}
if (bitmap_intersects(pipe->smp_map[0].fixed,
mdata->mmb_alloc_map,
mdata->smp_mb_cnt)) {
pr_err("overlapping fixed mmb map\n");
return -EINVAL;
}
bitmap_or(mdata->mmb_alloc_map, pipe->smp_map[0].fixed,
mdata->mmb_alloc_map, mdata->smp_mb_cnt);
}
return 0;
}
static int mdss_mdp_parse_dt_smp(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 num;
u32 data[2];
int rc, len;
const u32 *arr;
num = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-smp-data");
/*
* This property is optional for targets with fix pixel ram. Rest
* must provide no. of smp and size of each block.
*/
if (!num)
return 0;
else if (num != 2)
return -EINVAL;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-smp-data", data, num);
if (rc)
return rc;
rc = mdss_mdp_smp_setup(mdata, data[0], data[1]);
if (rc) {
pr_err("unable to setup smp data\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-smp-mb-per-pipe", data);
mdata->smp_mb_per_pipe = (!rc ? data[0] : 0);
rc = 0;
arr = of_get_property(pdev->dev.of_node,
"qcom,mdss-pipe-rgb-fixed-mmb", &len);
if (arr) {
rc = mdss_mdp_update_smp_map(pdev, arr, len,
mdata->nrgb_pipes, mdata->rgb_pipes);
if (rc)
pr_warn("unable to update smp map for RGB pipes\n");
}
arr = of_get_property(pdev->dev.of_node,
"qcom,mdss-pipe-vig-fixed-mmb", &len);
if (arr) {
rc = mdss_mdp_update_smp_map(pdev, arr, len,
mdata->nvig_pipes, mdata->vig_pipes);
if (rc)
pr_warn("unable to update smp map for VIG pipes\n");
}
return rc;
}
static void mdss_mdp_parse_dt_fudge_factors(struct platform_device *pdev,
char *prop_name, struct mult_factor *ff)
{
int rc;
u32 data[2] = {1, 1};
rc = mdss_mdp_parse_dt_handler(pdev, prop_name, data, 2);
if (rc) {
pr_debug("err reading %s\n", prop_name);
} else {
ff->numer = data[0];
ff->denom = data[1];
}
}
static int mdss_mdp_parse_dt_prefill(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
struct mdss_prefill_data *prefill = &mdata->prefill_data;
int rc;
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-outstanding-buffer-bytes",
&prefill->ot_bytes);
if (rc) {
pr_err("prefill outstanding buffer bytes not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-y-buffer-bytes", &prefill->y_buf_bytes);
if (rc) {
pr_err("prefill y buffer bytes not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-scaler-buffer-lines-bilinear",
&prefill->y_scaler_lines_bilinear);
if (rc) {
pr_err("prefill scaler lines for bilinear not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-scaler-buffer-lines-caf",
&prefill->y_scaler_lines_caf);
if (rc) {
pr_debug("prefill scaler lines for caf not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-post-scaler-buffer-pixels",
&prefill->post_scaler_pixels);
if (rc) {
pr_err("prefill post scaler buffer pixels not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-pingpong-buffer-pixels",
&prefill->pp_pixels);
if (rc) {
pr_err("prefill pingpong buffer lines not specified\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-prefill-fbc-lines", &prefill->fbc_lines);
if (rc)
pr_debug("prefill FBC lines not specified\n");
return 0;
}
static void mdss_mdp_parse_vbif_qos(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
int rc;
mdata->npriority_lvl = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-vbif-qos-rt-setting");
if (mdata->npriority_lvl == MDSS_VBIF_QOS_REMAP_ENTRIES) {
mdata->vbif_rt_qos = kcalloc(mdata->npriority_lvl,
sizeof(u32), GFP_KERNEL);
if (!mdata->vbif_rt_qos)
return;
rc = mdss_mdp_parse_dt_handler(pdev,
"qcom,mdss-vbif-qos-rt-setting",
mdata->vbif_rt_qos, mdata->npriority_lvl);
if (rc) {
pr_debug("rt setting not found\n");
return;
}
} else {
mdata->npriority_lvl = 0;
pr_debug("Invalid or no vbif qos rt setting\n");
return;
}
mdata->npriority_lvl = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-vbif-qos-nrt-setting");
if (mdata->npriority_lvl == MDSS_VBIF_QOS_REMAP_ENTRIES) {
mdata->vbif_nrt_qos = kcalloc(mdata->npriority_lvl,
sizeof(u32), GFP_KERNEL);
if (!mdata->vbif_nrt_qos)
return;
rc = mdss_mdp_parse_dt_handler(pdev,
"qcom,mdss-vbif-qos-nrt-setting", mdata->vbif_nrt_qos,
mdata->npriority_lvl);
if (rc) {
pr_debug("nrt setting not found\n");
return;
}
} else {
mdata->npriority_lvl = 0;
pr_debug("Invalid or no vbif qos nrt seting\n");
}
}
static void mdss_mdp_parse_max_bw_array(const u32 *arr,
struct mdss_max_bw_settings *max_bw_settings, int count)
{
int i;
for (i = 0; i < count; i++) {
max_bw_settings->mdss_max_bw_mode = be32_to_cpu(arr[i*2]);
max_bw_settings->mdss_max_bw_val = be32_to_cpu(arr[(i*2)+1]);
max_bw_settings++;
}
}
static void mdss_mdp_parse_max_bandwidth(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
struct mdss_max_bw_settings *max_bw_settings;
int max_bw_settings_cnt = 0;
const u32 *max_bw;
max_bw = of_get_property(pdev->dev.of_node, "qcom,max-bw-settings",
&max_bw_settings_cnt);
if (!max_bw || !max_bw_settings_cnt) {
pr_debug("MDSS max bandwidth settings not found\n");
return;
}
max_bw_settings_cnt /= 2 * sizeof(u32);
max_bw_settings = devm_kzalloc(&pdev->dev, sizeof(*max_bw_settings)
* max_bw_settings_cnt, GFP_KERNEL);
if (!max_bw_settings)
return;
mdss_mdp_parse_max_bw_array(max_bw, max_bw_settings,
max_bw_settings_cnt);
mdata->max_bw_settings = max_bw_settings;
mdata->max_bw_settings_cnt = max_bw_settings_cnt;
}
static void mdss_mdp_parse_per_pipe_bandwidth(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
struct mdss_max_bw_settings *max_bw_per_pipe_settings;
int max_bw_settings_cnt = 0;
const u32 *max_bw_settings;
u32 max_bw, min_bw, threshold, i = 0;
max_bw_settings = of_get_property(pdev->dev.of_node,
"qcom,max-bandwidth-per-pipe-kbps",
&max_bw_settings_cnt);
if (!max_bw_settings || !max_bw_settings_cnt) {
pr_debug("MDSS per pipe max bandwidth settings not found\n");
return;
}
/* Support targets where a common per pipe max bw is provided */
if ((max_bw_settings_cnt / sizeof(u32)) == 1) {
mdata->max_bw_per_pipe = be32_to_cpu(max_bw_settings[0]);
mdata->max_per_pipe_bw_settings = NULL;
pr_debug("Common per pipe max bandwidth provided\n");
return;
}
max_bw_settings_cnt /= 2 * sizeof(u32);
max_bw_per_pipe_settings = devm_kzalloc(&pdev->dev,
sizeof(struct mdss_max_bw_settings) * max_bw_settings_cnt,
GFP_KERNEL);
if (!max_bw_per_pipe_settings) {
pr_err("Memory allocation failed for max_bw_settings\n");
return;
}
mdss_mdp_parse_max_bw_array(max_bw_settings, max_bw_per_pipe_settings,
max_bw_settings_cnt);
mdata->max_per_pipe_bw_settings = max_bw_per_pipe_settings;
mdata->mdss_per_pipe_bw_cnt = max_bw_settings_cnt;
/* Calculate min and max allowed per pipe BW */
min_bw = mdata->max_bw_high;
max_bw = 0;
while (i < max_bw_settings_cnt) {
threshold = mdata->max_per_pipe_bw_settings[i].mdss_max_bw_val;
if (threshold > max_bw)
max_bw = threshold;
if (threshold < min_bw)
min_bw = threshold;
++i;
}
mdata->max_bw_per_pipe = max_bw;
mdata->min_bw_per_pipe = min_bw;
}
static int mdss_mdp_parse_dt_misc(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 data, slave_pingpong_off;
const char *wfd_data;
int rc;
struct property *prop = NULL;
rc = of_property_read_u32(pdev->dev.of_node, "qcom,mdss-rot-block-size",
&data);
mdata->rot_block_size = (!rc ? data : 128);
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-default-ot-rd-limit", &data);
mdata->default_ot_rd_limit = (!rc ? data : 0);
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-default-ot-wr-limit", &data);
mdata->default_ot_wr_limit = (!rc ? data : 0);
mdata->has_non_scalar_rgb = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-non-scalar-rgb");
mdata->has_bwc = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-bwc");
mdata->has_decimation = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-decimation");
mdata->has_no_lut_read = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-no-lut-read");
mdata->needs_hist_vote = !(of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-no-hist-vote"));
wfd_data = of_get_property(pdev->dev.of_node,
"qcom,mdss-wfd-mode", NULL);
if (wfd_data) {
pr_debug("wfd mode: %s\n", wfd_data);
if (!strcmp(wfd_data, "intf")) {
mdata->wfd_mode = MDSS_MDP_WFD_INTERFACE;
} else if (!strcmp(wfd_data, "shared")) {
mdata->wfd_mode = MDSS_MDP_WFD_SHARED;
} else if (!strcmp(wfd_data, "dedicated")) {
mdata->wfd_mode = MDSS_MDP_WFD_DEDICATED;
} else {
pr_debug("wfd default mode: Shared\n");
mdata->wfd_mode = MDSS_MDP_WFD_SHARED;
}
} else {
pr_warn("wfd mode not configured. Set to default: Shared\n");
mdata->wfd_mode = MDSS_MDP_WFD_SHARED;
}
mdata->has_src_split = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-source-split");
mdata->has_fixed_qos_arbiter_enabled =
of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-fixed-qos-arbiter-enabled");
mdata->idle_pc_enabled = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-idle-power-collapse-enabled");
prop = of_find_property(pdev->dev.of_node, "batfet-supply", NULL);
mdata->batfet_required = prop ? true : false;
mdata->en_svs_high = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-en-svs-high");
if (!mdata->en_svs_high)
pr_debug("%s: svs_high is not enabled\n", __func__);
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-highest-bank-bit", &(mdata->highest_bank_bit));
if (rc)
pr_debug("Could not read optional property: highest bank bit\n");
mdata->has_pingpong_split = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-pingpong-split");
if (mdata->has_pingpong_split) {
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-slave-pingpong-off",
&slave_pingpong_off);
if (rc) {
pr_err("Error in device tree: slave pingpong offset\n");
return rc;
}
mdata->slave_pingpong_base = mdata->mdss_io.base +
slave_pingpong_off;
rc = mdss_mdp_parse_dt_ppb_off(pdev);
if (rc) {
pr_err("Error in device tree: ppb offset not configured\n");
return rc;
}
}
/*
* 2x factor on AB because bus driver will divide by 2
* due to 2x ports to BIMC
*/
mdata->ab_factor.numer = 2;
mdata->ab_factor.denom = 1;
mdss_mdp_parse_dt_fudge_factors(pdev, "qcom,mdss-ab-factor",
&mdata->ab_factor);
/*
* 1.2 factor on ib as default value. This value is
* experimentally determined and should be tuned in device
* tree.
*/
mdata->ib_factor.numer = 6;
mdata->ib_factor.denom = 5;
mdss_mdp_parse_dt_fudge_factors(pdev, "qcom,mdss-ib-factor",
&mdata->ib_factor);
/*
* Set overlap ib value equal to ib by default. This value can
* be tuned in device tree to be different from ib.
* This factor apply when the max bandwidth per pipe
* is the overlap BW.
*/
mdata->ib_factor_overlap.numer = mdata->ib_factor.numer;
mdata->ib_factor_overlap.denom = mdata->ib_factor.denom;
mdss_mdp_parse_dt_fudge_factors(pdev, "qcom,mdss-ib-factor-overlap",
&mdata->ib_factor_overlap);
mdata->clk_factor.numer = 1;
mdata->clk_factor.denom = 1;
mdss_mdp_parse_dt_fudge_factors(pdev, "qcom,mdss-clk-factor",
&mdata->clk_factor);
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,max-bandwidth-low-kbps", &mdata->max_bw_low);
if (rc)
pr_debug("max bandwidth (low) property not specified\n");
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,max-bandwidth-high-kbps", &mdata->max_bw_high);
if (rc)
pr_debug("max bandwidth (high) property not specified\n");
mdss_mdp_parse_per_pipe_bandwidth(pdev);
mdss_mdp_parse_max_bandwidth(pdev);
mdata->nclk_lvl = mdss_mdp_parse_dt_prop_len(pdev,
"qcom,mdss-clk-levels");
if (mdata->nclk_lvl) {
mdata->clock_levels = kcalloc(mdata->nclk_lvl, sizeof(u32),
GFP_KERNEL);
if (!mdata->clock_levels)
return -ENOMEM;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-clk-levels",
mdata->clock_levels, mdata->nclk_lvl);
if (rc)
pr_debug("clock levels not found\n");
}
mdss_mdp_parse_vbif_qos(pdev);
mdata->traffic_shaper_en = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-traffic-shaper-enabled");
mdata->has_rot_dwnscale = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-rotator-downscale");
if (mdata->has_rot_dwnscale) {
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-rot-downscale-min",
&mdata->rot_dwnscale_min);
if (rc)
pr_err("Min rotator downscale property not specified\n");
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-rot-downscale-max",
&mdata->rot_dwnscale_max);
if (rc)
pr_err("Max rotator downscale property not specified\n");
}
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-dram-channels", &mdata->bus_channels);
if (rc)
pr_debug("number of channels property not specified\n");
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,max-pipe-width", &mdata->max_pipe_width);
if (rc) {
pr_debug("max pipe width not specified. Using default value\n");
mdata->max_pipe_width = DEFAULT_MDP_PIPE_WIDTH;
}
return 0;
}
static int mdss_mdp_parse_dt_ad_cfg(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 *ad_offsets = NULL;
int rc;
mdata->nad_cfgs = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-ad-off");
if (mdata->nad_cfgs == 0) {
mdata->ad_cfgs = NULL;
return 0;
}
if (mdata->nad_cfgs > mdata->nmixers_intf)
return -EINVAL;
mdata->has_wb_ad = of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-has-wb-ad");
ad_offsets = kcalloc(mdata->nad_cfgs, sizeof(u32), GFP_KERNEL);
if (!ad_offsets)
return -ENOMEM;
rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-ad-off", ad_offsets,
mdata->nad_cfgs);
if (rc)
goto parse_done;
rc = mdss_mdp_ad_addr_setup(mdata, ad_offsets);
if (rc)
pr_err("unable to setup assertive display\n");
parse_done:
kfree(ad_offsets);
return rc;
}
static int mdss_mdp_parse_dt_ppb_off(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
u32 len, index;
const u32 *arr;
arr = of_get_property(pdev->dev.of_node, "qcom,mdss-ppb-ctl-off", &len);
if (arr) {
mdata->nppb_ctl = len / sizeof(u32);
mdata->ppb_ctl = devm_kzalloc(&mdata->pdev->dev,
sizeof(u32) * mdata->nppb_ctl, GFP_KERNEL);
if (mdata->ppb_ctl == NULL)
return -ENOMEM;
for (index = 0; index < mdata->nppb_ctl; index++)
mdata->ppb_ctl[index] = be32_to_cpu(arr[index]);
}
arr = of_get_property(pdev->dev.of_node, "qcom,mdss-ppb-cfg-off", &len);
if (arr) {
mdata->nppb_cfg = len / sizeof(u32);
mdata->ppb_cfg = devm_kzalloc(&mdata->pdev->dev,
sizeof(u32) * mdata->nppb_cfg, GFP_KERNEL);
if (mdata->ppb_cfg == NULL)
return -ENOMEM;
for (index = 0; index < mdata->nppb_cfg; index++)
mdata->ppb_cfg[index] = be32_to_cpu(arr[index]);
}
return 0;
}
#ifdef CONFIG_QCOM_BUS_SCALING
static int mdss_mdp_parse_dt_bus_scale(struct platform_device *pdev)
{
int rc, paths;
struct device_node *node;
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,msm-bus,num-paths", &paths);
if (rc) {
pr_err("Error. qcom,msm-bus,num-paths prop not found.rc=%d\n",
rc);
return rc;
}
mdss_res->axi_port_cnt = paths;
rc = of_property_read_u32(pdev->dev.of_node,
"qcom,mdss-num-nrt-paths", &mdata->nrt_axi_port_cnt);
if (rc && mdata->has_fixed_qos_arbiter_enabled) {
pr_err("Error. qcom,mdss-num-nrt-paths prop not found.rc=%d\n",
rc);
return rc;
}
rc = 0;
mdata->bus_scale_table = msm_bus_cl_get_pdata(pdev);
if (IS_ERR_OR_NULL(mdata->bus_scale_table)) {
rc = PTR_ERR(mdata->bus_scale_table);
if (!rc)
rc = -EINVAL;
pr_err("msm_bus_cl_get_pdata failed. rc=%d\n", rc);
mdata->bus_scale_table = NULL;
return rc;
}
/*
* if mdss-reg-bus is not found then default table is picked
* hence below code wont return error.
*/
node = of_get_child_by_name(pdev->dev.of_node, "qcom,mdss-reg-bus");
if (node) {
mdata->reg_bus_scale_table =
msm_bus_pdata_from_node(pdev, node);
if (IS_ERR_OR_NULL(mdata->reg_bus_scale_table)) {
rc = PTR_ERR(mdata->reg_bus_scale_table);
if (!rc)
pr_err("bus_pdata reg_bus failed rc=%d\n", rc);
rc = 0;
mdata->reg_bus_scale_table = NULL;
}
} else {
rc = 0;
mdata->reg_bus_scale_table = NULL;
pr_debug("mdss-reg-bus not found\n");
}
node = of_get_child_by_name(pdev->dev.of_node, "qcom,mdss-hw-rt-bus");
if (node) {
mdata->hw_rt_bus_scale_table =
msm_bus_pdata_from_node(pdev, node);
if (IS_ERR_OR_NULL(mdata->hw_rt_bus_scale_table)) {
rc = PTR_ERR(mdata->hw_rt_bus_scale_table);
if (!rc)
pr_err("hw_rt_bus_scale failed rc=%d\n", rc);
rc = 0;
mdata->hw_rt_bus_scale_table = NULL;
}
} else {
rc = 0;
mdata->hw_rt_bus_scale_table = NULL;
pr_debug("mdss-hw-rt-bus not found\n");
}
return rc;
}
#else
__maybe_unused
static int mdss_mdp_parse_dt_bus_scale(struct platform_device *pdev)
{
return 0;
}
#endif
static int mdss_mdp_parse_dt_handler(struct platform_device *pdev,
char *prop_name, u32 *offsets, int len)
{
int rc;
rc = of_property_read_u32_array(pdev->dev.of_node, prop_name,
offsets, len);
if (rc) {
pr_err("Error from prop %s : u32 array read\n", prop_name);
return -EINVAL;
}
return 0;
}
static int mdss_mdp_parse_dt_prop_len(struct platform_device *pdev,
char *prop_name)
{
int len = 0;
of_find_property(pdev->dev.of_node, prop_name, &len);
if (len < 1) {
pr_debug("prop %s : doesn't exist in device tree\n",
prop_name);
return 0;
}
len = len/sizeof(u32);
return len;
}
struct mdss_data_type *mdss_mdp_get_mdata(void)
{
return mdss_res;
}
void mdss_mdp_batfet_ctrl(struct mdss_data_type *mdata, int enable)
{
int ret;
if (!mdata->batfet_required)
return;
if (!mdata->batfet) {
if (enable) {
mdata->batfet = devm_regulator_get(&mdata->pdev->dev,
"batfet");
if (IS_ERR_OR_NULL(mdata->batfet)) {
pr_debug("unable to get batfet reg. rc=%d\n",
PTR_RET(mdata->batfet));
mdata->batfet = NULL;
return;
}
} else {
pr_debug("Batfet regulator disable w/o enable\n");
return;
}
}
if (enable) {
ret = regulator_enable(mdata->batfet);
if (ret)
pr_err("regulator_enable failed\n");
} else {
regulator_disable(mdata->batfet);
}
}
/**
* mdss_is_ready() - checks if mdss is probed and ready
*
* Checks if mdss resources have been initialized
*
* returns true if mdss is ready, else returns false
*/
bool mdss_is_ready(void)
{
return mdss_mdp_get_mdata() ? true : false;
}
EXPORT_SYMBOL(mdss_mdp_get_mdata);
/**
* mdss_panel_intf_type() - checks if a given intf type is primary
* @intf_val: panel interface type of the individual controller
*
* Individual controller queries with MDP to check if it is
* configured as the primary interface.
*
* returns a pointer to the configured structure mdss_panel_cfg
* to the controller that's configured as the primary panel interface.
* returns NULL on error or if @intf_val is not the configured
* controller.
*/
struct mdss_panel_cfg *mdss_panel_intf_type(int intf_val)
{
if (!mdss_res || !mdss_res->pan_cfg.init_done)
return ERR_PTR(-EPROBE_DEFER);
if (mdss_res->pan_cfg.pan_intf == intf_val)
return &mdss_res->pan_cfg;
else
return NULL;
}
EXPORT_SYMBOL(mdss_panel_intf_type);
struct irq_info *mdss_intr_line()
{
return mdss_mdp_hw.irq_info;
}
EXPORT_SYMBOL(mdss_intr_line);
int mdss_mdp_wait_for_xin_halt(u32 xin_id, bool is_vbif_nrt)
{
void __iomem *vbif_base;
u32 status;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
u32 idle_mask = BIT(xin_id);
int rc;
vbif_base = is_vbif_nrt ? mdata->vbif_nrt_io.base :
mdata->vbif_io.base;
rc = readl_poll_timeout(vbif_base + MMSS_VBIF_XIN_HALT_CTRL1,
status, (status & idle_mask),
1000, XIN_HALT_TIMEOUT_US);
if (rc == -ETIMEDOUT) {
pr_err("VBIF client %d not halting. TIMEDOUT.\n",
xin_id);
MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt",
"dbg_bus", "vbif_dbg_bus", "panic");
} else {
pr_debug("VBIF client %d is halted\n", xin_id);
}
return rc;
}
/**
* force_on_xin_clk() - enable/disable the force-on for the pipe clock
* @bit_off: offset of the bit to enable/disable the force-on.
* @reg_off: register offset for the clock control.
* @enable: boolean to indicate if the force-on of the clock needs to be
* enabled or disabled.
*
* This function returns:
* true - if the clock is forced-on by this function
* false - if the clock was already forced on
* It is the caller responsibility to check if this function is forcing
* the clock on; if so, it will need to remove the force of the clock,
* otherwise it should avoid to remove the force-on.
* Clocks must be on when calling this function.
*/
bool force_on_xin_clk(u32 bit_off, u32 clk_ctl_reg_off, bool enable)
{
u32 val;
u32 force_on_mask;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
bool clk_forced_on = false;
force_on_mask = BIT(bit_off);
val = readl_relaxed(mdata->mdp_base + clk_ctl_reg_off);
clk_forced_on = !(force_on_mask & val);
if (true == enable)
val |= force_on_mask;
else
val &= ~force_on_mask;
writel_relaxed(val, mdata->mdp_base + clk_ctl_reg_off);
return clk_forced_on;
}
static void apply_dynamic_ot_limit(u32 *ot_lim,
struct mdss_mdp_set_ot_params *params)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
u32 res, read_vbif_ot;
u32 rot_ot = 4;
if (false == test_bit(MDSS_QOS_OTLIM, mdata->mdss_qos_map))
return;
/* Dynamic OT setting done only for rotator and WFD */
if (!((params->is_rot && params->is_yuv) || params->is_wb))
return;
res = params->width * params->height;
pr_debug("w:%d h:%d rot:%d yuv:%d wb:%d res:%d fps:%d\n",
params->width, params->height, params->is_rot,
params->is_yuv, params->is_wb, res, params->frame_rate);
switch (mdata->mdp_rev) {
case MDSS_MDP_HW_REV_114:
/*
* MDP rev is same for msm8937 and msm8940, but rotator OT
* recommendations are different. Setting it based on AXI OT.
*/
read_vbif_ot = MDSS_VBIF_READ(mdata, MMSS_VBIF_OUT_RD_LIM_CONF0,
false);
rot_ot = (read_vbif_ot == 0x10) ? 4 : 8;
/* fall-through */
case MDSS_MDP_HW_REV_115:
case MDSS_MDP_HW_REV_116:
if ((res <= RES_1080p) && (params->frame_rate <= 30))
*ot_lim = 2;
else if (params->is_rot && params->is_yuv)
*ot_lim = rot_ot;
else
*ot_lim = 6;
break;
default:
if (res <= RES_1080p) {
*ot_lim = 2;
} else if (res <= RES_UHD) {
if (params->is_rot && params->is_yuv)
*ot_lim = 8;
else
*ot_lim = 16;
}
break;
}
}
static u32 get_ot_limit(u32 reg_off, u32 bit_off,
struct mdss_mdp_set_ot_params *params)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
u32 ot_lim = 0;
u32 is_vbif_nrt, val;
if (mdata->default_ot_wr_limit &&
(params->reg_off_vbif_lim_conf == MMSS_VBIF_WR_LIM_CONF))
ot_lim = mdata->default_ot_wr_limit;
else if (mdata->default_ot_rd_limit &&
(params->reg_off_vbif_lim_conf == MMSS_VBIF_RD_LIM_CONF))
ot_lim = mdata->default_ot_rd_limit;
/*
* If default ot is not set from dt,
* then do not configure it.
*/
if (ot_lim == 0)
goto exit;
/* Modify the limits if the target and the use case requires it */
apply_dynamic_ot_limit(&ot_lim, params);
is_vbif_nrt = params->is_vbif_nrt;
val = MDSS_VBIF_READ(mdata, reg_off, is_vbif_nrt);
val &= (0xFF << bit_off);
val = val >> bit_off;
if (val == ot_lim)
ot_lim = 0;
exit:
pr_debug("ot_lim=%d\n", ot_lim);
return ot_lim;
}
void mdss_mdp_set_ot_limit(struct mdss_mdp_set_ot_params *params)
{
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
u32 ot_lim;
u32 reg_off_vbif_lim_conf = (params->xin_id / 4) * 4 +
params->reg_off_vbif_lim_conf;
u32 bit_off_vbif_lim_conf = (params->xin_id % 4) * 8;
bool is_vbif_nrt = params->is_vbif_nrt;
u32 reg_val;
bool forced_on;
ot_lim = get_ot_limit(
reg_off_vbif_lim_conf,
bit_off_vbif_lim_conf,
params) & 0xFF;
if (ot_lim == 0)
goto exit;
trace_mdp_perf_set_ot(params->num, params->xin_id, ot_lim,
is_vbif_nrt);
mutex_lock(&mdata->reg_lock);
forced_on = force_on_xin_clk(params->bit_off_mdp_clk_ctrl,
params->reg_off_mdp_clk_ctrl, true);
reg_val = MDSS_VBIF_READ(mdata, reg_off_vbif_lim_conf,
is_vbif_nrt);
reg_val &= ~(0xFF << bit_off_vbif_lim_conf);
reg_val |= (ot_lim) << bit_off_vbif_lim_conf;
MDSS_VBIF_WRITE(mdata, reg_off_vbif_lim_conf, reg_val,
is_vbif_nrt);
reg_val = MDSS_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL0,
is_vbif_nrt);
MDSS_VBIF_WRITE(mdata, MMSS_VBIF_XIN_HALT_CTRL0,
reg_val | BIT(params->xin_id), is_vbif_nrt);
mutex_unlock(&mdata->reg_lock);
mdss_mdp_wait_for_xin_halt(params->xin_id, is_vbif_nrt);
mutex_lock(&mdata->reg_lock);
reg_val = MDSS_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL0,
is_vbif_nrt);
MDSS_VBIF_WRITE(mdata, MMSS_VBIF_XIN_HALT_CTRL0,
reg_val & ~BIT(params->xin_id), is_vbif_nrt);
if (forced_on)
force_on_xin_clk(params->bit_off_mdp_clk_ctrl,
params->reg_off_mdp_clk_ctrl, false);
mutex_unlock(&mdata->reg_lock);
exit:
return;
}
#define RPM_MISC_REQ_TYPE 0x6373696d
#define RPM_MISC_REQ_SVS_PLUS_KEY 0x2B737673
static void mdss_mdp_config_cx_voltage(struct mdss_data_type *mdata, int enable)
{
int ret = 0;
static struct msm_rpm_kvp rpm_kvp;
static uint8_t svs_en;
if (!mdata->en_svs_high)
return;
if (!rpm_kvp.key) {
rpm_kvp.key = RPM_MISC_REQ_SVS_PLUS_KEY;
rpm_kvp.length = sizeof(uint64_t);
pr_debug("%s: Initialized rpm_kvp structure\n", __func__);
}
if (enable) {
svs_en = 1;
rpm_kvp.data = &svs_en;
pr_debug("%s: voting for svs high\n", __func__);
ret = msm_rpm_send_message(MSM_RPM_CTX_ACTIVE_SET,
RPM_MISC_REQ_TYPE, 0,
&rpm_kvp, 1);
if (ret)
pr_err("vote for active_set svs high failed: %d\n",
ret);
ret = msm_rpm_send_message(MSM_RPM_CTX_SLEEP_SET,
RPM_MISC_REQ_TYPE, 0,
&rpm_kvp, 1);
if (ret)
pr_err("vote for sleep_set svs high failed: %d\n",
ret);
} else {
svs_en = 0;
rpm_kvp.data = &svs_en;
pr_debug("%s: Removing vote for svs high\n", __func__);
ret = msm_rpm_send_message(MSM_RPM_CTX_ACTIVE_SET,
RPM_MISC_REQ_TYPE, 0,
&rpm_kvp, 1);
if (ret)
pr_err("Remove vote:active_set svs high failed: %d\n",
ret);
ret = msm_rpm_send_message(MSM_RPM_CTX_SLEEP_SET,
RPM_MISC_REQ_TYPE, 0,
&rpm_kvp, 1);
if (ret)
pr_err("Remove vote:sleep_set svs high failed: %d\n",
ret);
}
}
static int mdss_mdp_cx_ctrl(struct mdss_data_type *mdata, int enable)
{
int rc = 0;
if (!mdata->vdd_cx)
return rc;
if (enable) {
rc = regulator_set_voltage(
mdata->vdd_cx,
RPM_REGULATOR_CORNER_SVS_SOC,
RPM_REGULATOR_CORNER_SUPER_TURBO);
if (rc < 0)
goto vreg_set_voltage_fail;
pr_debug("Enabling CX power rail\n");
rc = regulator_enable(mdata->vdd_cx);
if (rc) {
pr_err("Failed to enable regulator.\n");
return rc;
}
} else {
pr_debug("Disabling CX power rail\n");
rc = regulator_disable(mdata->vdd_cx);
if (rc) {
pr_err("Failed to disable regulator.\n");
return rc;
}
rc = regulator_set_voltage(
mdata->vdd_cx,
RPM_REGULATOR_CORNER_NONE,
RPM_REGULATOR_CORNER_SUPER_TURBO);
if (rc < 0)
goto vreg_set_voltage_fail;
}
return rc;
vreg_set_voltage_fail:
pr_err("Set vltg fail\n");
return rc;
}
/**
* mdss_mdp_footswitch_ctrl() - Disable/enable MDSS GDSC and CX/Batfet rails
* @mdata: MDP private data
* @on: 1 to turn on footswitch, 0 to turn off footswitch
*
* When no active references to the MDP device node and it's child nodes are
* held, MDSS GDSC can be turned off. However, any any panels are still
* active (but likely in an idle state), the vote for the CX and the batfet
* rails should not be released.
*/
static void mdss_mdp_footswitch_ctrl(struct mdss_data_type *mdata, int on)
{
int ret;
int active_cnt = 0;
if (!mdata->fs)
return;
MDSS_XLOG(on, mdata->fs_ena, mdata->idle_pc, mdata->en_svs_high,
atomic_read(&mdata->active_intf_cnt));
if (on) {
if (!mdata->fs_ena) {
pr_debug("Enable MDP FS\n");
if (mdata->venus) {
ret = regulator_enable(mdata->venus);
if (ret)
pr_err("venus failed to enable\n");
}
ret = regulator_enable(mdata->fs);
if (ret)
pr_warn("Footswitch failed to enable\n");
if (!mdata->idle_pc) {
mdss_mdp_cx_ctrl(mdata, true);
mdss_mdp_batfet_ctrl(mdata, true);
}
}
if (mdata->en_svs_high)
mdss_mdp_config_cx_voltage(mdata, true);
mdata->fs_ena = true;
} else {
if (mdata->fs_ena) {
pr_debug("Disable MDP FS\n");
active_cnt = atomic_read(&mdata->active_intf_cnt);
if (active_cnt != 0) {
/*
* Turning off GDSC while overlays are still
* active.
*/
mdata->idle_pc = true;
pr_debug("idle pc. active overlays=%d\n",
active_cnt);
mdss_mdp_memory_retention_enter();
} else {
mdss_mdp_cx_ctrl(mdata, false);
mdss_mdp_batfet_ctrl(mdata, false);
}
if (mdata->en_svs_high)
mdss_mdp_config_cx_voltage(mdata, false);
regulator_disable(mdata->fs);
if (mdata->venus)
regulator_disable(mdata->venus);
}
mdata->fs_ena = false;
}
}
int mdss_mdp_secure_display_ctrl(struct mdss_data_type *mdata,
unsigned int enable)
{
struct sd_ctrl_req {
unsigned int enable;
} __attribute__ ((__packed__)) request;
unsigned int resp = -1;
int ret = 0;
struct scm_desc desc;
if ((enable && (mdss_get_sd_client_cnt() > 0)) ||
(!enable && (mdss_get_sd_client_cnt() > 1))) {
mdss_update_sd_client(mdata, enable);
return ret;
}
desc.args[0] = request.enable = enable;
desc.arginfo = SCM_ARGS(1);
if (!is_scm_armv8()) {
ret = scm_call(SCM_SVC_MP, MEM_PROTECT_SD_CTRL,
&request, sizeof(request), &resp, sizeof(resp));
} else {
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
mem_protect_sd_ctrl_id), &desc);
resp = desc.ret[0];
}
pr_debug("scm_call MEM_PROTECT_SD_CTRL(%u): ret=%d, resp=%x",
enable, ret, resp);
if (ret)
return ret;
mdss_update_sd_client(mdata, enable);
return resp;
}
static inline int mdss_mdp_suspend_sub(struct mdss_data_type *mdata)
{
mdata->suspend_fs_ena = mdata->fs_ena;
mdss_mdp_footswitch_ctrl(mdata, false);
pr_debug("suspend done fs=%d\n", mdata->suspend_fs_ena);
return 0;
}
static inline int mdss_mdp_resume_sub(struct mdss_data_type *mdata)
{
if (mdata->suspend_fs_ena)
mdss_mdp_footswitch_ctrl(mdata, true);
pr_debug("resume done fs=%d\n", mdata->suspend_fs_ena);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int mdss_mdp_pm_suspend(struct device *dev)
{
struct mdss_data_type *mdata;
mdata = dev_get_drvdata(dev);
if (!mdata)
return -ENODEV;
dev_dbg(dev, "display pm suspend\n");
return mdss_mdp_suspend_sub(mdata);
}
static int mdss_mdp_pm_resume(struct device *dev)
{
struct mdss_data_type *mdata;
mdata = dev_get_drvdata(dev);
if (!mdata)
return -ENODEV;
dev_dbg(dev, "display pm resume\n");
/*
* It is possible that the runtime status of the mdp device may
* have been active when the system was suspended. Reset the runtime
* status to suspended state after a complete system resume.
*/
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
pm_runtime_enable(dev);
return mdss_mdp_resume_sub(mdata);
}
#endif
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
static int mdss_mdp_suspend(struct platform_device *pdev, pm_message_t state)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
if (!mdata)
return -ENODEV;
dev_dbg(&pdev->dev, "display suspend\n");
return mdss_mdp_suspend_sub(mdata);
}
static int mdss_mdp_resume(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
if (!mdata)
return -ENODEV;
dev_dbg(&pdev->dev, "display resume\n");
return mdss_mdp_resume_sub(mdata);
}
#else
#define mdss_mdp_suspend NULL
#define mdss_mdp_resume NULL
#endif
#ifdef CONFIG_PM
static int mdss_mdp_runtime_resume(struct device *dev)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
bool device_on = true;
if (!mdata)
return -ENODEV;
dev_dbg(dev, "pm_runtime: resuming. active overlay cnt=%d\n",
atomic_read(&mdata->active_intf_cnt));
/* do not resume panels when coming out of idle power collapse */
if (!mdata->idle_pc)
device_for_each_child(dev, &device_on, mdss_fb_suspres_panel);
mdss_mdp_footswitch_ctrl(mdata, true);
return 0;
}
static int mdss_mdp_runtime_idle(struct device *dev)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
if (!mdata)
return -ENODEV;
dev_dbg(dev, "pm_runtime: idling...\n");
return 0;
}
static int mdss_mdp_runtime_suspend(struct device *dev)
{
struct mdss_data_type *mdata = dev_get_drvdata(dev);
bool device_on = false;
if (!mdata)
return -ENODEV;
dev_dbg(dev, "pm_runtime: suspending. active overlay cnt=%d\n",
atomic_read(&mdata->active_intf_cnt));
if (mdata->clk_ena) {
pr_err("MDP suspend failed\n");
return -EBUSY;
}
mdss_mdp_footswitch_ctrl(mdata, false);
/* do not suspend panels when going in to idle power collapse */
if (!mdata->idle_pc)
device_for_each_child(dev, &device_on, mdss_fb_suspres_panel);
return 0;
}
#endif
static const struct dev_pm_ops mdss_mdp_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mdss_mdp_pm_suspend, mdss_mdp_pm_resume)
#ifdef CONFIG_PM
SET_RUNTIME_PM_OPS(mdss_mdp_runtime_suspend,
mdss_mdp_runtime_resume,
mdss_mdp_runtime_idle)
#endif
};
static int mdss_mdp_remove(struct platform_device *pdev)
{
struct mdss_data_type *mdata = platform_get_drvdata(pdev);
if (!mdata)
return -ENODEV;
pm_runtime_disable(&pdev->dev);
mdss_mdp_pp_term(&pdev->dev);
mdss_mdp_bus_scale_unregister(mdata);
mdss_debugfs_remove(mdata);
if (mdata->regulator_notif_register)
regulator_unregister_notifier(mdata->fs, &(mdata->gdsc_cb));
return 0;
}
static const struct of_device_id mdss_mdp_dt_match[] = {
{ .compatible = "qcom,mdss_mdp",},
{}
};
MODULE_DEVICE_TABLE(of, mdss_mdp_dt_match);
static struct platform_driver mdss_mdp_driver = {
.probe = mdss_mdp_probe,
.remove = mdss_mdp_remove,
.suspend = mdss_mdp_suspend,
.resume = mdss_mdp_resume,
.shutdown = NULL,
.driver = {
/*
* Driver name must match the device name added in
* platform.c.
*/
.name = "mdp",
.of_match_table = mdss_mdp_dt_match,
.pm = &mdss_mdp_pm_ops,
},
};
static int mdss_mdp_register_driver(void)
{
return platform_driver_register(&mdss_mdp_driver);
}
static int __init mdss_mdp_driver_init(void)
{
int ret;
ret = mdss_mdp_register_driver();
if (ret) {
pr_err("mdp_register_driver() failed!\n");
return ret;
}
return 0;
}
module_param_string(panel, mdss_mdp_panel, MDSS_MAX_PANEL_LEN, 0600);
/*
* panel=<lk_cfg>:<pan_intf>:<pan_intf_cfg>:<panel_topology_cfg>
* where <lk_cfg> is "1"-lk/gcdb config or "0" non-lk/non-gcdb
* config; <pan_intf> is dsi:<ctrl_id> or hdmi or edp
* <pan_intf_cfg> is panel interface specific string
* Ex: This string is panel's device node name from DT
* for DSI interface
* hdmi/edp interface does not use this string
* <panel_topology_cfg> is an optional string. Currently it is
* only valid for DSI panels. In dual-DSI case, it needs to be
* used on both panels or none. When used, format is config%d
* where %d is one of the configuration found in device node of
* panel selected by <pan_intf_cfg>
*/
MODULE_PARM_DESC(panel, "lk supplied panel selection string");
MODULE_PARM_DESC(panel,
"panel=<lk_cfg>:<pan_intf>:<pan_intf_cfg>:<panel_topology_cfg>");
module_init(mdss_mdp_driver_init);