@@ -99,6 +99,7 @@ bool msm_dsi_manager_cmd_xfer_trigger(int id, u32 dma_base, u32 len);
void msm_dsi_manager_attach_dsi_device(int id, u32 device_flags);
int msm_dsi_manager_register(struct msm_dsi *msm_dsi);
void msm_dsi_manager_unregister(struct msm_dsi *msm_dsi);
+void msm_dsi_hw_readback(struct msm_dsi *msm_dsi);
/* msm dsi */
static inline bool msm_dsi_device_connected(struct msm_dsi *msm_dsi)
@@ -28,11 +28,14 @@
#include <linux/regmap.h>
#include <video/mipi_display.h>
+#include "msm_drv.h"
+#include "msm_kms.h"
#include "dsi.h"
#include "dsi.xml.h"
#include "sfpb.xml.h"
#include "dsi_cfg.h"
#include "msm_kms.h"
+#include "phy/dsi_phy.h"
static int dsi_get_version(const void __iomem *base, u32 *major, u32 *minor)
{
@@ -1866,6 +1869,35 @@ void msm_dsi_host_unregister(struct mipi_dsi_host *host)
}
}
+// XXX msm_dsi_host_hw_readback()... preserve the layer-cake?
+void msm_dsi_hw_readback(struct msm_dsi *msm_dsi)
+{
+ struct msm_dsi_host *msm_host = to_msm_dsi_host(msm_dsi->host);
+ struct msm_drm_private *priv = msm_dsi->dev->dev_private;
+ struct msm_kms *kms = priv->kms;
+
+ if (!__clk_is_enabled(msm_host->pixel_clk))
+ return;
+
+ kms->funcs->hw_readback_encoder(kms, msm_dsi->encoder);
+ msm_dsi->connector->state->crtc = msm_dsi->encoder->crtc;
+ msm_dsi->connector->state->best_encoder = msm_dsi->encoder;
+ msm_dsi->encoder->crtc->state->connector_mask =
+ (1 << drm_connector_index(msm_dsi->connector));
+ msm_host->power_on = true;
+
+ /* also fixup refcnt on regulators: */
+ dsi_host_regulator_enable(msm_host);
+
+ /* clocks will already be on, but with just a single refcnt,
+ * whereas normally (at least in some cases) both dsi and
+ * mdp5 take a reference to the same clks. So take an extra
+ * reference here to balance things out in the disable path.
+ */
+ dsi_bus_clk_enable(msm_host);
+ dsi_phy_enable_resource(msm_dsi->phy);
+}
+
int msm_dsi_host_xfer_prepare(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
{
@@ -354,11 +354,13 @@ static int dsi_phy_regulator_enable(struct msm_dsi_phy *phy)
return ret;
}
-static int dsi_phy_enable_resource(struct msm_dsi_phy *phy)
+int dsi_phy_enable_resource(struct msm_dsi_phy *phy)
{
struct device *dev = &phy->pdev->dev;
int ret;
+ DBG("");
+
pm_runtime_get_sync(dev);
ret = clk_prepare_enable(phy->ahb_clk);
@@ -372,6 +374,7 @@ static int dsi_phy_enable_resource(struct msm_dsi_phy *phy)
static void dsi_phy_disable_resource(struct msm_dsi_phy *phy)
{
+ DBG("");
clk_disable_unprepare(phy->ahb_clk);
pm_runtime_put_sync(&phy->pdev->dev);
}
@@ -103,6 +103,7 @@ int msm_dsi_dphy_timing_calc_v2(struct msm_dsi_dphy_timing *timing,
void msm_dsi_phy_set_src_pll(struct msm_dsi_phy *phy, int pll_id, u32 reg,
u32 bit_mask);
int msm_dsi_phy_init_common(struct msm_dsi_phy *phy);
+int dsi_phy_enable_resource(struct msm_dsi_phy *phy);
#endif /* __DSI_PHY_H__ */
@@ -1088,6 +1088,8 @@ struct msm_dsi_pll *msm_dsi_pll_14nm_init(struct platform_device *pdev, int id)
pll->save_state = dsi_pll_14nm_save_state;
pll->restore_state = dsi_pll_14nm_restore_state;
pll->set_usecase = dsi_pll_14nm_set_usecase;
+ pll->pll_on = pll_14nm_poll_for_ready(pll_14nm, POLL_MAX_READS,
+ POLL_TIMEOUT_US);
pll_14nm->vco_delay = 1;
@@ -313,6 +313,7 @@ static const struct clk_ops clk_ops_dsi_pll_28nm_vco = {
.prepare = msm_dsi_pll_helper_clk_prepare,
.unprepare = msm_dsi_pll_helper_clk_unprepare,
.is_enabled = dsi_pll_28nm_clk_is_enabled,
+ .is_prepared = dsi_pll_28nm_clk_is_enabled,
};
/*
@@ -619,6 +620,7 @@ struct msm_dsi_pll *msm_dsi_pll_28nm_init(struct platform_device *pdev,
pll->disable_seq = dsi_pll_28nm_disable_seq;
pll->save_state = dsi_pll_28nm_save_state;
pll->restore_state = dsi_pll_28nm_restore_state;
+ pll->pll_on = pll_28nm_poll_for_ready(pll_28nm, 10, 50);
if (type == MSM_DSI_PHY_28NM_HPM) {
pll_28nm->vco_delay = 1;
@@ -520,6 +520,8 @@ struct msm_dsi_pll *msm_dsi_pll_28nm_8960_init(struct platform_device *pdev,
pll->disable_seq = dsi_pll_28nm_disable_seq;
pll->save_state = dsi_pll_28nm_save_state;
pll->restore_state = dsi_pll_28nm_restore_state;
+ pll->pll_on = pll_28nm_poll_for_ready(pll_28nm, POLL_MAX_READS,
+ POLL_TIMEOUT_US);
pll->en_seq_cnt = 1;
pll->enable_seqs[0] = dsi_pll_28nm_enable_seq;
@@ -146,6 +146,14 @@ void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
mdp5_crtc_set_pipeline(encoder->crtc);
}
+struct drm_display_mode *mdp5_cmd_encoder_readback_mode(struct drm_encoder *encoder)
+{
+ // TODO can we even reconstruct something that resembles a mode
+ // in the case of cmd mode? (And will bootloader ever enable
+ // a panel in cmd mode, or can we just ignore this scenario?)
+ return NULL;
+}
+
void mdp5_cmd_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder);
@@ -409,6 +409,27 @@ static void mdp5_crtc_mode_set_nofb(struct drm_crtc *crtc)
spin_unlock_irqrestore(&mdp5_crtc->lm_lock, flags);
}
+void mdp5_crtc_readback(struct drm_crtc *crtc, struct drm_display_mode *mode,
+ enum mdp5_pipe pipe)
+{
+ struct drm_crtc_state *state = crtc->state;
+ struct drm_plane *plane = crtc->primary;
+ int ret;
+
+ // TODO probably just drop mdp5_state->enabled?
+ to_mdp5_crtc(crtc)->enabled = true;
+
+ state->active = true;
+ ret = drm_atomic_set_mode_for_crtc(state, mode);
+ WARN_ON(ret);
+ drm_mode_copy(&state->adjusted_mode, mode);
+
+ state->plane_mask = 1 << drm_plane_index(plane);
+ plane->state->crtc = crtc;
+
+ mdp5_plane_readback(plane, pipe);
+}
+
static void mdp5_crtc_disable(struct drm_crtc *crtc)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
@@ -446,6 +446,54 @@ int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
return 0;
}
+/* Look at the CTL_LAYER_REG's and figure out currently used layermixer
+ * and pipe. This is really only suitable for the single layer case,
+ * but that is all we'll get from the bootloader.
+ */
+struct mdp5_hw_mixer * mdp5_ctl_readback(struct mdp5_ctl *ctl,
+ enum mdp5_pipe *pipe)
+{
+ struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm);
+ int i;
+
+ for (i = 0; i < mdp5_kms->num_hwmixers; i++) {
+ struct mdp5_hw_mixer *mixer = mdp5_kms->hwmixers[i];
+ uint32_t reg;
+
+ reg = ctl_read(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm));
+
+ if (!reg)
+ continue;
+
+ if (FIELD(reg, MDP5_CTL_LAYER_REG_RGB0))
+ *pipe = SSPP_RGB0;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_RGB1))
+ *pipe = SSPP_RGB1;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_RGB2))
+ *pipe = SSPP_RGB2;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_RGB3))
+ *pipe = SSPP_RGB3;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_VIG0))
+ *pipe = SSPP_VIG0;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_VIG1))
+ *pipe = SSPP_VIG1;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_VIG2))
+ *pipe = SSPP_VIG2;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_VIG2))
+ *pipe = SSPP_VIG2;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_DMA0))
+ *pipe = SSPP_DMA0;
+ else if (FIELD(reg, MDP5_CTL_LAYER_REG_DMA1))
+ *pipe = SSPP_DMA1;
+ else
+ continue; /* WTF? */
+
+ return mixer;
+ }
+
+ return NULL;
+}
+
u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf)
{
if (intf->type == INTF_WB)
@@ -65,6 +65,9 @@ int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline,
enum mdp5_pipe r_stage[][MAX_PIPE_STAGE],
u32 stage_cnt, u32 ctl_blend_op_flags);
+struct mdp5_hw_mixer * mdp5_ctl_readback(struct mdp5_ctl *ctl,
+ enum mdp5_pipe *pipe);
+
/**
* mdp_ctl_flush_mask...() - Register FLUSH masks
*
@@ -101,6 +101,18 @@ static const struct drm_encoder_funcs mdp5_encoder_funcs = {
.destroy = mdp5_encoder_destroy,
};
+static void mdp5_encoder_setup_crtc_state(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_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;
+}
+
static void mdp5_vid_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
@@ -209,6 +221,70 @@ static void mdp5_vid_encoder_mode_set(struct drm_encoder *encoder,
mdp5_crtc_set_pipeline(encoder->crtc);
}
+static struct drm_display_mode *
+mdp5_vid_encoder_readback_mode(struct drm_encoder *encoder)
+{
+ struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
+ struct mdp5_kms *mdp5_kms = get_kms(encoder);
+ struct drm_display_mode *mode;
+ int intfn = mdp5_encoder->intf->num;
+ uint32_t hsync_ctl, display_hctl;
+ uint32_t vsync_period, vsync_len;
+ uint32_t display_v_start, display_v_end;
+ uint32_t dtv_hsync_skew = 0; /* get this from panel? */
+
+ mode = drm_mode_create(encoder->dev);
+
+ hsync_ctl = mdp5_read(mdp5_kms, REG_MDP5_INTF_HSYNC_CTL(intfn));
+ display_hctl = mdp5_read(mdp5_kms, REG_MDP5_INTF_DISPLAY_HCTL(intfn));
+ vsync_period = mdp5_read(mdp5_kms, REG_MDP5_INTF_VSYNC_PERIOD_F0(intfn));
+ display_v_start = mdp5_read(mdp5_kms, REG_MDP5_INTF_DISPLAY_VSTART_F0(intfn));
+ display_v_end = mdp5_read(mdp5_kms, REG_MDP5_INTF_DISPLAY_VEND_F0(intfn));
+ vsync_len = mdp5_read(mdp5_kms, REG_MDP5_INTF_VSYNC_LEN_F0(intfn));
+
+ // TODO I don't think there is a way to recover mode->clock??
+ mode->htotal = FIELD(hsync_ctl, MDP5_INTF_HSYNC_CTL_PERIOD);
+ mode->hsync_start = mode->htotal - FIELD(display_hctl, MDP5_INTF_DISPLAY_HCTL_START);
+ mode->hsync_end = FIELD(hsync_ctl, MDP5_INTF_HSYNC_CTL_PULSEW) + mode->hsync_start;
+ mode->hdisplay = FIELD(display_hctl, MDP5_INTF_DISPLAY_HCTL_END) + 1 -
+ mode->htotal + mode->hsync_start;
+
+ if (mdp5_encoder->intf->type == INTF_eDP) {
+ display_v_start -= mode->htotal - mode->hsync_start;
+ }
+
+ mode->vtotal = vsync_period / mode->htotal;
+ mode->vsync_start = mode->vtotal - ((display_v_start - dtv_hsync_skew) / mode->htotal);
+ mode->vsync_end = (vsync_len / mode->htotal) + mode->vsync_start;
+ mode->vdisplay = mode->vsync_start - ((vsync_period + dtv_hsync_skew - 1 - display_v_end) / mode->htotal);
+
+ // TODO maybe we want a flag to indicate that this is a readback
+ // mode, so not all fields are valid. (Ie. when comparing to
+ // another mode to decide whether to do full modeset or not,
+ // ignore the fields that are zero.)
+ mode->type = DRM_MODE_TYPE_DRIVER;
+
+ // XXX how can we get these? Maybe get modes from connector and
+ // see what fits (although that sounds ugly and annoying)
+ mode->clock = 148500;
+ mode->vrefresh = 60;
+
+ drm_mode_set_name(mode);
+
+ drm_mode_set_crtcinfo(mode, 0);
+
+ DBG("readback: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
+ mode->base.id, mode->name,
+ mode->vrefresh, mode->clock,
+ mode->hdisplay, mode->hsync_start,
+ mode->hsync_end, mode->htotal,
+ mode->vdisplay, mode->vsync_start,
+ mode->vsync_end, mode->vtotal,
+ mode->type, mode->flags);
+
+ return mode;
+}
+
static void mdp5_vid_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
@@ -282,6 +358,54 @@ static void mdp5_encoder_mode_set(struct drm_encoder *encoder,
mdp5_vid_encoder_mode_set(encoder, mode, adjusted_mode);
}
+void mdp5_encoder_readback(struct drm_encoder *encoder)
+{
+ struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
+ struct msm_drm_private *priv = encoder->dev->dev_private;
+ struct mdp5_kms *mdp5_kms = get_kms(encoder);
+ struct drm_display_mode *mode;
+ struct mdp5_crtc_state *mdp5_cstate;
+ struct mdp5_hw_mixer *mixer;
+ enum mdp5_pipe pipe;
+
+ if (mdp5_encoder->intf->mode == MDP5_INTF_DSI_MODE_COMMAND)
+ mode = mdp5_cmd_encoder_readback_mode(encoder);
+ else
+ mode = mdp5_vid_encoder_readback_mode(encoder);
+
+ if (!mode)
+ return;
+
+ /* things like the chosen ctl and mixer need to be read back
+ * and punched in to crtc state so that crtc knows what reg's
+ * to readback. The ctl is based on the encoder connected to
+ * the crtc (ie. us) and the ctl is needed to figure out what
+ * mixer is used.
+ *
+ * The good news is that since the crtc is fully virtualized,
+ * we can just pick any one!
+ */
+ encoder->crtc = priv->crtcs[0];
+ encoder->crtc->state->encoder_mask = (1 << drm_encoder_index(encoder));
+
+ mdp5_cstate = to_mdp5_crtc_state(encoder->crtc->state);
+
+ mdp5_encoder_setup_crtc_state(encoder, encoder->crtc->state);
+
+ mixer = mdp5_ctl_readback(mdp5_cstate->ctl, &pipe);
+ if (WARN_ON(!mixer))
+ return;
+
+ DBG("got mixer=%u, pipe=%s", mixer->lm, pipe2name(pipe));
+
+ mdp5_cstate->pipeline.mixer = mixer;
+ mdp5_kms->state->hwmixer.hwmixer_to_crtc[mixer->idx] = encoder->crtc;
+
+ mdp5_encoder->enabled = true;
+
+ mdp5_crtc_readback(encoder->crtc, mode, pipe);
+}
+
static void mdp5_encoder_disable(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
@@ -308,13 +432,7 @@ 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;
+ mdp5_encoder_setup_crtc_state(encoder, crtc_state);
return 0;
}
@@ -22,6 +22,7 @@
#include "msm_gem.h"
#include "msm_mmu.h"
#include "mdp5_kms.h"
+#include "dsi/dsi.h"
static const char *iommu_ports[] = {
"mdp_0",
@@ -33,6 +34,9 @@ static int mdp5_hw_init(struct msm_kms *kms)
struct platform_device *pdev = mdp5_kms->pdev;
unsigned long flags;
+ if (mdp5_kms->poweron_enabled)
+ return 0;
+
pm_runtime_get_sync(&pdev->dev);
mdp5_enable(mdp5_kms);
@@ -72,6 +76,56 @@ static int mdp5_hw_init(struct msm_kms *kms)
return 0;
}
+static void mdp5_hw_readback(struct msm_kms *kms)
+{
+ struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
+ struct msm_drm_private *priv = mdp5_kms->dev->dev_private;
+ unsigned i;
+
+ if (!mdp5_kms->poweron_enabled)
+ return;
+
+ /* We need to work backwards up the pipeline starting with the
+ * connectors.
+ *
+ * TODO eDP
+ * TODO HDMI
+ */
+ for (i = 0; i < ARRAY_SIZE(priv->dsi); i++)
+ if (priv->dsi[i])
+ msm_dsi_hw_readback(priv->dsi[i]);
+
+ if (mdp5_kms->smp) {
+ struct drm_plane *plane;
+
+ /* readback SMP state for active planes: */
+ drm_for_each_plane(plane, mdp5_kms->dev) {
+ struct mdp5_plane_state *pstate =
+ to_mdp5_plane_state(plane->state);
+ if (!plane->state->crtc ||
+ !plane->state->crtc->state->enable ||
+ !pstate->hwpipe)
+ continue;
+ mdp5_smp_readback(mdp5_kms->smp,
+ &mdp5_kms->state->smp,
+ pstate->hwpipe->pipe);
+ }
+ }
+
+ if (drm_debug & DRM_UT_DRIVER) {
+ struct drm_printer p = drm_info_printer(mdp5_kms->dev->dev);
+ drm_state_dump(mdp5_kms->dev, &p);
+ if (mdp5_kms->smp)
+ mdp5_smp_dump(mdp5_kms->smp, &p);
+ }
+}
+
+static void mdp5_hw_readback_encoder(struct msm_kms *kms,
+ struct drm_encoder *encoder)
+{
+ mdp5_encoder_readback(encoder);
+}
+
struct mdp5_state *mdp5_get_state(struct drm_atomic_state *s)
{
struct msm_drm_private *priv = s->dev->dev_private;
@@ -223,6 +277,8 @@ static int mdp5_kms_debugfs_init(struct msm_kms *kms, struct drm_minor *minor)
static const struct mdp_kms_funcs kms_funcs = {
.base = {
.hw_init = mdp5_hw_init,
+ .hw_readback = mdp5_hw_readback,
+ .hw_readback_encoder = mdp5_hw_readback_encoder,
.irq_preinstall = mdp5_irq_preinstall,
.irq_postinstall = mdp5_irq_postinstall,
.irq_uninstall = mdp5_irq_uninstall,
@@ -653,7 +709,9 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)
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);
+
+ if (!mdp5_kms->poweron_enabled)
+ 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);
}
@@ -908,11 +966,22 @@ static int mdp5_init(struct platform_device *pdev, struct drm_device *dev)
/* optional clocks: */
get_clk(pdev, &mdp5_kms->lut_clk, "lut_clk", false);
- /* 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:
+ /* If clock is enabled when driver is loaded, then bootloader
+ * has already set up the display:
*/
- clk_set_rate(mdp5_kms->core_clk, 200000000);
+ if (__clk_is_enabled(mdp5_kms->core_clk)) {
+ mdp5_kms->poweron_enabled = true;
+ mdp5_kms->enable_count++;
+
+ /* let pm-runtime know that we are already enabled: */
+ pm_runtime_get_noresume(&pdev->dev);
+ } else {
+ /* 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;
@@ -74,6 +74,7 @@ struct mdp5_kms {
spinlock_t resource_lock;
bool rpm_enabled;
+ bool poweron_enabled; /* display already on when driver probed */
struct mdp_irq error_handler;
@@ -273,12 +274,15 @@ 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);
+void mdp5_plane_readback(struct drm_plane *plane, enum mdp5_pipe pipe);
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);
+void mdp5_crtc_readback(struct drm_crtc *crtc, struct drm_display_mode *mode,
+ enum mdp5_pipe pipe);
struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc);
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc);
@@ -290,6 +294,7 @@ struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
struct drm_plane *plane,
struct drm_plane *cursor_plane, int id);
+void mdp5_encoder_readback(struct drm_encoder *encoder);
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev,
struct mdp5_interface *intf, struct mdp5_ctl *ctl);
int mdp5_vid_encoder_set_split_display(struct drm_encoder *encoder,
@@ -302,6 +307,7 @@ u32 mdp5_encoder_get_framecount(struct drm_encoder *encoder);
void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
+struct drm_display_mode *mdp5_cmd_encoder_readback_mode(struct drm_encoder *encoder);
void mdp5_cmd_encoder_disable(struct drm_encoder *encoder);
void mdp5_cmd_encoder_enable(struct drm_encoder *encoder);
int mdp5_cmd_encoder_set_split_display(struct drm_encoder *encoder,
@@ -312,6 +318,11 @@ static inline void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *adjusted_mode)
{
}
+static inline struct drm_display_mode *
+mdp5_cmd_encoder_readback_mode(struct drm_encoder *encoder)
+{
+ return NULL;
+}
static inline void mdp5_cmd_encoder_disable(struct drm_encoder *encoder)
{
}
@@ -192,6 +192,85 @@ mdp5_plane_atomic_print_state(struct drm_printer *p,
drm_printf(p, "\tstage=%s\n", stage2name(pstate->stage));
}
+void mdp5_plane_readback(struct drm_plane *plane, enum mdp5_pipe pipe)
+{
+ struct mdp5_kms *mdp5_kms = get_kms(plane);
+ struct drm_plane_state *state = plane->state;
+ struct mdp5_plane_state *mdp5_state = to_mdp5_plane_state(state);
+ struct msm_drm_private *priv = plane->dev->dev_private;
+ uint32_t reg, pitch, format;
+ unsigned i;
+
+ for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
+ struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i];
+
+ if (hwpipe->pipe == pipe) {
+ mdp5_state->hwpipe = hwpipe;
+ mdp5_kms->state->hwpipe.hwpipe_to_plane[hwpipe->idx] =
+ plane;
+ break;
+ }
+ }
+
+ if (WARN_ON(!mdp5_state->hwpipe))
+ return;
+
+ /* NOTE: we expect to just have a single layer, so punt on
+ * bothering to figure out how to map blending state back
+ * to plane state. And assume no scaling or anything fancy.
+ *
+ * TODO: we can't really differentiate between two non-
+ * overlaping planes hooked to one CRTC, and src-split
+ * mode, can we? For now just completely ignore r_hwpipe.
+ */
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_PIPE_SRC_IMG_SIZE(pipe));
+ state->src_w = FIELD(reg, MDP5_PIPE_SRC_SIZE_WIDTH) << 16;
+ state->src_h = FIELD(reg, MDP5_PIPE_SRC_SIZE_HEIGHT) << 16;
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_PIPE_SRC_XY(pipe));
+ state->src_x = FIELD(reg, MDP5_PIPE_SRC_XY_X) << 16;
+ state->src_y = FIELD(reg, MDP5_PIPE_SRC_XY_Y) << 16;
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_PIPE_OUT_SIZE(pipe));
+ state->crtc_w = FIELD(reg, MDP5_PIPE_OUT_SIZE_WIDTH);
+ state->crtc_h = FIELD(reg, MDP5_PIPE_OUT_SIZE_HEIGHT);
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_PIPE_OUT_XY(pipe));
+ state->crtc_x = FIELD(reg, MDP5_PIPE_OUT_XY_X);
+ state->crtc_y = FIELD(reg, MDP5_PIPE_OUT_XY_Y);
+
+ state->visible = true;
+ mdp5_state->stage = STAGE_BASE;
+
+ /* Reconstruct a framebuffer.
+ *
+ * TODO: assume XRGB8888 .. unpatched lk probably actually
+ * is using RGB565 or BGR888, but those also don't work with
+ * grub / EFI GOP, so just ignore it for now instead of
+ * figuring out how to map the format related registers back
+ * to a fourcc. Since we know lk is only going to use one
+ * of 3 formats, perhaps we could take a simplified approach
+ * by just looking at MDP5_PIPE_SRC_FORMAT.{A,R,G,B}_BPC.
+ * Note that mdp5 hw is expressive enough to encoded crazy
+ * formats (RXBG5856 anyone?) so I don't think we can really
+ * do a perfect job at mapping back to drm fourcc in any
+ * case.
+ */
+ format = DRM_FORMAT_XRGB8888;
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_A(pipe));
+ pitch = FIELD(reg, MDP5_PIPE_SRC_STRIDE_A_P0);
+
+ state->fb = msm_alloc_stolen_fb(plane->dev,
+ state->src_w >> 16,
+ state->src_h >> 16,
+ pitch, format);
+
+ priv->stolen_fb = state->fb;
+ drm_framebuffer_get(priv->stolen_fb);
+}
+
static void mdp5_plane_reset(struct drm_plane *plane)
{
struct mdp5_plane_state *mdp5_state;
@@ -257,6 +257,35 @@ static unsigned update_smp_state(struct mdp5_smp *smp,
return nblks;
}
+static void readback_smp_state(struct mdp5_smp *smp, u32 cid,
+ mdp5_smp_state_t *assigned)
+{
+ struct mdp5_kms *mdp5_kms = get_kms(smp);
+ u32 blk, reg, val;
+
+ for (blk = 0; blk < smp->blk_cnt; blk++) {
+ int idx = blk / 3;
+ int fld = blk % 3;
+
+ reg = mdp5_read(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx));
+
+ switch (fld) {
+ case 0:
+ val = FIELD(reg, MDP5_SMP_ALLOC_W_REG_CLIENT0);
+ break;
+ case 1:
+ val = FIELD(reg, MDP5_SMP_ALLOC_W_REG_CLIENT1);
+ break;
+ case 2:
+ val = FIELD(reg, MDP5_SMP_ALLOC_W_REG_CLIENT2);
+ break;
+ }
+
+ if (val == cid)
+ set_bit(blk, *assigned);
+ }
+}
+
void mdp5_smp_prepare_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state)
{
enum mdp5_pipe pipe;
@@ -292,6 +321,22 @@ void mdp5_smp_complete_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state
state->released = 0;
}
+void mdp5_smp_readback(struct mdp5_smp *smp, struct mdp5_smp_state *state,
+ enum mdp5_pipe pipe)
+{
+ unsigned i;
+
+ DBG("readback SMP state for %s", pipe2name(pipe));
+
+ for (i = 0; i < pipe2nclients(pipe); i++) {
+ u32 cid = pipe2client(pipe, i);
+ void *cs = state->client_state[cid];
+
+ readback_smp_state(smp, cid, cs);
+ bitmap_or(state->state, state->state, cs, smp->blk_cnt);
+ }
+}
+
void mdp5_smp_dump(struct mdp5_smp *smp, struct drm_printer *p)
{
struct mdp5_kms *mdp5_kms = get_kms(smp);
@@ -94,5 +94,7 @@ void mdp5_smp_release(struct mdp5_smp *smp, struct mdp5_smp_state *state,
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);
+void mdp5_smp_readback(struct mdp5_smp *smp, struct mdp5_smp_state *state,
+ enum mdp5_pipe pipe);
#endif /* __MDP5_SMP_H__ */
@@ -545,6 +545,9 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
drm_mode_config_reset(ddev);
+ if (kms->funcs->hw_readback)
+ kms->funcs->hw_readback(kms);
+
#ifdef CONFIG_DRM_FBDEV_EMULATION
if (fbdev)
priv->fbdev = msm_fbdev_init(ddev);
@@ -20,6 +20,7 @@
#include <linux/kernel.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/component.h>
@@ -109,6 +110,7 @@ struct msm_drm_private {
struct msm_file_private *lastctx;
struct drm_fb_helper *fbdev;
+ struct drm_framebuffer *stolen_fb;
struct msm_rd_state *rd;
struct msm_perf_state *perf;
@@ -78,7 +78,7 @@ static int msm_fbdev_create(struct drm_fb_helper *helper,
struct fb_info *fbi = NULL;
uint64_t paddr;
uint32_t format;
- int ret, pitch;
+ int ret;
format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth);
@@ -86,9 +86,16 @@ static int msm_fbdev_create(struct drm_fb_helper *helper,
sizes->surface_height, sizes->surface_bpp,
sizes->fb_width, sizes->fb_height);
- pitch = align_pitch(sizes->surface_width, sizes->surface_bpp);
- fb = msm_alloc_stolen_fb(dev, sizes->surface_width,
- sizes->surface_height, pitch, format);
+ if (priv->stolen_fb && (priv->stolen_fb->width == sizes->surface_width) &&
+ (priv->stolen_fb->height == sizes->surface_height) &&
+ (priv->stolen_fb->format->format == format)) {
+ DBG("Reusing stolen fb\n");
+ fb = priv->stolen_fb;
+ } else {
+ int pitch = align_pitch(sizes->surface_width, sizes->surface_bpp);
+ fb = msm_alloc_stolen_fb(dev, sizes->surface_width,
+ sizes->surface_height, pitch, format);
+ }
if (IS_ERR(fb)) {
dev_err(dev->dev, "failed to allocate fb\n");
@@ -33,6 +33,8 @@
struct msm_kms_funcs {
/* hw initialization: */
int (*hw_init)(struct msm_kms *kms);
+ void (*hw_readback)(struct msm_kms *kms);
+ void (*hw_readback_encoder)(struct msm_kms *kms, struct drm_encoder *encoder);
/* irq handling: */
void (*irq_preinstall)(struct msm_kms *kms);
int (*irq_postinstall)(struct msm_kms *kms);