Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
*/
#ifndef __MDP5_CFG_H__
#define __MDP5_CFG_H__
#include "msm_drv.h"
/*
* mdp5_cfg
*
* This module configures the dynamic offsets used by mdp5.xml.h
* (initialized in mdp5_cfg.c)
*/
extern const struct mdp5_cfg_hw *mdp5_cfg;
#define MAX_CTL 8
#define MAX_BASES 8
#define MAX_SMP_BLOCKS 44
#define MAX_CLIENTS 32
typedef DECLARE_BITMAP(mdp5_smp_state_t, MAX_SMP_BLOCKS);
#define MDP5_SUB_BLOCK_DEFINITION \
unsigned int count; \
uint32_t base[MAX_BASES]
struct mdp5_sub_block {
MDP5_SUB_BLOCK_DEFINITION;
};
struct mdp5_lm_instance {
int id;
int pp;
int dspp;
uint32_t caps;
};
struct mdp5_lm_block {
MDP5_SUB_BLOCK_DEFINITION;
struct mdp5_lm_instance instances[MAX_BASES];
uint32_t nb_stages; /* number of stages per blender */
uint32_t max_width; /* Maximum output resolution */
uint32_t max_height;
};
struct mdp5_pipe_block {
MDP5_SUB_BLOCK_DEFINITION;
uint32_t caps; /* pipe capabilities */
};
struct mdp5_ctl_block {
MDP5_SUB_BLOCK_DEFINITION;
uint32_t flush_hw_mask; /* FLUSH register's hardware mask */
};
struct mdp5_smp_block {
int mmb_count; /* number of SMP MMBs */
int mmb_size; /* MMB: size in bytes */
uint32_t clients[MAX_CLIENTS]; /* SMP port allocation /pipe */
mdp5_smp_state_t reserved_state;/* SMP MMBs statically allocated */
uint8_t reserved[MAX_CLIENTS]; /* # of MMBs allocated per client */
};
struct mdp5_mdp_block {
MDP5_SUB_BLOCK_DEFINITION;
uint32_t caps; /* MDP capabilities: MDP_CAP_xxx bits */
};
struct mdp5_wb_instance {
int id;
int lm;
};
struct mdp5_wb_block {
MDP5_SUB_BLOCK_DEFINITION;
struct mdp5_wb_instance instances[MAX_BASES];
};
#define MDP5_INTF_NUM_MAX 5
struct mdp5_intf_block {
uint32_t base[MAX_BASES];
u32 connect[MDP5_INTF_NUM_MAX]; /* array of enum mdp5_intf_type */
};
struct mdp5_perf_block {
u32 ab_inefficiency;
u32 ib_inefficiency;
u32 clk_inefficiency;
};
struct mdp5_cfg_hw {
char *name;
struct mdp5_mdp_block mdp;
struct mdp5_smp_block smp;
struct mdp5_ctl_block ctl;
struct mdp5_pipe_block pipe_vig;
struct mdp5_pipe_block pipe_rgb;
struct mdp5_pipe_block pipe_dma;
struct mdp5_pipe_block pipe_cursor;
struct mdp5_lm_block lm;
struct mdp5_sub_block dspp;
struct mdp5_sub_block ad;
struct mdp5_sub_block pp;
struct mdp5_sub_block dsc;
struct mdp5_sub_block cdm;
struct mdp5_wb_block wb;
struct mdp5_intf_block intf;
struct mdp5_perf_block perf;
uint32_t max_clk;
};
struct mdp5_cfg {
const struct mdp5_cfg_hw *hw;
};
struct mdp5_kms;
struct mdp5_cfg_handler;
const struct mdp5_cfg_hw *mdp5_cfg_get_hw_config(struct mdp5_cfg_handler *cfg_hnd);
struct mdp5_cfg *mdp5_cfg_get_config(struct mdp5_cfg_handler *cfg_hnd);
int mdp5_cfg_get_hw_rev(struct mdp5_cfg_handler *cfg_hnd);
#define mdp5_cfg_intf_is_virtual(intf_type) ({ \
typeof(intf_type) __val = (intf_type); \
(__val) >= INTF_VIRTUAL ? true : false; })
struct mdp5_cfg_handler *mdp5_cfg_init(struct mdp5_kms *mdp5_kms,
uint32_t major, uint32_t minor);
#endif /* __MDP5_CFG_H__ */
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
#include <drm/drm_crtc.h>
#include <drm/drm_probe_helper.h>
#include "mdp5_kms.h"
#ifdef CONFIG_DRM_MSM_DSI
static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
#define VSYNC_CLK_RATE 19200000
static int pingpong_tearcheck_setup(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct device *dev = encoder->dev->dev;
u32 total_lines, vclks_line, cfg;
long vsync_clk_speed;
struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc);
int pp_id = mixer->pp;
if (IS_ERR_OR_NULL(mdp5_kms->vsync_clk)) {
DRM_DEV_ERROR(dev, "vsync_clk is not initialized\n");
return -EINVAL;
}
total_lines = mode->vtotal * drm_mode_vrefresh(mode);
if (!total_lines) {
DRM_DEV_ERROR(dev, "%s: vtotal(%d) or vrefresh(%d) is 0\n",
__func__, mode->vtotal, drm_mode_vrefresh(mode));
return -EINVAL;
}
vsync_clk_speed = clk_round_rate(mdp5_kms->vsync_clk, VSYNC_CLK_RATE);
if (vsync_clk_speed <= 0) {
DRM_DEV_ERROR(dev, "vsync_clk round rate failed %ld\n",
vsync_clk_speed);
return -EINVAL;
}
vclks_line = vsync_clk_speed / total_lines;
cfg = MDP5_PP_SYNC_CONFIG_VSYNC_COUNTER_EN
| MDP5_PP_SYNC_CONFIG_VSYNC_IN_EN;
cfg |= MDP5_PP_SYNC_CONFIG_VSYNC_COUNT(vclks_line);
/*
* Tearcheck emits a blanking signal every vclks_line * vtotal * 2 ticks on
* the vsync_clk equating to roughly half the desired panel refresh rate.
* This is only necessary as stability fallback if interrupts from the
* panel arrive too late or not at all, but is currently used by default
* because these panel interrupts are not wired up yet.
*/
mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_CONFIG_VSYNC(pp_id), cfg);
mdp5_write(mdp5_kms,
REG_MDP5_PP_SYNC_CONFIG_HEIGHT(pp_id), (2 * mode->vtotal));
mdp5_write(mdp5_kms,
REG_MDP5_PP_VSYNC_INIT_VAL(pp_id), mode->vdisplay);
mdp5_write(mdp5_kms, REG_MDP5_PP_RD_PTR_IRQ(pp_id), mode->vdisplay + 1);
mdp5_write(mdp5_kms, REG_MDP5_PP_START_POS(pp_id), mode->vdisplay);
mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_THRESH(pp_id),
MDP5_PP_SYNC_THRESH_START(4) |
MDP5_PP_SYNC_THRESH_CONTINUE(4));
mdp5_write(mdp5_kms, REG_MDP5_PP_AUTOREFRESH_CONFIG(pp_id), 0x0);
return 0;
}
static int pingpong_tearcheck_enable(struct drm_encoder *encoder)
{
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc);
int pp_id = mixer->pp;
int ret;
ret = clk_set_rate(mdp5_kms->vsync_clk,
clk_round_rate(mdp5_kms->vsync_clk, VSYNC_CLK_RATE));
if (ret) {
DRM_DEV_ERROR(encoder->dev->dev,
"vsync_clk clk_set_rate failed, %d\n", ret);
return ret;
}
ret = clk_prepare_enable(mdp5_kms->vsync_clk);
if (ret) {
DRM_DEV_ERROR(encoder->dev->dev,
"vsync_clk clk_prepare_enable failed, %d\n", ret);
return ret;
}
mdp5_write(mdp5_kms, REG_MDP5_PP_TEAR_CHECK_EN(pp_id), 1);
return 0;
}
static void pingpong_tearcheck_disable(struct drm_encoder *encoder)
{
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc);
int pp_id = mixer->pp;
mdp5_write(mdp5_kms, REG_MDP5_PP_TEAR_CHECK_EN(pp_id), 0);
clk_disable_unprepare(mdp5_kms->vsync_clk);
}
void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
mode = adjusted_mode;
DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode));
pingpong_tearcheck_setup(encoder, mode);
mdp5_crtc_set_pipeline(encoder->crtc);
}
void mdp5_cmd_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder);
struct mdp5_ctl *ctl = mdp5_cmd_enc->ctl;
struct mdp5_interface *intf = mdp5_cmd_enc->intf;
struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc);
if (WARN_ON(!mdp5_cmd_enc->enabled))
return;
pingpong_tearcheck_disable(encoder);
mdp5_ctl_set_encoder_state(ctl, pipeline, false);
mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true);
mdp5_cmd_enc->enabled = false;
}
void mdp5_cmd_encoder_enable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder);
struct mdp5_ctl *ctl = mdp5_cmd_enc->ctl;
struct mdp5_interface *intf = mdp5_cmd_enc->intf;
struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc);
if (WARN_ON(mdp5_cmd_enc->enabled))
return;
if (pingpong_tearcheck_enable(encoder))
return;
mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true);
mdp5_ctl_set_encoder_state(ctl, pipeline, true);
mdp5_cmd_enc->enabled = true;
}
#endif /* CONFIG_DRM_MSM_DSI */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,662 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2015 The Linux Foundation. All rights reserved.
*/
#include <linux/string_choices.h>
#include "mdp5_kms.h"
#include "mdp5_ctl.h"
/*
* CTL - MDP Control Pool Manager
*
* Controls are shared between all display interfaces.
*
* They are intended to be used for data path configuration.
* The top level register programming describes the complete data path for
* a specific data path ID - REG_MDP5_CTL_*(<id>, ...)
*
* Hardware capabilities determine the number of concurrent data paths
*/
#define CTL_STAT_BUSY 0x1
#define CTL_STAT_BOOKED 0x2
struct mdp5_ctl {
struct mdp5_ctl_manager *ctlm;
u32 id;
/* CTL status bitmask */
u32 status;
bool encoder_enabled;
/* pending flush_mask bits */
u32 flush_mask;
/* REG_MDP5_CTL_*(<id>) registers access info + lock: */
spinlock_t hw_lock;
u32 reg_offset;
/* when do CTL registers need to be flushed? (mask of trigger bits) */
u32 pending_ctl_trigger;
bool cursor_on;
};
struct mdp5_ctl_manager {
struct drm_device *dev;
/* number of CTL / Layer Mixers in this hw config: */
u32 nlm;
u32 nctl;
/* to filter out non-present bits in the current hardware config */
u32 flush_hw_mask;
/* pool of CTLs + lock to protect resource allocation (ctls[i].busy) */
spinlock_t pool_lock;
struct mdp5_ctl ctls[MAX_CTL];
};
static inline
struct mdp5_kms *get_kms(struct mdp5_ctl_manager *ctl_mgr)
{
struct msm_drm_private *priv = ctl_mgr->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
static inline
void ctl_write(struct mdp5_ctl *ctl, u32 reg, u32 data)
{
struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm);
(void)ctl->reg_offset; /* TODO use this instead of mdp5_write */
mdp5_write(mdp5_kms, reg, data);
}
static inline
u32 ctl_read(struct mdp5_ctl *ctl, u32 reg)
{
struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm);
(void)ctl->reg_offset; /* TODO use this instead of mdp5_write */
return mdp5_read(mdp5_kms, reg);
}
static void set_display_intf(struct mdp5_kms *mdp5_kms,
struct mdp5_interface *intf)
{
unsigned long flags;
u32 intf_sel;
spin_lock_irqsave(&mdp5_kms->resource_lock, flags);
intf_sel = mdp5_read(mdp5_kms, REG_MDP5_DISP_INTF_SEL);
switch (intf->num) {
case 0:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF0__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF0(intf->type);
break;
case 1:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF1__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF1(intf->type);
break;
case 2:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF2__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF2(intf->type);
break;
case 3:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF3__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF3(intf->type);
break;
default:
BUG();
break;
}
mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, intf_sel);
spin_unlock_irqrestore(&mdp5_kms->resource_lock, flags);
}
static void set_ctl_op(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline)
{
unsigned long flags;
struct mdp5_interface *intf = pipeline->intf;
u32 ctl_op = 0;
if (!mdp5_cfg_intf_is_virtual(intf->type))
ctl_op |= MDP5_CTL_OP_INTF_NUM(INTF0 + intf->num);
switch (intf->type) {
case INTF_DSI:
if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)
ctl_op |= MDP5_CTL_OP_CMD_MODE;
break;
case INTF_WB:
if (intf->mode == MDP5_INTF_WB_MODE_LINE)
ctl_op |= MDP5_CTL_OP_MODE(MODE_WB_2_LINE);
break;
default:
break;
}
if (pipeline->r_mixer)
ctl_op |= MDP5_CTL_OP_PACK_3D_ENABLE |
MDP5_CTL_OP_PACK_3D(1);
spin_lock_irqsave(&ctl->hw_lock, flags);
ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id), ctl_op);
spin_unlock_irqrestore(&ctl->hw_lock, flags);
}
int mdp5_ctl_set_pipeline(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline)
{
struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm);
struct mdp5_interface *intf = pipeline->intf;
/* Virtual interfaces need not set a display intf (e.g.: Writeback) */
if (!mdp5_cfg_intf_is_virtual(intf->type))
set_display_intf(mdp5_kms, intf);
set_ctl_op(ctl, pipeline);
return 0;
}
static bool start_signal_needed(struct mdp5_ctl *ctl,
struct mdp5_pipeline *pipeline)
{
struct mdp5_interface *intf = pipeline->intf;
if (!ctl->encoder_enabled)
return false;
switch (intf->type) {
case INTF_WB:
return true;
case INTF_DSI:
return intf->mode == MDP5_INTF_DSI_MODE_COMMAND;
default:
return false;
}
}
/*
* send_start_signal() - Overlay Processor Start Signal
*
* For a given control operation (display pipeline), a START signal needs to be
* executed in order to kick off operation and activate all layers.
* e.g.: DSI command mode, Writeback
*/
static void send_start_signal(struct mdp5_ctl *ctl)
{
unsigned long flags;
spin_lock_irqsave(&ctl->hw_lock, flags);
ctl_write(ctl, REG_MDP5_CTL_START(ctl->id), 1);
spin_unlock_irqrestore(&ctl->hw_lock, flags);
}
/**
* mdp5_ctl_set_encoder_state() - set the encoder state
*
* @ctl: the CTL instance
* @pipeline: the encoder's INTF + MIXER configuration
* @enabled: true, when encoder is ready for data streaming; false, otherwise.
*
* Note:
* This encoder state is needed to trigger START signal (data path kickoff).
*/
int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl,
struct mdp5_pipeline *pipeline,
bool enabled)
{
struct mdp5_interface *intf = pipeline->intf;
if (WARN_ON(!ctl))
return -EINVAL;
ctl->encoder_enabled = enabled;
DBG("intf_%d: %s", intf->num, str_on_off(enabled));
if (start_signal_needed(ctl, pipeline)) {
send_start_signal(ctl);
}
return 0;
}
/*
* Note:
* CTL registers need to be flushed after calling this function
* (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask)
*/
int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
int cursor_id, bool enable)
{
struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
unsigned long flags;
u32 blend_cfg;
struct mdp5_hw_mixer *mixer = pipeline->mixer;
if (WARN_ON(!mixer)) {
DRM_DEV_ERROR(ctl_mgr->dev->dev, "CTL %d cannot find LM",
ctl->id);
return -EINVAL;
}
if (pipeline->r_mixer) {
DRM_DEV_ERROR(ctl_mgr->dev->dev, "unsupported configuration");
return -EINVAL;
}
spin_lock_irqsave(&ctl->hw_lock, flags);
blend_cfg = ctl_read(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm));
if (enable)
blend_cfg |= MDP5_CTL_LAYER_REG_CURSOR_OUT;
else
blend_cfg &= ~MDP5_CTL_LAYER_REG_CURSOR_OUT;
ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm), blend_cfg);
ctl->cursor_on = enable;
spin_unlock_irqrestore(&ctl->hw_lock, flags);
ctl->pending_ctl_trigger = mdp_ctl_flush_mask_cursor(cursor_id);
return 0;
}
static u32 mdp_ctl_blend_mask(enum mdp5_pipe pipe,
enum mdp_mixer_stage_id stage)
{
switch (pipe) {
case SSPP_VIG0: return MDP5_CTL_LAYER_REG_VIG0(stage);
case SSPP_VIG1: return MDP5_CTL_LAYER_REG_VIG1(stage);
case SSPP_VIG2: return MDP5_CTL_LAYER_REG_VIG2(stage);
case SSPP_RGB0: return MDP5_CTL_LAYER_REG_RGB0(stage);
case SSPP_RGB1: return MDP5_CTL_LAYER_REG_RGB1(stage);
case SSPP_RGB2: return MDP5_CTL_LAYER_REG_RGB2(stage);
case SSPP_DMA0: return MDP5_CTL_LAYER_REG_DMA0(stage);
case SSPP_DMA1: return MDP5_CTL_LAYER_REG_DMA1(stage);
case SSPP_VIG3: return MDP5_CTL_LAYER_REG_VIG3(stage);
case SSPP_RGB3: return MDP5_CTL_LAYER_REG_RGB3(stage);
case SSPP_CURSOR0:
case SSPP_CURSOR1:
default: return 0;
}
}
static u32 mdp_ctl_blend_ext_mask(enum mdp5_pipe pipe,
enum mdp_mixer_stage_id stage)
{
if (stage < STAGE6 && (pipe != SSPP_CURSOR0 && pipe != SSPP_CURSOR1))
return 0;
switch (pipe) {
case SSPP_VIG0: return MDP5_CTL_LAYER_EXT_REG_VIG0_BIT3;
case SSPP_VIG1: return MDP5_CTL_LAYER_EXT_REG_VIG1_BIT3;
case SSPP_VIG2: return MDP5_CTL_LAYER_EXT_REG_VIG2_BIT3;
case SSPP_RGB0: return MDP5_CTL_LAYER_EXT_REG_RGB0_BIT3;
case SSPP_RGB1: return MDP5_CTL_LAYER_EXT_REG_RGB1_BIT3;
case SSPP_RGB2: return MDP5_CTL_LAYER_EXT_REG_RGB2_BIT3;
case SSPP_DMA0: return MDP5_CTL_LAYER_EXT_REG_DMA0_BIT3;
case SSPP_DMA1: return MDP5_CTL_LAYER_EXT_REG_DMA1_BIT3;
case SSPP_VIG3: return MDP5_CTL_LAYER_EXT_REG_VIG3_BIT3;
case SSPP_RGB3: return MDP5_CTL_LAYER_EXT_REG_RGB3_BIT3;
case SSPP_CURSOR0: return MDP5_CTL_LAYER_EXT_REG_CURSOR0(stage);
case SSPP_CURSOR1: return MDP5_CTL_LAYER_EXT_REG_CURSOR1(stage);
default: return 0;
}
}
static void mdp5_ctl_reset_blend_regs(struct mdp5_ctl *ctl)
{
unsigned long flags;
struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
int i;
spin_lock_irqsave(&ctl->hw_lock, flags);
for (i = 0; i < ctl_mgr->nlm; i++) {
ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, i), 0x0);
ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, i), 0x0);
}
spin_unlock_irqrestore(&ctl->hw_lock, flags);
}
#define PIPE_LEFT 0
#define PIPE_RIGHT 1
int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
enum mdp5_pipe stage[][MAX_PIPE_STAGE],
enum mdp5_pipe r_stage[][MAX_PIPE_STAGE],
u32 stage_cnt, u32 ctl_blend_op_flags)
{
struct mdp5_hw_mixer *mixer = pipeline->mixer;
struct mdp5_hw_mixer *r_mixer = pipeline->r_mixer;
unsigned long flags;
u32 blend_cfg = 0, blend_ext_cfg = 0;
u32 r_blend_cfg = 0, r_blend_ext_cfg = 0;
int i, start_stage;
mdp5_ctl_reset_blend_regs(ctl);
if (ctl_blend_op_flags & MDP5_CTL_BLEND_OP_FLAG_BORDER_OUT) {
start_stage = STAGE0;
blend_cfg |= MDP5_CTL_LAYER_REG_BORDER_COLOR;
if (r_mixer)
r_blend_cfg |= MDP5_CTL_LAYER_REG_BORDER_COLOR;
} else {
start_stage = STAGE_BASE;
}
for (i = start_stage; stage_cnt && i <= STAGE_MAX; i++) {
blend_cfg |=
mdp_ctl_blend_mask(stage[i][PIPE_LEFT], i) |
mdp_ctl_blend_mask(stage[i][PIPE_RIGHT], i);
blend_ext_cfg |=
mdp_ctl_blend_ext_mask(stage[i][PIPE_LEFT], i) |
mdp_ctl_blend_ext_mask(stage[i][PIPE_RIGHT], i);
if (r_mixer) {
r_blend_cfg |=
mdp_ctl_blend_mask(r_stage[i][PIPE_LEFT], i) |
mdp_ctl_blend_mask(r_stage[i][PIPE_RIGHT], i);
r_blend_ext_cfg |=
mdp_ctl_blend_ext_mask(r_stage[i][PIPE_LEFT], i) |
mdp_ctl_blend_ext_mask(r_stage[i][PIPE_RIGHT], i);
}
}
spin_lock_irqsave(&ctl->hw_lock, flags);
if (ctl->cursor_on)
blend_cfg |= MDP5_CTL_LAYER_REG_CURSOR_OUT;
ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm), blend_cfg);
ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, mixer->lm),
blend_ext_cfg);
if (r_mixer) {
ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, r_mixer->lm),
r_blend_cfg);
ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, r_mixer->lm),
r_blend_ext_cfg);
}
spin_unlock_irqrestore(&ctl->hw_lock, flags);
ctl->pending_ctl_trigger = mdp_ctl_flush_mask_lm(mixer->lm);
if (r_mixer)
ctl->pending_ctl_trigger |= mdp_ctl_flush_mask_lm(r_mixer->lm);
DBG("lm%d: blend config = 0x%08x. ext_cfg = 0x%08x", mixer->lm,
blend_cfg, blend_ext_cfg);
if (r_mixer)
DBG("lm%d: blend config = 0x%08x. ext_cfg = 0x%08x",
r_mixer->lm, r_blend_cfg, r_blend_ext_cfg);
return 0;
}
u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf)
{
if (intf->type == INTF_WB)
return MDP5_CTL_FLUSH_WB;
switch (intf->num) {
case 0: return MDP5_CTL_FLUSH_TIMING_0;
case 1: return MDP5_CTL_FLUSH_TIMING_1;
case 2: return MDP5_CTL_FLUSH_TIMING_2;
case 3: return MDP5_CTL_FLUSH_TIMING_3;
default: return 0;
}
}
u32 mdp_ctl_flush_mask_cursor(int cursor_id)
{
switch (cursor_id) {
case 0: return MDP5_CTL_FLUSH_CURSOR_0;
case 1: return MDP5_CTL_FLUSH_CURSOR_1;
default: return 0;
}
}
u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe)
{
switch (pipe) {
case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0;
case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1;
case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2;
case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0;
case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1;
case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2;
case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0;
case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1;
case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3;
case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3;
case SSPP_CURSOR0: return MDP5_CTL_FLUSH_CURSOR_0;
case SSPP_CURSOR1: return MDP5_CTL_FLUSH_CURSOR_1;
default: return 0;
}
}
u32 mdp_ctl_flush_mask_lm(int lm)
{
switch (lm) {
case 0: return MDP5_CTL_FLUSH_LM0;
case 1: return MDP5_CTL_FLUSH_LM1;
case 2: return MDP5_CTL_FLUSH_LM2;
case 3: return MDP5_CTL_FLUSH_LM3;
case 4: return MDP5_CTL_FLUSH_LM4;
case 5: return MDP5_CTL_FLUSH_LM5;
default: return 0;
}
}
static u32 fix_sw_flush(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
u32 flush_mask)
{
struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
u32 sw_mask = 0;
#define BIT_NEEDS_SW_FIX(bit) \
(!(ctl_mgr->flush_hw_mask & bit) && (flush_mask & bit))
/* for some targets, cursor bit is the same as LM bit */
if (BIT_NEEDS_SW_FIX(MDP5_CTL_FLUSH_CURSOR_0))
sw_mask |= mdp_ctl_flush_mask_lm(pipeline->mixer->lm);
return sw_mask;
}
/**
* mdp5_ctl_commit() - Register Flush
*
* @ctl: the CTL instance
* @pipeline: the encoder's INTF + MIXER configuration
* @flush_mask: bitmask of display controller hw blocks to flush
* @start: if true, immediately update flush registers and set START
* bit, otherwise accumulate flush_mask bits until we are
* ready to START
*
* The flush register is used to indicate several registers are all
* programmed, and are safe to update to the back copy of the double
* buffered registers.
*
* Some registers FLUSH bits are shared when the hardware does not have
* dedicated bits for them; handling these is the job of fix_sw_flush().
*
* CTL registers need to be flushed in some circumstances; if that is the
* case, some trigger bits will be present in both flush mask and
* ctl->pending_ctl_trigger.
*
* Return H/W flushed bit mask.
*/
u32 mdp5_ctl_commit(struct mdp5_ctl *ctl,
struct mdp5_pipeline *pipeline,
u32 flush_mask, bool start)
{
struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm;
unsigned long flags;
u32 flush_id = ctl->id;
u32 curr_ctl_flush_mask;
VERB("flush_mask=%x, trigger=%x", flush_mask, ctl->pending_ctl_trigger);
if (ctl->pending_ctl_trigger & flush_mask) {
flush_mask |= MDP5_CTL_FLUSH_CTL;
ctl->pending_ctl_trigger = 0;
}
flush_mask |= fix_sw_flush(ctl, pipeline, flush_mask);
flush_mask &= ctl_mgr->flush_hw_mask;
curr_ctl_flush_mask = flush_mask;
if (!start) {
ctl->flush_mask |= flush_mask;
return curr_ctl_flush_mask;
} else {
flush_mask |= ctl->flush_mask;
ctl->flush_mask = 0;
}
if (flush_mask) {
spin_lock_irqsave(&ctl->hw_lock, flags);
ctl_write(ctl, REG_MDP5_CTL_FLUSH(flush_id), flush_mask);
spin_unlock_irqrestore(&ctl->hw_lock, flags);
}
if (start_signal_needed(ctl, pipeline)) {
send_start_signal(ctl);
}
return curr_ctl_flush_mask;
}
u32 mdp5_ctl_get_commit_status(struct mdp5_ctl *ctl)
{
return ctl_read(ctl, REG_MDP5_CTL_FLUSH(ctl->id));
}
int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl)
{
return WARN_ON(!ctl) ? -EINVAL : ctl->id;
}
/*
* mdp5_ctl_request() - CTL allocation
*
* Try to return booked CTL for @intf_num is 1 or 2, unbooked for other INTFs.
* If no CTL is available in preferred category, allocate from the other one.
*
* @return fail if no CTL is available.
*/
struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctl_mgr,
int intf_num)
{
struct mdp5_ctl *ctl = NULL;
const u32 checkm = CTL_STAT_BUSY | CTL_STAT_BOOKED;
u32 match = ((intf_num == 1) || (intf_num == 2)) ? CTL_STAT_BOOKED : 0;
unsigned long flags;
int c;
spin_lock_irqsave(&ctl_mgr->pool_lock, flags);
/* search the preferred */
for (c = 0; c < ctl_mgr->nctl; c++)
if ((ctl_mgr->ctls[c].status & checkm) == match)
goto found;
dev_warn(ctl_mgr->dev->dev,
"fall back to the other CTL category for INTF %d!\n", intf_num);
match ^= CTL_STAT_BOOKED;
for (c = 0; c < ctl_mgr->nctl; c++)
if ((ctl_mgr->ctls[c].status & checkm) == match)
goto found;
DRM_DEV_ERROR(ctl_mgr->dev->dev, "No more CTL available!");
goto unlock;
found:
ctl = &ctl_mgr->ctls[c];
ctl->status |= CTL_STAT_BUSY;
ctl->pending_ctl_trigger = 0;
DBG("CTL %d allocated", ctl->id);
unlock:
spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);
return ctl;
}
void mdp5_ctlm_hw_reset(struct mdp5_ctl_manager *ctl_mgr)
{
unsigned long flags;
int c;
for (c = 0; c < ctl_mgr->nctl; c++) {
struct mdp5_ctl *ctl = &ctl_mgr->ctls[c];
spin_lock_irqsave(&ctl->hw_lock, flags);
ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id), 0);
spin_unlock_irqrestore(&ctl->hw_lock, flags);
}
}
struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev,
void __iomem *mmio_base, struct mdp5_cfg_handler *cfg_hnd)
{
struct mdp5_ctl_manager *ctl_mgr;
const struct mdp5_cfg_hw *hw_cfg = mdp5_cfg_get_hw_config(cfg_hnd);
const struct mdp5_ctl_block *ctl_cfg = &hw_cfg->ctl;
unsigned long flags;
int c, ret;
ctl_mgr = devm_kzalloc(dev->dev, sizeof(*ctl_mgr), GFP_KERNEL);
if (!ctl_mgr) {
DRM_DEV_ERROR(dev->dev, "failed to allocate CTL manager\n");
return ERR_PTR(-ENOMEM);
}
if (WARN_ON(ctl_cfg->count > MAX_CTL)) {
DRM_DEV_ERROR(dev->dev, "Increase static pool size to at least %d\n",
ctl_cfg->count);
return ERR_PTR(-ENOSPC);
}
/* initialize the CTL manager: */
ctl_mgr->dev = dev;
ctl_mgr->nlm = hw_cfg->lm.count;
ctl_mgr->nctl = ctl_cfg->count;
ctl_mgr->flush_hw_mask = ctl_cfg->flush_hw_mask;
spin_lock_init(&ctl_mgr->pool_lock);
/* initialize each CTL of the pool: */
spin_lock_irqsave(&ctl_mgr->pool_lock, flags);
for (c = 0; c < ctl_mgr->nctl; c++) {
struct mdp5_ctl *ctl = &ctl_mgr->ctls[c];
if (WARN_ON(!ctl_cfg->base[c])) {
DRM_DEV_ERROR(dev->dev, "CTL_%d: base is null!\n", c);
ret = -EINVAL;
spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);
return ERR_PTR(ret);
}
ctl->ctlm = ctl_mgr;
ctl->id = c;
ctl->reg_offset = ctl_cfg->base[c];
ctl->status = 0;
spin_lock_init(&ctl->hw_lock);
}
spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);
DBG("Pool of %d CTLs created.", ctl_mgr->nctl);
return ctl_mgr;
}
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
*/
#ifndef __MDP5_CTL_H__
#define __MDP5_CTL_H__
#include "msm_drv.h"
/*
* CTL Manager prototypes:
* mdp5_ctlm_init() returns a ctlm (CTL Manager) handler,
* which is then used to call the other mdp5_ctlm_*(ctlm, ...) functions.
*/
struct mdp5_ctl_manager;
struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev,
void __iomem *mmio_base, struct mdp5_cfg_handler *cfg_hnd);
void mdp5_ctlm_hw_reset(struct mdp5_ctl_manager *ctlm);
/*
* CTL prototypes:
* mdp5_ctl_request(ctlm, ...) returns a ctl (CTL resource) handler,
* which is then used to call the other mdp5_ctl_*(ctl, ...) functions.
*/
struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctlm, int intf_num);
int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl);
struct mdp5_interface;
struct mdp5_pipeline;
int mdp5_ctl_set_pipeline(struct mdp5_ctl *ctl, struct mdp5_pipeline *p);
int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl, struct mdp5_pipeline *p,
bool enabled);
int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
int cursor_id, bool enable);
#define MAX_PIPE_STAGE 2
/*
* mdp5_ctl_blend() - Blend multiple layers on a Layer Mixer (LM)
*
* @stage: array to contain the pipe num for each stage
* @stage_cnt: valid stage number in stage array
* @ctl_blend_op_flags: blender operation mode flags
*
* Note:
* CTL registers need to be flushed after calling this function
* (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask)
*/
#define MDP5_CTL_BLEND_OP_FLAG_BORDER_OUT BIT(0)
int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
enum mdp5_pipe stage[][MAX_PIPE_STAGE],
enum mdp5_pipe r_stage[][MAX_PIPE_STAGE],
u32 stage_cnt, u32 ctl_blend_op_flags);
/**
* mdp_ctl_flush_mask...() - Register FLUSH masks
*
* These masks are used to specify which block(s) need to be flushed
* through @flush_mask parameter in mdp5_ctl_commit(.., flush_mask).
*/
u32 mdp_ctl_flush_mask_lm(int lm);
u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe);
u32 mdp_ctl_flush_mask_cursor(int cursor_id);
u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf);
/* @flush_mask: see CTL flush masks definitions below */
u32 mdp5_ctl_commit(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
u32 flush_mask, bool start);
u32 mdp5_ctl_get_commit_status(struct mdp5_ctl *ctl);
#endif /* __MDP5_CTL_H__ */
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <drm/drm_crtc.h>
#include <drm/drm_probe_helper.h>
#include "mdp5_kms.h"
static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
static void mdp5_vid_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
int intf = mdp5_encoder->intf->num;
uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol;
uint32_t display_v_start, display_v_end;
uint32_t hsync_start_x, hsync_end_x;
uint32_t format = 0x2100;
unsigned long flags;
mode = adjusted_mode;
DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode));
ctrl_pol = 0;
/* DSI controller cannot handle active-low sync signals. */
if (mdp5_encoder->intf->type != INTF_DSI) {
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl_pol |= MDP5_INTF_POLARITY_CTL_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl_pol |= MDP5_INTF_POLARITY_CTL_VSYNC_LOW;
}
/* probably need to get DATA_EN polarity from panel.. */
dtv_hsync_skew = 0; /* get this from panel? */
/* Get color format from panel, default is 8bpc */
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
switch (connector->display_info.bpc) {
case 4:
format |= 0;
break;
case 5:
format |= 0x15;
break;
case 6:
format |= 0x2A;
break;
case 8:
default:
format |= 0x3F;
break;
}
break;
}
}
hsync_start_x = (mode->htotal - mode->hsync_start);
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
vsync_period = mode->vtotal * mode->htotal;
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew;
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1;
/*
* For edp only:
* DISPLAY_V_START = (VBP * HCYCLE) + HBP
* DISPLAY_V_END = (VBP + VACTIVE) * HCYCLE - 1 - HFP
*/
if (mdp5_encoder->intf->type == INTF_eDP) {
display_v_start += mode->htotal - mode->hsync_start;
display_v_end -= mode->hsync_start - mode->hdisplay;
}
spin_lock_irqsave(&mdp5_encoder->intf_lock, flags);
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_CTL(intf),
MDP5_INTF_HSYNC_CTL_PULSEW(mode->hsync_end - mode->hsync_start) |
MDP5_INTF_HSYNC_CTL_PERIOD(mode->htotal));
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_PERIOD_F0(intf), vsync_period);
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_LEN_F0(intf), vsync_len);
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_HCTL(intf),
MDP5_INTF_DISPLAY_HCTL_START(hsync_start_x) |
MDP5_INTF_DISPLAY_HCTL_END(hsync_end_x));
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VSTART_F0(intf), display_v_start);
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VEND_F0(intf), display_v_end);
mdp5_write(mdp5_kms, REG_MDP5_INTF_BORDER_COLOR(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_UNDERFLOW_COLOR(intf), 0xff);
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_SKEW(intf), dtv_hsync_skew);
mdp5_write(mdp5_kms, REG_MDP5_INTF_POLARITY_CTL(intf), ctrl_pol);
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_HCTL(intf),
MDP5_INTF_ACTIVE_HCTL_START(0) |
MDP5_INTF_ACTIVE_HCTL_END(0));
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VSTART_F0(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VEND_F0(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_PANEL_FORMAT(intf), format);
mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(intf), 0x3); /* frame+line? */
spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
mdp5_crtc_set_pipeline(encoder->crtc);
}
static void mdp5_vid_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct mdp5_ctl *ctl = mdp5_encoder->ctl;
struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc);
struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc);
struct mdp5_interface *intf = mdp5_encoder->intf;
int intfn = mdp5_encoder->intf->num;
unsigned long flags;
if (WARN_ON(!mdp5_encoder->enabled))
return;
mdp5_ctl_set_encoder_state(ctl, pipeline, false);
spin_lock_irqsave(&mdp5_encoder->intf_lock, flags);
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 0);
spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true);
/*
* Wait for a vsync so we know the ENABLE=0 latched before
* the (connector) source of the vsync's gets disabled,
* otherwise we end up in a funny state if we re-enable
* before the disable latches, which results that some of
* the settings changes for the new modeset (like new
* scanout buffer) don't latch properly..
*/
mdp_irq_wait(&mdp5_kms->base, intf2vblank(mixer, intf));
mdp5_encoder->enabled = false;
}
static void mdp5_vid_encoder_enable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
struct mdp5_ctl *ctl = mdp5_encoder->ctl;
struct mdp5_interface *intf = mdp5_encoder->intf;
struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc);
int intfn = intf->num;
unsigned long flags;
if (WARN_ON(mdp5_encoder->enabled))
return;
spin_lock_irqsave(&mdp5_encoder->intf_lock, flags);
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 1);
spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags);
mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true);
mdp5_ctl_set_encoder_state(ctl, pipeline, true);
mdp5_encoder->enabled = true;
}
static void mdp5_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_interface *intf = mdp5_encoder->intf;
if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)
mdp5_cmd_encoder_mode_set(encoder, mode, adjusted_mode);
else
mdp5_vid_encoder_mode_set(encoder, mode, adjusted_mode);
}
static void mdp5_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_interface *intf = mdp5_encoder->intf;
if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)
mdp5_cmd_encoder_disable(encoder);
else
mdp5_vid_encoder_disable(encoder);
}
static void mdp5_encoder_enable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_interface *intf = mdp5_encoder->intf;
/* this isn't right I think */
struct drm_crtc_state *cstate = encoder->crtc->state;
mdp5_encoder_mode_set(encoder, &cstate->mode, &cstate->adjusted_mode);
if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)
mdp5_cmd_encoder_enable(encoder);
else
mdp5_vid_encoder_enable(encoder);
}
static int mdp5_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
struct mdp5_interface *intf = mdp5_encoder->intf;
struct mdp5_ctl *ctl = mdp5_encoder->ctl;
mdp5_cstate->ctl = ctl;
mdp5_cstate->pipeline.intf = intf;
/*
* This is a bit awkward, but we want to flush the CTL and hit the
* START bit at most once for an atomic update. In the non-full-
* modeset case, this is done from crtc->atomic_flush(), but that
* is too early in the case of full modeset, in which case we
* defer to encoder->enable(). But we need to *know* whether
* encoder->enable() will be called to do this:
*/
if (drm_atomic_crtc_needs_modeset(crtc_state))
mdp5_cstate->defer_start = true;
return 0;
}
static const struct drm_encoder_helper_funcs mdp5_encoder_helper_funcs = {
.disable = mdp5_encoder_disable,
.enable = mdp5_encoder_enable,
.atomic_check = mdp5_encoder_atomic_check,
};
int mdp5_encoder_get_linecount(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
int intf = mdp5_encoder->intf->num;
return mdp5_read(mdp5_kms, REG_MDP5_INTF_LINE_COUNT(intf));
}
u32 mdp5_encoder_get_framecount(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
int intf = mdp5_encoder->intf->num;
return mdp5_read(mdp5_kms, REG_MDP5_INTF_FRAME_COUNT(intf));
}
void mdp5_encoder_set_intf_mode(struct drm_encoder *encoder, bool cmd_mode)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_interface *intf = mdp5_encoder->intf;
/* TODO: Expand this to set writeback modes too */
if (cmd_mode) {
WARN_ON(intf->type != INTF_DSI);
intf->mode = MDP5_INTF_DSI_MODE_COMMAND;
} else {
if (intf->type == INTF_DSI)
intf->mode = MDP5_INTF_DSI_MODE_VIDEO;
else
intf->mode = MDP5_INTF_MODE_NONE;
}
}
/* initialize encoder */
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev,
struct mdp5_interface *intf,
struct mdp5_ctl *ctl)
{
struct drm_encoder *encoder = NULL;
struct mdp5_encoder *mdp5_encoder;
int enc_type = (intf->type == INTF_DSI) ?
DRM_MODE_ENCODER_DSI : DRM_MODE_ENCODER_TMDS;
mdp5_encoder = drmm_encoder_alloc(dev, struct mdp5_encoder, base,
NULL, enc_type, NULL);
if (IS_ERR(mdp5_encoder))
return ERR_CAST(mdp5_encoder);
encoder = &mdp5_encoder->base;
mdp5_encoder->ctl = ctl;
mdp5_encoder->intf = intf;
spin_lock_init(&mdp5_encoder->intf_lock);
drm_encoder_helper_add(encoder, &mdp5_encoder_helper_funcs);
return encoder;
}
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/irq.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "msm_drv.h"
#include "mdp5_kms.h"
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask,
uint32_t old_irqmask)
{
mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_CLEAR,
irqmask ^ (irqmask & old_irqmask));
mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_EN, irqmask);
}
static void mdp5_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp5_kms *mdp5_kms = container_of(irq, struct mdp5_kms, error_handler);
static DEFINE_RATELIMIT_STATE(rs, 5*HZ, 1);
extern bool dumpstate;
DRM_ERROR_RATELIMITED("errors: %08x\n", irqstatus);
if (dumpstate && __ratelimit(&rs)) {
struct drm_printer p = drm_info_printer(mdp5_kms->dev->dev);
drm_state_dump(mdp5_kms->dev, &p);
}
}
void mdp5_irq_preinstall(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct device *dev = &mdp5_kms->pdev->dev;
pm_runtime_get_sync(dev);
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, 0xffffffff);
mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000);
pm_runtime_put_sync(dev);
}
int mdp5_irq_postinstall(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
struct device *dev = &mdp5_kms->pdev->dev;
struct mdp_irq *error_handler = &mdp5_kms->error_handler;
error_handler->irq = mdp5_irq_error_handler;
error_handler->irqmask = MDP5_IRQ_INTF0_UNDER_RUN |
MDP5_IRQ_INTF1_UNDER_RUN |
MDP5_IRQ_INTF2_UNDER_RUN |
MDP5_IRQ_INTF3_UNDER_RUN;
pm_runtime_get_sync(dev);
mdp_irq_register(mdp_kms, error_handler);
pm_runtime_put_sync(dev);
return 0;
}
void mdp5_irq_uninstall(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct device *dev = &mdp5_kms->pdev->dev;
pm_runtime_get_sync(dev);
mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000);
pm_runtime_put_sync(dev);
}
irqreturn_t mdp5_irq(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
struct drm_device *dev = mdp5_kms->dev;
struct drm_crtc *crtc;
uint32_t status, enable;
enable = mdp5_read(mdp5_kms, REG_MDP5_INTR_EN);
status = mdp5_read(mdp5_kms, REG_MDP5_INTR_STATUS) & enable;
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, status);
VERB("status=%08x", status);
mdp_dispatch_irqs(mdp_kms, status);
drm_for_each_crtc(crtc, dev)
if (status & mdp5_crtc_vblank(crtc))
drm_crtc_handle_vblank(crtc);
return IRQ_HANDLED;
}
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct device *dev = &mdp5_kms->pdev->dev;
pm_runtime_get_sync(dev);
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp5_crtc_vblank(crtc), true);
pm_runtime_put_sync(dev);
return 0;
}
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct device *dev = &mdp5_kms->pdev->dev;
pm_runtime_get_sync(dev);
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp5_crtc_vblank(crtc), false);
pm_runtime_put_sync(dev);
}
@@ -0,0 +1,932 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include <linux/interconnect.h>
#include <linux/of_irq.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_vblank.h>
#include "msm_drv.h"
#include "msm_gem.h"
#include "msm_mmu.h"
#include "mdp5_kms.h"
static int mdp5_hw_init(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct device *dev = &mdp5_kms->pdev->dev;
unsigned long flags;
pm_runtime_get_sync(dev);
/* Magic unknown register writes:
*
* W VBIF:0x004 00000001 (mdss_mdp.c:839)
* W MDP5:0x2e0 0xe9 (mdss_mdp.c:839)
* W MDP5:0x2e4 0x55 (mdss_mdp.c:839)
* W MDP5:0x3ac 0xc0000ccc (mdss_mdp.c:839)
* W MDP5:0x3b4 0xc0000ccc (mdss_mdp.c:839)
* W MDP5:0x3bc 0xcccccc (mdss_mdp.c:839)
* W MDP5:0x4a8 0xcccc0c0 (mdss_mdp.c:839)
* W MDP5:0x4b0 0xccccc0c0 (mdss_mdp.c:839)
* W MDP5:0x4b8 0xccccc000 (mdss_mdp.c:839)
*
* Downstream fbdev driver gets these register offsets/values
* from DT.. not really sure what these registers are or if
* different values for different boards/SoC's, etc. I guess
* they are the golden registers.
*
* Not setting these does not seem to cause any problem. But
* we may be getting lucky with the bootloader initializing
* them for us. OTOH, if we can always count on the bootloader
* setting the golden registers, then perhaps we don't need to
* care.
*/
spin_lock_irqsave(&mdp5_kms->resource_lock, flags);
mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, 0);
spin_unlock_irqrestore(&mdp5_kms->resource_lock, flags);
mdp5_ctlm_hw_reset(mdp5_kms->ctlm);
pm_runtime_put_sync(dev);
return 0;
}
/* Global/shared object state funcs */
/*
* This is a helper that returns the private state currently in operation.
* Note that this would return the "old_state" if called in the atomic check
* path, and the "new_state" after the atomic swap has been done.
*/
struct mdp5_global_state *
mdp5_get_existing_global_state(struct mdp5_kms *mdp5_kms)
{
return to_mdp5_global_state(mdp5_kms->glob_state.state);
}
/*
* This acquires the modeset lock set aside for global state, creates
* a new duplicated private object state.
*/
struct mdp5_global_state *mdp5_get_global_state(struct drm_atomic_commit *s)
{
struct msm_drm_private *priv = s->dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct drm_private_state *priv_state;
priv_state = drm_atomic_get_private_obj_state(s, &mdp5_kms->glob_state);
if (IS_ERR(priv_state))
return ERR_CAST(priv_state);
return to_mdp5_global_state(priv_state);
}
static struct drm_private_state *
mdp5_global_duplicate_state(struct drm_private_obj *obj)
{
struct mdp5_global_state *state;
state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
if (!state)
return NULL;
__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
return &state->base;
}
static void mdp5_global_destroy_state(struct drm_private_obj *obj,
struct drm_private_state *state)
{
struct mdp5_global_state *mdp5_state = to_mdp5_global_state(state);
kfree(mdp5_state);
}
static struct drm_private_state *
mdp5_global_create_state(struct drm_private_obj *obj)
{
struct drm_device *dev = obj->dev;
struct msm_drm_private *priv = dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct mdp5_global_state *mdp5_state;
mdp5_state = kzalloc_obj(*mdp5_state);
if (!mdp5_state)
return ERR_PTR(-ENOMEM);
__drm_atomic_helper_private_obj_create_state(obj, &mdp5_state->base);
mdp5_state->mdp5_kms = mdp5_kms;
return &mdp5_state->base;
}
static void mdp5_global_print_state(struct drm_printer *p,
const struct drm_private_state *state)
{
struct mdp5_global_state *mdp5_state = to_mdp5_global_state(state);
if (mdp5_state->mdp5_kms->smp)
mdp5_smp_dump(mdp5_state->mdp5_kms->smp, p, mdp5_state);
}
static const struct drm_private_state_funcs mdp5_global_state_funcs = {
.atomic_create_state = mdp5_global_create_state,
.atomic_duplicate_state = mdp5_global_duplicate_state,
.atomic_destroy_state = mdp5_global_destroy_state,
.atomic_print_state = mdp5_global_print_state,
};
static void mdp5_enable_commit(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
pm_runtime_get_sync(&mdp5_kms->pdev->dev);
}
static void mdp5_disable_commit(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
pm_runtime_put_sync(&mdp5_kms->pdev->dev);
}
static void mdp5_prepare_commit(struct msm_kms *kms, struct drm_atomic_commit *state)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct mdp5_global_state *global_state;
global_state = mdp5_get_existing_global_state(mdp5_kms);
if (mdp5_kms->smp)
mdp5_smp_prepare_commit(mdp5_kms->smp, &global_state->smp);
}
static void mdp5_flush_commit(struct msm_kms *kms, unsigned crtc_mask)
{
/* TODO */
}
static void mdp5_wait_flush(struct msm_kms *kms, unsigned crtc_mask)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct drm_crtc *crtc;
for_each_crtc_mask(mdp5_kms->dev, crtc, crtc_mask)
mdp5_crtc_wait_for_commit_done(crtc);
}
static void mdp5_complete_commit(struct msm_kms *kms, unsigned crtc_mask)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct mdp5_global_state *global_state;
global_state = mdp5_get_existing_global_state(mdp5_kms);
if (mdp5_kms->smp)
mdp5_smp_complete_commit(mdp5_kms->smp, &global_state->smp);
}
static void mdp5_destroy(struct mdp5_kms *mdp5_kms);
static void mdp5_kms_destroy(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
if (kms->vm) {
struct msm_mmu *mmu = to_msm_vm(kms->vm)->mmu;
mmu->funcs->detach(mmu);
drm_gpuvm_put(kms->vm);
}
mdp_kms_destroy(&mdp5_kms->base);
mdp5_destroy(mdp5_kms);
}
static const struct mdp_kms_funcs kms_funcs = {
.base = {
.hw_init = mdp5_hw_init,
.irq_preinstall = mdp5_irq_preinstall,
.irq_postinstall = mdp5_irq_postinstall,
.irq_uninstall = mdp5_irq_uninstall,
.irq = mdp5_irq,
.enable_vblank = mdp5_enable_vblank,
.disable_vblank = mdp5_disable_vblank,
.flush_commit = mdp5_flush_commit,
.enable_commit = mdp5_enable_commit,
.disable_commit = mdp5_disable_commit,
.prepare_commit = mdp5_prepare_commit,
.wait_flush = mdp5_wait_flush,
.complete_commit = mdp5_complete_commit,
.destroy = mdp5_kms_destroy,
},
.set_irqmask = mdp5_set_irqmask,
};
static int mdp5_disable(struct mdp5_kms *mdp5_kms)
{
DBG("");
mdp5_kms->enable_count--;
WARN_ON(mdp5_kms->enable_count < 0);
clk_disable_unprepare(mdp5_kms->tbu_rt_clk);
clk_disable_unprepare(mdp5_kms->tbu_clk);
clk_disable_unprepare(mdp5_kms->ahb_clk);
clk_disable_unprepare(mdp5_kms->axi_clk);
clk_disable_unprepare(mdp5_kms->core_clk);
clk_disable_unprepare(mdp5_kms->lut_clk);
return 0;
}
static int mdp5_enable(struct mdp5_kms *mdp5_kms)
{
DBG("");
mdp5_kms->enable_count++;
clk_prepare_enable(mdp5_kms->ahb_clk);
clk_prepare_enable(mdp5_kms->axi_clk);
clk_prepare_enable(mdp5_kms->core_clk);
clk_prepare_enable(mdp5_kms->lut_clk);
clk_prepare_enable(mdp5_kms->tbu_clk);
clk_prepare_enable(mdp5_kms->tbu_rt_clk);
return 0;
}
static struct drm_encoder *construct_encoder(struct mdp5_kms *mdp5_kms,
struct mdp5_interface *intf,
struct mdp5_ctl *ctl)
{
struct drm_device *dev = mdp5_kms->dev;
struct drm_encoder *encoder;
encoder = mdp5_encoder_init(dev, intf, ctl);
if (IS_ERR(encoder)) {
DRM_DEV_ERROR(dev->dev, "failed to construct encoder\n");
return encoder;
}
return encoder;
}
static int get_dsi_id_from_intf(const struct mdp5_cfg_hw *hw_cfg, int intf_num)
{
const enum mdp5_intf_type *intfs = hw_cfg->intf.connect;
const int intf_cnt = ARRAY_SIZE(hw_cfg->intf.connect);
int id = 0, i;
for (i = 0; i < intf_cnt; i++) {
if (intfs[i] == INTF_DSI) {
if (intf_num == i)
return id;
id++;
}
}
return -EINVAL;
}
static int modeset_init_intf(struct mdp5_kms *mdp5_kms,
struct mdp5_interface *intf)
{
struct drm_device *dev = mdp5_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
struct mdp5_ctl_manager *ctlm = mdp5_kms->ctlm;
struct mdp5_ctl *ctl;
struct drm_encoder *encoder;
int ret = 0;
switch (intf->type) {
case INTF_eDP:
DRM_DEV_INFO(dev->dev, "Skipping eDP interface %d\n", intf->num);
break;
case INTF_HDMI:
if (!priv->kms->hdmi)
break;
ctl = mdp5_ctlm_request(ctlm, intf->num);
if (!ctl) {
ret = -EINVAL;
break;
}
encoder = construct_encoder(mdp5_kms, intf, ctl);
if (IS_ERR(encoder)) {
ret = PTR_ERR(encoder);
break;
}
ret = msm_hdmi_modeset_init(priv->kms->hdmi, dev, encoder);
break;
case INTF_DSI:
{
const struct mdp5_cfg_hw *hw_cfg =
mdp5_cfg_get_hw_config(mdp5_kms->cfg);
int dsi_id = get_dsi_id_from_intf(hw_cfg, intf->num);
if ((dsi_id >= ARRAY_SIZE(priv->kms->dsi)) || (dsi_id < 0)) {
DRM_DEV_ERROR(dev->dev, "failed to find dsi from intf %d\n",
intf->num);
ret = -EINVAL;
break;
}
if (!priv->kms->dsi[dsi_id])
break;
ctl = mdp5_ctlm_request(ctlm, intf->num);
if (!ctl) {
ret = -EINVAL;
break;
}
encoder = construct_encoder(mdp5_kms, intf, ctl);
if (IS_ERR(encoder)) {
ret = PTR_ERR(encoder);
break;
}
ret = msm_dsi_modeset_init(priv->kms->dsi[dsi_id], dev, encoder);
if (!ret)
mdp5_encoder_set_intf_mode(encoder,
msm_dsi_is_cmd_mode(priv->kms->dsi[dsi_id]));
break;
}
default:
DRM_DEV_ERROR(dev->dev, "unknown intf: %d\n", intf->type);
ret = -EINVAL;
break;
}
return ret;
}
static int modeset_init(struct mdp5_kms *mdp5_kms)
{
struct drm_device *dev = mdp5_kms->dev;
unsigned int num_crtcs;
int i, ret, pi = 0, ci = 0;
struct drm_plane *primary[MAX_BASES] = { NULL };
struct drm_plane *cursor[MAX_BASES] = { NULL };
struct drm_encoder *encoder;
unsigned int num_encoders;
/*
* Construct encoders and modeset initialize connector devices
* for each external display interface.
*/
for (i = 0; i < mdp5_kms->num_intfs; i++) {
ret = modeset_init_intf(mdp5_kms, mdp5_kms->intfs[i]);
if (ret)
goto fail;
}
num_encoders = 0;
drm_for_each_encoder(encoder, dev)
num_encoders++;
/*
* We should ideally have less number of encoders (set up by parsing
* the MDP5 interfaces) than the number of layer mixers present in HW,
* but let's be safe here anyway
*/
num_crtcs = min(num_encoders, mdp5_kms->num_hwmixers);
/*
* Construct planes equaling the number of hw pipes, and CRTCs for the
* N encoders set up by the driver. The first N planes become primary
* planes for the CRTCs, with the remainder as overlay planes:
*/
for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i];
struct drm_plane *plane;
enum drm_plane_type type;
if (i < num_crtcs)
type = DRM_PLANE_TYPE_PRIMARY;
else if (hwpipe->caps & MDP_PIPE_CAP_CURSOR)
type = DRM_PLANE_TYPE_CURSOR;
else
type = DRM_PLANE_TYPE_OVERLAY;
plane = mdp5_plane_init(dev, type);
if (IS_ERR(plane)) {
ret = PTR_ERR(plane);
DRM_DEV_ERROR(dev->dev, "failed to construct plane %d (%d)\n", i, ret);
goto fail;
}
if (type == DRM_PLANE_TYPE_PRIMARY)
primary[pi++] = plane;
if (type == DRM_PLANE_TYPE_CURSOR)
cursor[ci++] = plane;
}
for (i = 0; i < num_crtcs; i++) {
struct drm_crtc *crtc;
crtc = mdp5_crtc_init(dev, primary[i], cursor[i], i);
if (IS_ERR(crtc)) {
ret = PTR_ERR(crtc);
DRM_DEV_ERROR(dev->dev, "failed to construct crtc %d (%d)\n", i, ret);
goto fail;
}
}
/*
* Now that we know the number of crtcs we've created, set the possible
* crtcs for the encoders
*/
drm_for_each_encoder(encoder, dev)
encoder->possible_crtcs = (1 << dev->mode_config.num_crtc) - 1;
return 0;
fail:
return ret;
}
static void read_mdp_hw_revision(struct mdp5_kms *mdp5_kms,
u32 *major, u32 *minor)
{
struct device *dev = &mdp5_kms->pdev->dev;
u32 version;
pm_runtime_get_sync(dev);
version = mdp5_read(mdp5_kms, REG_MDP5_HW_VERSION);
pm_runtime_put_sync(dev);
*major = FIELD(version, MDP5_HW_VERSION_MAJOR);
*minor = FIELD(version, MDP5_HW_VERSION_MINOR);
DRM_DEV_INFO(dev, "MDP5 version v%d.%d", *major, *minor);
}
static int get_clk(struct platform_device *pdev, struct clk **clkp,
const char *name, bool mandatory)
{
struct device *dev = &pdev->dev;
struct clk *clk = msm_clk_get(pdev, name);
if (IS_ERR(clk) && mandatory) {
DRM_DEV_ERROR(dev, "failed to get %s (%ld)\n", name, PTR_ERR(clk));
return PTR_ERR(clk);
}
if (IS_ERR(clk))
DBG("skipping %s", name);
else
*clkp = clk;
return 0;
}
static int mdp5_init(struct platform_device *pdev, struct drm_device *dev);
static int mdp5_kms_init(struct drm_device *dev)
{
struct msm_drm_private *priv = dev->dev_private;
struct platform_device *pdev;
struct mdp5_kms *mdp5_kms;
struct mdp5_cfg *config;
struct msm_kms *kms = priv->kms;
struct drm_gpuvm *vm;
int i, ret;
ret = mdp5_init(to_platform_device(dev->dev), dev);
if (ret)
return ret;
mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
pdev = mdp5_kms->pdev;
ret = mdp_kms_init(&mdp5_kms->base, &kms_funcs);
if (ret) {
DRM_DEV_ERROR(&pdev->dev, "failed to init kms\n");
goto fail;
}
config = mdp5_cfg_get_config(mdp5_kms->cfg);
/* make sure things are off before attaching iommu (bootloader could
* have left things on, in which case we'll start getting faults if
* we don't disable):
*/
pm_runtime_get_sync(&pdev->dev);
for (i = 0; i < MDP5_INTF_NUM_MAX; i++) {
if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) ||
!config->hw->intf.base[i])
continue;
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(i), 0x3);
}
mdelay(16);
vm = msm_kms_init_vm(mdp5_kms->dev, pdev->dev.parent);
if (IS_ERR(vm)) {
ret = PTR_ERR(vm);
goto fail;
}
kms->vm = vm;
pm_runtime_put_sync(&pdev->dev);
ret = modeset_init(mdp5_kms);
if (ret) {
DRM_DEV_ERROR(&pdev->dev, "modeset_init failed: %d\n", ret);
goto fail;
}
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.max_width = 0xffff;
dev->mode_config.max_height = 0xffff;
dev->max_vblank_count = 0; /* max_vblank_count is set on each CRTC */
dev->vblank_disable_immediate = true;
return 0;
fail:
if (kms)
mdp5_kms_destroy(kms);
return ret;
}
static void mdp5_destroy(struct mdp5_kms *mdp5_kms)
{
if (mdp5_kms->rpm_enabled)
pm_runtime_disable(&mdp5_kms->pdev->dev);
drm_atomic_private_obj_fini(&mdp5_kms->glob_state);
}
static int construct_pipes(struct mdp5_kms *mdp5_kms, int cnt,
const enum mdp5_pipe *pipes, const uint32_t *offsets,
uint32_t caps)
{
struct drm_device *dev = mdp5_kms->dev;
int i, ret;
for (i = 0; i < cnt; i++) {
struct mdp5_hw_pipe *hwpipe;
hwpipe = mdp5_pipe_init(dev, pipes[i], offsets[i], caps);
if (IS_ERR(hwpipe)) {
ret = PTR_ERR(hwpipe);
DRM_DEV_ERROR(dev->dev, "failed to construct pipe for %s (%d)\n",
pipe2name(pipes[i]), ret);
return ret;
}
hwpipe->idx = mdp5_kms->num_hwpipes;
mdp5_kms->hwpipes[mdp5_kms->num_hwpipes++] = hwpipe;
}
return 0;
}
static int hwpipe_init(struct mdp5_kms *mdp5_kms)
{
static const enum mdp5_pipe rgb_planes[] = {
SSPP_RGB0, SSPP_RGB1, SSPP_RGB2, SSPP_RGB3,
};
static const enum mdp5_pipe vig_planes[] = {
SSPP_VIG0, SSPP_VIG1, SSPP_VIG2, SSPP_VIG3,
};
static const enum mdp5_pipe dma_planes[] = {
SSPP_DMA0, SSPP_DMA1,
};
static const enum mdp5_pipe cursor_planes[] = {
SSPP_CURSOR0, SSPP_CURSOR1,
};
const struct mdp5_cfg_hw *hw_cfg;
int ret;
hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
/* Construct RGB pipes: */
ret = construct_pipes(mdp5_kms, hw_cfg->pipe_rgb.count, rgb_planes,
hw_cfg->pipe_rgb.base, hw_cfg->pipe_rgb.caps);
if (ret)
return ret;
/* Construct video (VIG) pipes: */
ret = construct_pipes(mdp5_kms, hw_cfg->pipe_vig.count, vig_planes,
hw_cfg->pipe_vig.base, hw_cfg->pipe_vig.caps);
if (ret)
return ret;
/* Construct DMA pipes: */
ret = construct_pipes(mdp5_kms, hw_cfg->pipe_dma.count, dma_planes,
hw_cfg->pipe_dma.base, hw_cfg->pipe_dma.caps);
if (ret)
return ret;
/* Construct cursor pipes: */
ret = construct_pipes(mdp5_kms, hw_cfg->pipe_cursor.count,
cursor_planes, hw_cfg->pipe_cursor.base,
hw_cfg->pipe_cursor.caps);
if (ret)
return ret;
return 0;
}
static int hwmixer_init(struct mdp5_kms *mdp5_kms)
{
struct drm_device *dev = mdp5_kms->dev;
const struct mdp5_cfg_hw *hw_cfg;
int i, ret;
hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
for (i = 0; i < hw_cfg->lm.count; i++) {
struct mdp5_hw_mixer *mixer;
mixer = mdp5_mixer_init(dev, &hw_cfg->lm.instances[i]);
if (IS_ERR(mixer)) {
ret = PTR_ERR(mixer);
DRM_DEV_ERROR(dev->dev, "failed to construct LM%d (%d)\n",
i, ret);
return ret;
}
mixer->idx = mdp5_kms->num_hwmixers;
mdp5_kms->hwmixers[mdp5_kms->num_hwmixers++] = mixer;
}
return 0;
}
static int interface_init(struct mdp5_kms *mdp5_kms)
{
struct drm_device *dev = mdp5_kms->dev;
const struct mdp5_cfg_hw *hw_cfg;
const enum mdp5_intf_type *intf_types;
int i;
hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
intf_types = hw_cfg->intf.connect;
for (i = 0; i < ARRAY_SIZE(hw_cfg->intf.connect); i++) {
struct mdp5_interface *intf;
if (intf_types[i] == INTF_DISABLED)
continue;
intf = devm_kzalloc(dev->dev, sizeof(*intf), GFP_KERNEL);
if (!intf) {
DRM_DEV_ERROR(dev->dev, "failed to construct INTF%d\n", i);
return -ENOMEM;
}
intf->num = i;
intf->type = intf_types[i];
intf->mode = MDP5_INTF_MODE_NONE;
intf->idx = mdp5_kms->num_intfs;
mdp5_kms->intfs[mdp5_kms->num_intfs++] = intf;
}
return 0;
}
static int mdp5_init(struct platform_device *pdev, struct drm_device *dev)
{
struct msm_drm_private *priv = dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct mdp5_cfg *config;
u32 major, minor;
int ret;
mdp5_kms->dev = dev;
drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state,
&mdp5_global_state_funcs);
/* we need to set a default rate before enabling. Set a safe
* rate first, then figure out hw revision, and then set a
* more optimal rate:
*/
clk_set_rate(mdp5_kms->core_clk, 200000000);
pm_runtime_enable(&pdev->dev);
mdp5_kms->rpm_enabled = true;
read_mdp_hw_revision(mdp5_kms, &major, &minor);
mdp5_kms->cfg = mdp5_cfg_init(mdp5_kms, major, minor);
if (IS_ERR(mdp5_kms->cfg)) {
ret = PTR_ERR(mdp5_kms->cfg);
mdp5_kms->cfg = NULL;
goto fail;
}
config = mdp5_cfg_get_config(mdp5_kms->cfg);
mdp5_kms->caps = config->hw->mdp.caps;
/* TODO: compute core clock rate at runtime */
clk_set_rate(mdp5_kms->core_clk, config->hw->max_clk);
/*
* Some chipsets have a Shared Memory Pool (SMP), while others
* have dedicated latency buffering per source pipe instead;
* this section initializes the SMP:
*/
if (mdp5_kms->caps & MDP_CAP_SMP) {
mdp5_kms->smp = mdp5_smp_init(mdp5_kms, &config->hw->smp);
if (IS_ERR(mdp5_kms->smp)) {
ret = PTR_ERR(mdp5_kms->smp);
mdp5_kms->smp = NULL;
goto fail;
}
}
mdp5_kms->ctlm = mdp5_ctlm_init(dev, mdp5_kms->mmio, mdp5_kms->cfg);
if (IS_ERR(mdp5_kms->ctlm)) {
ret = PTR_ERR(mdp5_kms->ctlm);
mdp5_kms->ctlm = NULL;
goto fail;
}
ret = hwpipe_init(mdp5_kms);
if (ret)
goto fail;
ret = hwmixer_init(mdp5_kms);
if (ret)
goto fail;
ret = interface_init(mdp5_kms);
if (ret)
goto fail;
return 0;
fail:
mdp5_destroy(mdp5_kms);
return ret;
}
static int mdp5_setup_interconnect(struct platform_device *pdev)
{
struct icc_path *path0 = msm_icc_get(&pdev->dev, "mdp0-mem");
struct icc_path *path1 = msm_icc_get(&pdev->dev, "mdp1-mem");
struct icc_path *path_rot = msm_icc_get(&pdev->dev, "rotator-mem");
if (IS_ERR(path0))
return PTR_ERR(path0);
if (!path0) {
/* no interconnect support is not necessarily a fatal
* condition, the platform may simply not have an
* interconnect driver yet. But warn about it in case
* bootloader didn't setup bus clocks high enough for
* scanout.
*/
dev_warn(&pdev->dev, "No interconnect support may cause display underflows!\n");
return 0;
}
icc_set_bw(path0, 0, MBps_to_icc(6400));
if (!IS_ERR_OR_NULL(path1))
icc_set_bw(path1, 0, MBps_to_icc(6400));
if (!IS_ERR_OR_NULL(path_rot))
icc_set_bw(path_rot, 0, MBps_to_icc(6400));
return 0;
}
static int mdp5_dev_probe(struct platform_device *pdev)
{
struct mdp5_kms *mdp5_kms;
int ret, irq;
DBG("");
if (!msm_disp_drv_should_bind(&pdev->dev, false))
return -ENODEV;
mdp5_kms = devm_kzalloc(&pdev->dev, sizeof(*mdp5_kms), GFP_KERNEL);
if (!mdp5_kms)
return -ENOMEM;
ret = mdp5_setup_interconnect(pdev);
if (ret)
return ret;
mdp5_kms->pdev = pdev;
spin_lock_init(&mdp5_kms->resource_lock);
mdp5_kms->mmio = msm_ioremap(pdev, "mdp_phys");
if (IS_ERR(mdp5_kms->mmio))
return PTR_ERR(mdp5_kms->mmio);
/* mandatory clocks: */
ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus", true);
if (ret)
return ret;
ret = get_clk(pdev, &mdp5_kms->ahb_clk, "iface", true);
if (ret)
return ret;
ret = get_clk(pdev, &mdp5_kms->core_clk, "core", true);
if (ret)
return ret;
ret = get_clk(pdev, &mdp5_kms->vsync_clk, "vsync", true);
if (ret)
return ret;
/* optional clocks: */
get_clk(pdev, &mdp5_kms->lut_clk, "lut", false);
get_clk(pdev, &mdp5_kms->tbu_clk, "tbu", false);
get_clk(pdev, &mdp5_kms->tbu_rt_clk, "tbu_rt", false);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return dev_err_probe(&pdev->dev, irq, "failed to get irq\n");
mdp5_kms->base.base.irq = irq;
return msm_drv_probe(&pdev->dev, mdp5_kms_init, &mdp5_kms->base.base);
}
static void mdp5_dev_remove(struct platform_device *pdev)
{
DBG("");
component_master_del(&pdev->dev, &msm_drm_ops);
}
static __maybe_unused int mdp5_runtime_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct msm_drm_private *priv = platform_get_drvdata(pdev);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
DBG("");
return mdp5_disable(mdp5_kms);
}
static __maybe_unused int mdp5_runtime_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct msm_drm_private *priv = platform_get_drvdata(pdev);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
DBG("");
return mdp5_enable(mdp5_kms);
}
static const struct dev_pm_ops mdp5_pm_ops = {
SET_RUNTIME_PM_OPS(mdp5_runtime_suspend, mdp5_runtime_resume, NULL)
.prepare = msm_kms_pm_prepare,
.complete = msm_kms_pm_complete,
};
static const struct of_device_id mdp5_dt_match[] = {
{ .compatible = "qcom,mdp5", },
/* to support downstream DT files */
{ .compatible = "qcom,mdss_mdp", },
{}
};
MODULE_DEVICE_TABLE(of, mdp5_dt_match);
static struct platform_driver mdp5_driver = {
.probe = mdp5_dev_probe,
.remove = mdp5_dev_remove,
.shutdown = msm_kms_shutdown,
.driver = {
.name = "msm_mdp",
.of_match_table = mdp5_dt_match,
.pm = &mdp5_pm_ops,
},
};
void __init msm_mdp_register(void)
{
DBG("");
platform_driver_register(&mdp5_driver);
}
void __exit msm_mdp_unregister(void)
{
DBG("");
platform_driver_unregister(&mdp5_driver);
}
@@ -0,0 +1,317 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#ifndef __MDP5_KMS_H__
#define __MDP5_KMS_H__
#include "msm_drv.h"
#include "msm_kms.h"
#include "disp/mdp_kms.h"
#include "mdp5_cfg.h" /* must be included before mdp5.xml.h */
#include "mdp5.xml.h"
#include "mdp5_pipe.h"
#include "mdp5_mixer.h"
#include "mdp5_ctl.h"
#include "mdp5_smp.h"
struct mdp5_kms {
struct mdp_kms base;
struct drm_device *dev;
struct platform_device *pdev;
unsigned num_hwpipes;
struct mdp5_hw_pipe *hwpipes[SSPP_MAX];
unsigned num_hwmixers;
struct mdp5_hw_mixer *hwmixers[8];
unsigned num_intfs;
struct mdp5_interface *intfs[5];
struct mdp5_cfg_handler *cfg;
uint32_t caps; /* MDP capabilities (MDP_CAP_XXX bits) */
/*
* Global private object state, Do not access directly, use
* mdp5_global_get_state()
*/
struct drm_private_obj glob_state;
struct mdp5_smp *smp;
struct mdp5_ctl_manager *ctlm;
/* io/register spaces: */
void __iomem *mmio;
struct clk *axi_clk;
struct clk *ahb_clk;
struct clk *core_clk;
struct clk *lut_clk;
struct clk *tbu_clk;
struct clk *tbu_rt_clk;
struct clk *vsync_clk;
/*
* lock to protect access to global resources: ie., following register:
* - REG_MDP5_DISP_INTF_SEL
*/
spinlock_t resource_lock;
bool rpm_enabled;
struct mdp_irq error_handler;
int enable_count;
};
#define to_mdp5_kms(x) container_of(x, struct mdp5_kms, base)
/* Global private object state for tracking resources that are shared across
* multiple kms objects (planes/crtcs/etc).
*/
#define to_mdp5_global_state(x) container_of(x, struct mdp5_global_state, base)
struct mdp5_global_state {
struct drm_private_state base;
struct drm_atomic_commit *state;
struct mdp5_kms *mdp5_kms;
struct mdp5_hw_pipe_state hwpipe;
struct mdp5_hw_mixer_state hwmixer;
struct mdp5_smp_state smp;
};
struct mdp5_global_state * mdp5_get_existing_global_state(struct mdp5_kms *mdp5_kms);
struct mdp5_global_state *__must_check mdp5_get_global_state(struct drm_atomic_commit *s);
/* Atomic plane state. Subclasses the base drm_plane_state in order to
* track assigned hwpipe and hw specific state.
*/
struct mdp5_plane_state {
struct drm_plane_state base;
struct mdp5_hw_pipe *hwpipe;
struct mdp5_hw_pipe *r_hwpipe; /* right hwpipe */
/* assigned by crtc blender */
enum mdp_mixer_stage_id stage;
/* whether attached CRTC needs pixel data explicitly flushed to
* display (ex. DSI command mode display)
*/
bool needs_dirtyfb;
};
#define to_mdp5_plane_state(x) \
container_of(x, struct mdp5_plane_state, base)
struct mdp5_pipeline {
struct mdp5_interface *intf;
struct mdp5_hw_mixer *mixer;
struct mdp5_hw_mixer *r_mixer; /* right mixer */
};
struct mdp5_crtc_state {
struct drm_crtc_state base;
struct mdp5_ctl *ctl;
struct mdp5_pipeline pipeline;
/* these are derivatives of intf/mixer state in mdp5_pipeline */
u32 vblank_irqmask;
u32 err_irqmask;
u32 pp_done_irqmask;
bool cmd_mode;
/* should we not write CTL[n].START register on flush? If the
* encoder has changed this is set to true, since encoder->enable()
* is called after crtc state is committed, but we only want to
* write the CTL[n].START register once. This lets us defer
* writing CTL[n].START until encoder->enable()
*/
bool defer_start;
};
#define to_mdp5_crtc_state(x) \
container_of(x, struct mdp5_crtc_state, base)
enum mdp5_intf_mode {
MDP5_INTF_MODE_NONE = 0,
/* Modes used for DSI interface (INTF_DSI type): */
MDP5_INTF_DSI_MODE_VIDEO,
MDP5_INTF_DSI_MODE_COMMAND,
/* Modes used for WB interface (INTF_WB type): */
MDP5_INTF_WB_MODE_BLOCK,
MDP5_INTF_WB_MODE_LINE,
};
struct mdp5_interface {
int idx;
int num; /* display interface number */
enum mdp5_intf_type type;
enum mdp5_intf_mode mode;
};
struct mdp5_encoder {
struct drm_encoder base;
spinlock_t intf_lock; /* protect REG_MDP5_INTF_* registers */
bool enabled;
uint32_t bsc;
struct mdp5_interface *intf;
struct mdp5_ctl *ctl;
};
#define to_mdp5_encoder(x) container_of(x, struct mdp5_encoder, base)
static inline void mdp5_write(struct mdp5_kms *mdp5_kms, u32 reg, u32 data)
{
WARN_ON(mdp5_kms->enable_count <= 0);
writel(data, mdp5_kms->mmio + reg);
}
static inline u32 mdp5_read(struct mdp5_kms *mdp5_kms, u32 reg)
{
WARN_ON(mdp5_kms->enable_count <= 0);
return readl(mdp5_kms->mmio + reg);
}
static inline const char *stage2name(enum mdp_mixer_stage_id stage)
{
static const char *names[] = {
#define NAME(n) [n] = #n
NAME(STAGE_UNUSED), NAME(STAGE_BASE),
NAME(STAGE0), NAME(STAGE1), NAME(STAGE2),
NAME(STAGE3), NAME(STAGE4), NAME(STAGE6),
#undef NAME
};
return names[stage];
}
static inline const char *pipe2name(enum mdp5_pipe pipe)
{
static const char *names[] = {
#define NAME(n) [SSPP_ ## n] = #n
NAME(VIG0), NAME(VIG1), NAME(VIG2),
NAME(RGB0), NAME(RGB1), NAME(RGB2),
NAME(DMA0), NAME(DMA1),
NAME(VIG3), NAME(RGB3),
NAME(CURSOR0), NAME(CURSOR1),
#undef NAME
};
return names[pipe];
}
static inline int pipe2nclients(enum mdp5_pipe pipe)
{
switch (pipe) {
case SSPP_RGB0:
case SSPP_RGB1:
case SSPP_RGB2:
case SSPP_RGB3:
return 1;
default:
return 3;
}
}
static inline uint32_t intf2err(int intf_num)
{
switch (intf_num) {
case 0: return MDP5_IRQ_INTF0_UNDER_RUN;
case 1: return MDP5_IRQ_INTF1_UNDER_RUN;
case 2: return MDP5_IRQ_INTF2_UNDER_RUN;
case 3: return MDP5_IRQ_INTF3_UNDER_RUN;
default: return 0;
}
}
static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
struct mdp5_interface *intf)
{
/*
* In case of DSI Command Mode, the Ping Pong's read pointer IRQ
* acts as a Vblank signal. The Ping Pong buffer used is bound to
* layer mixer.
*/
if ((intf->type == INTF_DSI) &&
(intf->mode == MDP5_INTF_DSI_MODE_COMMAND))
return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
if (intf->type == INTF_WB)
return MDP5_IRQ_WB_2_DONE;
switch (intf->num) {
case 0: return MDP5_IRQ_INTF0_VSYNC;
case 1: return MDP5_IRQ_INTF1_VSYNC;
case 2: return MDP5_IRQ_INTF2_VSYNC;
case 3: return MDP5_IRQ_INTF3_VSYNC;
default: return 0;
}
}
static inline uint32_t lm2ppdone(struct mdp5_hw_mixer *mixer)
{
return MDP5_IRQ_PING_PONG_0_DONE << mixer->pp;
}
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask,
uint32_t old_irqmask);
void mdp5_irq_preinstall(struct msm_kms *kms);
int mdp5_irq_postinstall(struct msm_kms *kms);
void mdp5_irq_uninstall(struct msm_kms *kms);
irqreturn_t mdp5_irq(struct msm_kms *kms);
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
int mdp5_irq_domain_init(struct mdp5_kms *mdp5_kms);
void mdp5_irq_domain_fini(struct mdp5_kms *mdp5_kms);
uint32_t mdp5_plane_get_flush(struct drm_plane *plane);
enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane);
enum mdp5_pipe mdp5_plane_right_pipe(struct drm_plane *plane);
struct drm_plane *mdp5_plane_init(struct drm_device *dev,
enum drm_plane_type type);
struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc);
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc);
struct mdp5_hw_mixer *mdp5_crtc_get_mixer(struct drm_crtc *crtc);
struct mdp5_pipeline *mdp5_crtc_get_pipeline(struct drm_crtc *crtc);
void mdp5_crtc_set_pipeline(struct drm_crtc *crtc);
void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc);
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
struct drm_plane *plane,
struct drm_plane *cursor_plane, int id);
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev,
struct mdp5_interface *intf, struct mdp5_ctl *ctl);
void mdp5_encoder_set_intf_mode(struct drm_encoder *encoder, bool cmd_mode);
int mdp5_encoder_get_linecount(struct drm_encoder *encoder);
u32 mdp5_encoder_get_framecount(struct drm_encoder *encoder);
#ifdef CONFIG_DRM_MSM_DSI
void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void mdp5_cmd_encoder_disable(struct drm_encoder *encoder);
void mdp5_cmd_encoder_enable(struct drm_encoder *encoder);
#else
static inline void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
}
static inline void mdp5_cmd_encoder_disable(struct drm_encoder *encoder)
{
}
static inline void mdp5_cmd_encoder_enable(struct drm_encoder *encoder)
{
}
#endif
#endif /* __MDP5_KMS_H__ */
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 The Linux Foundation. All rights reserved.
*/
#include "mdp5_kms.h"
/*
* As of now, there are only 2 combinations possible for source split:
*
* Left | Right
* -----|------
* LM0 | LM1
* LM2 | LM5
*
*/
static int lm_right_pair[] = { 1, -1, 5, -1, -1, -1 };
static int get_right_pair_idx(struct mdp5_kms *mdp5_kms, int lm)
{
int i;
int pair_lm;
pair_lm = lm_right_pair[lm];
if (pair_lm < 0)
return -EINVAL;
for (i = 0; i < mdp5_kms->num_hwmixers; i++) {
struct mdp5_hw_mixer *mixer = mdp5_kms->hwmixers[i];
if (mixer->lm == pair_lm)
return mixer->idx;
}
return -1;
}
int mdp5_mixer_assign(struct drm_atomic_commit *s, struct drm_crtc *crtc,
uint32_t caps, struct mdp5_hw_mixer **mixer,
struct mdp5_hw_mixer **r_mixer)
{
struct msm_drm_private *priv = s->dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct mdp5_global_state *global_state = mdp5_get_global_state(s);
struct mdp5_hw_mixer_state *new_state;
int i;
if (IS_ERR(global_state))
return PTR_ERR(global_state);
new_state = &global_state->hwmixer;
for (i = 0; i < mdp5_kms->num_hwmixers; i++) {
struct mdp5_hw_mixer *cur = mdp5_kms->hwmixers[i];
/*
* skip if already in-use by a different CRTC. If there is a
* mixer already assigned to this CRTC, it means this call is
* a request to get an additional right mixer. Assume that the
* existing mixer is the 'left' one, and try to see if we can
* get its corresponding 'right' pair.
*/
if (new_state->hwmixer_to_crtc[cur->idx] &&
new_state->hwmixer_to_crtc[cur->idx] != crtc)
continue;
/* skip if doesn't support some required caps: */
if (caps & ~cur->caps)
continue;
if (r_mixer) {
int pair_idx;
pair_idx = get_right_pair_idx(mdp5_kms, cur->lm);
if (pair_idx < 0)
return -EINVAL;
if (new_state->hwmixer_to_crtc[pair_idx])
continue;
*r_mixer = mdp5_kms->hwmixers[pair_idx];
}
/*
* prefer a pair-able LM over an unpairable one. We can
* switch the CRTC from Normal mode to Source Split mode
* without requiring a full modeset if we had already
* assigned this CRTC a pair-able LM.
*
* TODO: There will be assignment sequences which would
* result in the CRTC requiring a full modeset, even
* if we have the LM resources to prevent it. For a platform
* with a few displays, we don't run out of pair-able LMs
* so easily. For now, ignore the possibility of requiring
* a full modeset.
*/
if (!(*mixer) || cur->caps & MDP_LM_CAP_PAIR)
*mixer = cur;
}
if (!(*mixer))
return -ENOMEM;
if (r_mixer && !(*r_mixer))
return -ENOMEM;
DBG("assigning Layer Mixer %d to crtc %s", (*mixer)->lm, crtc->name);
new_state->hwmixer_to_crtc[(*mixer)->idx] = crtc;
if (r_mixer) {
DBG("assigning Right Layer Mixer %d to crtc %s", (*r_mixer)->lm,
crtc->name);
new_state->hwmixer_to_crtc[(*r_mixer)->idx] = crtc;
}
return 0;
}
int mdp5_mixer_release(struct drm_atomic_commit *s, struct mdp5_hw_mixer *mixer)
{
struct mdp5_global_state *global_state = mdp5_get_global_state(s);
struct mdp5_hw_mixer_state *new_state;
if (!mixer)
return 0;
if (IS_ERR(global_state))
return PTR_ERR(global_state);
new_state = &global_state->hwmixer;
if (WARN_ON(!new_state->hwmixer_to_crtc[mixer->idx]))
return -EINVAL;
DBG("%s: release from crtc %s", mixer->name,
new_state->hwmixer_to_crtc[mixer->idx]->name);
new_state->hwmixer_to_crtc[mixer->idx] = NULL;
return 0;
}
static const char * const mixer_names[] = {
"LM0", "LM1", "LM2", "LM3", "LM4", "LM5",
};
struct mdp5_hw_mixer *mdp5_mixer_init(struct drm_device *dev,
const struct mdp5_lm_instance *lm)
{
struct mdp5_hw_mixer *mixer;
mixer = devm_kzalloc(dev->dev, sizeof(*mixer), GFP_KERNEL);
if (!mixer)
return ERR_PTR(-ENOMEM);
mixer->name = mixer_names[lm->id];
mixer->lm = lm->id;
mixer->caps = lm->caps;
mixer->pp = lm->pp;
mixer->dspp = lm->dspp;
mixer->flush_mask = mdp_ctl_flush_mask_lm(lm->id);
return mixer;
}
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017 The Linux Foundation. All rights reserved.
*/
#ifndef __MDP5_LM_H__
#define __MDP5_LM_H__
/* represents a hw Layer Mixer, one (or more) is dynamically assigned to a crtc */
struct mdp5_hw_mixer {
int idx;
const char *name;
int lm; /* the LM instance # */
uint32_t caps;
int pp;
int dspp;
uint32_t flush_mask; /* used to commit LM registers */
};
/* global atomic state of assignment between CRTCs and Layer Mixers: */
struct mdp5_hw_mixer_state {
struct drm_crtc *hwmixer_to_crtc[8];
};
struct mdp5_hw_mixer *mdp5_mixer_init(struct drm_device *dev,
const struct mdp5_lm_instance *lm);
int mdp5_mixer_assign(struct drm_atomic_commit *s, struct drm_crtc *crtc,
uint32_t caps, struct mdp5_hw_mixer **mixer,
struct mdp5_hw_mixer **r_mixer);
int mdp5_mixer_release(struct drm_atomic_commit *s,
struct mdp5_hw_mixer *mixer);
#endif /* __MDP5_LM_H__ */
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include "mdp5_kms.h"
int mdp5_pipe_assign(struct drm_atomic_commit *s, struct drm_plane *plane,
uint32_t caps, uint32_t blkcfg,
struct mdp5_hw_pipe **hwpipe,
struct mdp5_hw_pipe **r_hwpipe)
{
struct msm_drm_private *priv = s->dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct mdp5_global_state *new_global_state, *old_global_state;
struct mdp5_hw_pipe_state *old_state, *new_state;
int i, j;
new_global_state = mdp5_get_global_state(s);
if (IS_ERR(new_global_state))
return PTR_ERR(new_global_state);
/* grab old_state after mdp5_get_global_state(), since now we hold lock: */
old_global_state = mdp5_get_existing_global_state(mdp5_kms);
old_state = &old_global_state->hwpipe;
new_state = &new_global_state->hwpipe;
for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
struct mdp5_hw_pipe *cur = mdp5_kms->hwpipes[i];
/* skip if already in-use.. check both new and old state,
* since we cannot immediately re-use a pipe that is
* released in the current update in some cases:
* (1) mdp5 can have SMP (non-double-buffered)
* (2) hw pipe previously assigned to different CRTC
* (vblanks might not be aligned)
*/
if (new_state->hwpipe_to_plane[cur->idx] ||
old_state->hwpipe_to_plane[cur->idx])
continue;
/* skip if doesn't support some required caps: */
if (caps & ~cur->caps)
continue;
/*
* don't assign a cursor pipe to a plane that isn't going to
* be used as a cursor
*/
if (cur->caps & MDP_PIPE_CAP_CURSOR &&
plane->type != DRM_PLANE_TYPE_CURSOR)
continue;
/* possible candidate, take the one with the
* fewest unneeded caps bits set:
*/
if (!(*hwpipe) || (hweight_long(cur->caps & ~caps) <
hweight_long((*hwpipe)->caps & ~caps))) {
bool r_found = false;
if (r_hwpipe) {
for (j = i + 1; j < mdp5_kms->num_hwpipes;
j++) {
struct mdp5_hw_pipe *r_cur =
mdp5_kms->hwpipes[j];
/* reject different types of hwpipes */
if (r_cur->caps != cur->caps)
continue;
/* respect priority, eg. VIG0 > VIG1 */
if (cur->pipe > r_cur->pipe)
continue;
*r_hwpipe = r_cur;
r_found = true;
break;
}
}
if (!r_hwpipe || r_found)
*hwpipe = cur;
}
}
if (!(*hwpipe))
return -ENOMEM;
if (r_hwpipe && !(*r_hwpipe))
return -ENOMEM;
if (mdp5_kms->smp) {
int ret;
/* We don't support SMP and 2 hwpipes/plane together */
WARN_ON(r_hwpipe);
DBG("%s: alloc SMP blocks", (*hwpipe)->name);
ret = mdp5_smp_assign(mdp5_kms->smp, &new_global_state->smp,
(*hwpipe)->pipe, blkcfg);
if (ret)
return -ENOMEM;
(*hwpipe)->blkcfg = blkcfg;
}
DBG("%s: assign to plane %s for caps %x",
(*hwpipe)->name, plane->name, caps);
new_state->hwpipe_to_plane[(*hwpipe)->idx] = plane;
if (r_hwpipe) {
DBG("%s: assign to right of plane %s for caps %x",
(*r_hwpipe)->name, plane->name, caps);
new_state->hwpipe_to_plane[(*r_hwpipe)->idx] = plane;
}
return 0;
}
int mdp5_pipe_release(struct drm_atomic_commit *s, struct mdp5_hw_pipe *hwpipe)
{
struct msm_drm_private *priv = s->dev->dev_private;
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
struct mdp5_global_state *state;
struct mdp5_hw_pipe_state *new_state;
if (!hwpipe)
return 0;
state = mdp5_get_global_state(s);
if (IS_ERR(state))
return PTR_ERR(state);
new_state = &state->hwpipe;
if (WARN_ON(!new_state->hwpipe_to_plane[hwpipe->idx]))
return -EINVAL;
DBG("%s: release from plane %s", hwpipe->name,
new_state->hwpipe_to_plane[hwpipe->idx]->name);
if (mdp5_kms->smp) {
DBG("%s: free SMP blocks", hwpipe->name);
mdp5_smp_release(mdp5_kms->smp, &state->smp, hwpipe->pipe);
}
new_state->hwpipe_to_plane[hwpipe->idx] = NULL;
return 0;
}
struct mdp5_hw_pipe *mdp5_pipe_init(struct drm_device *dev,
enum mdp5_pipe pipe,
uint32_t reg_offset, uint32_t caps)
{
struct mdp5_hw_pipe *hwpipe;
hwpipe = devm_kzalloc(dev->dev, sizeof(*hwpipe), GFP_KERNEL);
if (!hwpipe)
return ERR_PTR(-ENOMEM);
hwpipe->name = pipe2name(pipe);
hwpipe->pipe = pipe;
hwpipe->reg_offset = reg_offset;
hwpipe->caps = caps;
hwpipe->flush_mask = mdp_ctl_flush_mask_pipe(pipe);
return hwpipe;
}
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2016 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#ifndef __MDP5_PIPE_H__
#define __MDP5_PIPE_H__
/* TODO: Add SSPP_MAX in mdp5.xml.h */
#define SSPP_MAX (SSPP_CURSOR1 + 1)
/* represents a hw pipe, which is dynamically assigned to a plane */
struct mdp5_hw_pipe {
int idx;
const char *name;
enum mdp5_pipe pipe;
uint32_t reg_offset;
uint32_t caps;
uint32_t flush_mask; /* used to commit pipe registers */
/* number of smp blocks per plane, ie:
* nblks_y | (nblks_u << 8) | (nblks_v << 16)
*/
uint32_t blkcfg;
};
/* global atomic state of assignment between pipes and planes: */
struct mdp5_hw_pipe_state {
struct drm_plane *hwpipe_to_plane[SSPP_MAX];
};
int mdp5_pipe_assign(struct drm_atomic_commit *s, struct drm_plane *plane,
uint32_t caps, uint32_t blkcfg,
struct mdp5_hw_pipe **hwpipe,
struct mdp5_hw_pipe **r_hwpipe);
int mdp5_pipe_release(struct drm_atomic_commit *s, struct mdp5_hw_pipe *hwpipe);
struct mdp5_hw_pipe *mdp5_pipe_init(struct drm_device *dev,
enum mdp5_pipe pipe,
uint32_t reg_offset, uint32_t caps);
#endif /* __MDP5_PIPE_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,383 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <drm/drm_fourcc.h>
#include <drm/drm_util.h>
#include "mdp5_kms.h"
#include "mdp5_smp.h"
struct mdp5_smp {
struct drm_device *dev;
uint8_t reserved[MAX_CLIENTS]; /* fixed MMBs allocation per client */
int blk_cnt;
int blk_size;
/* register cache */
u32 alloc_w[22];
u32 alloc_r[22];
u32 pipe_reqprio_fifo_wm0[SSPP_MAX];
u32 pipe_reqprio_fifo_wm1[SSPP_MAX];
u32 pipe_reqprio_fifo_wm2[SSPP_MAX];
};
static inline
struct mdp5_kms *get_kms(struct mdp5_smp *smp)
{
struct msm_drm_private *priv = smp->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
static inline u32 pipe2client(enum mdp5_pipe pipe, int plane)
{
#define CID_UNUSED 0
if (WARN_ON(plane >= pipe2nclients(pipe)))
return CID_UNUSED;
/*
* Note on SMP clients:
* For ViG pipes, fetch Y/Cr/Cb-components clients are always
* consecutive, and in that order.
*
* e.g.:
* if mdp5_cfg->smp.clients[SSPP_VIG0] = N,
* Y plane's client ID is N
* Cr plane's client ID is N + 1
* Cb plane's client ID is N + 2
*/
return mdp5_cfg->smp.clients[pipe] + plane;
}
/* allocate blocks for the specified request: */
static int smp_request_block(struct mdp5_smp *smp,
struct mdp5_smp_state *state,
u32 cid, int nblks)
{
void *cs = state->client_state[cid];
int i, avail, cnt = smp->blk_cnt;
uint8_t reserved;
/* we shouldn't be requesting blocks for an in-use client: */
WARN_ON(!bitmap_empty(cs, cnt));
reserved = smp->reserved[cid];
if (reserved) {
nblks = max(0, nblks - reserved);
DBG("%d MMBs allocated (%d reserved)", nblks, reserved);
}
avail = cnt - bitmap_weight(state->state, cnt);
if (nblks > avail) {
DRM_DEV_ERROR(smp->dev->dev, "out of blks (req=%d > avail=%d)\n",
nblks, avail);
return -ENOSPC;
}
for (i = 0; i < nblks; i++) {
int blk = find_first_zero_bit(state->state, cnt);
set_bit(blk, cs);
set_bit(blk, state->state);
}
return 0;
}
static void set_fifo_thresholds(struct mdp5_smp *smp,
enum mdp5_pipe pipe, int nblks)
{
u32 smp_entries_per_blk = smp->blk_size / (128 / BITS_PER_BYTE);
u32 val;
/* 1/4 of SMP pool that is being fetched */
val = (nblks * smp_entries_per_blk) / 4;
smp->pipe_reqprio_fifo_wm0[pipe] = val * 1;
smp->pipe_reqprio_fifo_wm1[pipe] = val * 2;
smp->pipe_reqprio_fifo_wm2[pipe] = val * 3;
}
/*
* NOTE: looks like if horizontal decimation is used (if we supported that)
* then the width used to calculate SMP block requirements is the post-
* decimated width. Ie. SMP buffering sits downstream of decimation (which
* presumably happens during the dma from scanout buffer).
*/
uint32_t mdp5_smp_calculate(struct mdp5_smp *smp,
const struct msm_format *format,
u32 width, bool hdecim)
{
const struct drm_format_info *info = drm_format_info(format->pixel_format);
int i, hsub, nplanes, nlines;
uint32_t blkcfg = 0;
nplanes = info->num_planes;
hsub = info->hsub;
/* different if BWC (compressed framebuffer?) enabled: */
nlines = 2;
/* Newer MDPs have split/packing logic, which fetches sub-sampled
* U and V components (splits them from Y if necessary) and packs
* them together, writes to SMP using a single client.
*/
if (format->chroma_sample > CHROMA_FULL) {
nplanes = 2;
/* if decimation is enabled, HW decimates less on the
* sub sampled chroma components
*/
if (hdecim && (hsub > 1))
hsub = 1;
}
for (i = 0; i < nplanes; i++) {
int n, fetch_stride, cpp;
cpp = info->cpp[i];
fetch_stride = width * cpp / (i ? hsub : 1);
n = DIV_ROUND_UP(fetch_stride * nlines, smp->blk_size);
blkcfg |= (n << (8 * i));
}
return blkcfg;
}
int mdp5_smp_assign(struct mdp5_smp *smp, struct mdp5_smp_state *state,
enum mdp5_pipe pipe, uint32_t blkcfg)
{
struct mdp5_kms *mdp5_kms = get_kms(smp);
struct drm_device *dev = mdp5_kms->dev;
int i, ret;
for (i = 0; i < pipe2nclients(pipe); i++) {
u32 cid = pipe2client(pipe, i);
int n = blkcfg & 0xff;
if (!n)
continue;
DBG("%s[%d]: request %d SMP blocks", pipe2name(pipe), i, n);
ret = smp_request_block(smp, state, cid, n);
if (ret) {
DRM_DEV_ERROR(dev->dev, "Cannot allocate %d SMP blocks: %d\n",
n, ret);
return ret;
}
blkcfg >>= 8;
}
state->assigned |= (1 << pipe);
return 0;
}
/* Release SMP blocks for all clients of the pipe */
void mdp5_smp_release(struct mdp5_smp *smp, struct mdp5_smp_state *state,
enum mdp5_pipe pipe)
{
int i;
int cnt = smp->blk_cnt;
for (i = 0; i < pipe2nclients(pipe); i++) {
u32 cid = pipe2client(pipe, i);
void *cs = state->client_state[cid];
/* update global state: */
bitmap_andnot(state->state, state->state, cs, cnt);
/* clear client's state */
bitmap_zero(cs, cnt);
}
state->released |= (1 << pipe);
}
/* NOTE: SMP_ALLOC_* regs are *not* double buffered, so release has to
* happen after scanout completes.
*/
static unsigned update_smp_state(struct mdp5_smp *smp,
u32 cid, mdp5_smp_state_t *assigned)
{
int cnt = smp->blk_cnt;
unsigned nblks = 0;
u32 blk, val;
for_each_set_bit(blk, *assigned, cnt) {
int idx = blk / 3;
int fld = blk % 3;
val = smp->alloc_w[idx];
switch (fld) {
case 0:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT0(cid);
break;
case 1:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT1(cid);
break;
case 2:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT2(cid);
break;
}
smp->alloc_w[idx] = val;
smp->alloc_r[idx] = val;
nblks++;
}
return nblks;
}
static void write_smp_alloc_regs(struct mdp5_smp *smp)
{
struct mdp5_kms *mdp5_kms = get_kms(smp);
int i, num_regs;
num_regs = smp->blk_cnt / 3 + 1;
for (i = 0; i < num_regs; i++) {
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(i),
smp->alloc_w[i]);
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_R_REG(i),
smp->alloc_r[i]);
}
}
static void write_smp_fifo_regs(struct mdp5_smp *smp)
{
struct mdp5_kms *mdp5_kms = get_kms(smp);
int i;
for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i];
enum mdp5_pipe pipe = hwpipe->pipe;
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_0(pipe),
smp->pipe_reqprio_fifo_wm0[pipe]);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_1(pipe),
smp->pipe_reqprio_fifo_wm1[pipe]);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_2(pipe),
smp->pipe_reqprio_fifo_wm2[pipe]);
}
}
void mdp5_smp_prepare_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state)
{
enum mdp5_pipe pipe;
for_each_set_bit(pipe, &state->assigned, sizeof(state->assigned) * 8) {
unsigned i, nblks = 0;
for (i = 0; i < pipe2nclients(pipe); i++) {
u32 cid = pipe2client(pipe, i);
void *cs = state->client_state[cid];
nblks += update_smp_state(smp, cid, cs);
DBG("assign %s:%u, %u blks",
pipe2name(pipe), i, nblks);
}
set_fifo_thresholds(smp, pipe, nblks);
}
write_smp_alloc_regs(smp);
write_smp_fifo_regs(smp);
state->assigned = 0;
}
void mdp5_smp_complete_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state)
{
enum mdp5_pipe pipe;
for_each_set_bit(pipe, &state->released, sizeof(state->released) * 8) {
DBG("release %s", pipe2name(pipe));
set_fifo_thresholds(smp, pipe, 0);
}
write_smp_fifo_regs(smp);
state->released = 0;
}
void mdp5_smp_dump(struct mdp5_smp *smp, struct drm_printer *p,
struct mdp5_global_state *global_state)
{
struct mdp5_kms *mdp5_kms = get_kms(smp);
struct mdp5_hw_pipe_state *hwpstate;
struct mdp5_smp_state *state;
int total = 0, i, j;
drm_printf(p, "name\tinuse\tplane\n");
drm_printf(p, "----\t-----\t-----\n");
/* grab these *after* we hold the state_lock */
hwpstate = &global_state->hwpipe;
state = &global_state->smp;
for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i];
struct drm_plane *plane = hwpstate->hwpipe_to_plane[hwpipe->idx];
enum mdp5_pipe pipe = hwpipe->pipe;
for (j = 0; j < pipe2nclients(pipe); j++) {
u32 cid = pipe2client(pipe, j);
void *cs = state->client_state[cid];
int inuse = bitmap_weight(cs, smp->blk_cnt);
drm_printf(p, "%s:%d\t%d\t%s\n",
pipe2name(pipe), j, inuse,
plane ? plane->name : "(null)");
total += inuse;
}
}
drm_printf(p, "TOTAL:\t%d\t(of %d)\n", total, smp->blk_cnt);
drm_printf(p, "AVAIL:\t%d\n", smp->blk_cnt -
bitmap_weight(state->state, smp->blk_cnt));
}
struct mdp5_smp *mdp5_smp_init(struct mdp5_kms *mdp5_kms, const struct mdp5_smp_block *cfg)
{
struct drm_device *dev = mdp5_kms->dev;
struct mdp5_smp_state *state;
struct mdp5_global_state *global_state;
struct mdp5_smp *smp;
smp = devm_kzalloc(dev->dev, sizeof(*smp), GFP_KERNEL);
if (unlikely(!smp))
return ERR_PTR(-ENOMEM);
smp->dev = mdp5_kms->dev;
smp->blk_cnt = cfg->mmb_count;
smp->blk_size = cfg->mmb_size;
global_state = mdp5_get_existing_global_state(mdp5_kms);
state = &global_state->smp;
/* statically tied MMBs cannot be re-allocated: */
bitmap_copy(state->state, cfg->reserved_state, smp->blk_cnt);
memcpy(smp->reserved, cfg->reserved, sizeof(smp->reserved));
return smp;
}
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#ifndef __MDP5_SMP_H__
#define __MDP5_SMP_H__
#include <drm/drm_print.h>
#include "msm_drv.h"
/*
* SMP - Shared Memory Pool:
*
* SMP blocks are shared between all the clients, where each plane in
* a scanout buffer is a SMP client. Ie. scanout of 3 plane I420 on
* pipe VIG0 => 3 clients: VIG0_Y, VIG0_CB, VIG0_CR.
*
* Based on the size of the attached scanout buffer, a certain # of
* blocks must be allocated to that client out of the shared pool.
*
* In some hw, some blocks are statically allocated for certain pipes
* and CANNOT be re-allocated (eg: MMB0 and MMB1 both tied to RGB0).
*
*
* Atomic SMP State:
*
* On atomic updates that modify SMP configuration, the state is cloned
* (copied) and modified. For test-only, or in cases where atomic
* update fails (or if we hit ww_mutex deadlock/backoff condition) the
* new state is simply thrown away.
*
* Because the SMP registers are not double buffered, updates are a
* two step process:
*
* 1) in _prepare_commit() we configure things (via read-modify-write)
* for the newly assigned pipes, so we don't take away blocks
* assigned to pipes that are still scanning out
* 2) in _complete_commit(), after vblank/etc, we clear things for the
* released clients, since at that point old pipes are no longer
* scanning out.
*/
struct mdp5_smp_state {
/* global state of what blocks are in use: */
mdp5_smp_state_t state;
/* per client state of what blocks they are using: */
mdp5_smp_state_t client_state[MAX_CLIENTS];
/* assigned pipes (hw updated at _prepare_commit()): */
unsigned long assigned;
/* released pipes (hw updated at _complete_commit()): */
unsigned long released;
};
struct mdp5_kms;
struct mdp5_smp;
/*
* SMP module prototypes:
* mdp5_smp_init() returns a SMP @handler,
* which is then used to call the other mdp5_smp_*(handler, ...) functions.
*/
struct mdp5_smp *mdp5_smp_init(struct mdp5_kms *mdp5_kms,
const struct mdp5_smp_block *cfg);
struct mdp5_global_state;
void mdp5_smp_dump(struct mdp5_smp *smp, struct drm_printer *p,
struct mdp5_global_state *global_state);
uint32_t mdp5_smp_calculate(struct mdp5_smp *smp,
const struct msm_format *format,
u32 width, bool hdecim);
int mdp5_smp_assign(struct mdp5_smp *smp, struct mdp5_smp_state *state,
enum mdp5_pipe pipe, uint32_t blkcfg);
void mdp5_smp_release(struct mdp5_smp *smp, struct mdp5_smp_state *state,
enum mdp5_pipe pipe);
void mdp5_smp_prepare_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state);
void mdp5_smp_complete_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state);
#endif /* __MDP5_SMP_H__ */