/** * struct dsi_phy_10nm_tuning_cfg - Holds 10nm PHY tuning config parameters. * @rescode_offset_top: Offset for pull-up legs rescode. * @rescode_offset_bot: Offset for pull-down legs rescode. * @vreg_ctrl: vreg ctrl to drive LDO level
*/ struct dsi_phy_10nm_tuning_cfg {
u8 rescode_offset_top[DSI_LANE_MAX];
u8 rescode_offset_bot[DSI_LANE_MAX];
u8 vreg_ctrl;
};
/* * Global list of private DSI PLL struct pointers. We need this for bonded DSI * mode, where the master PLL's clk_ops needs access the slave's private data
*/ staticstruct dsi_pll_10nm *pll_10nm_list[DSI_MAX];
writel(0x80, base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_ONE);
writel(0x03, base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_TWO);
writel(0x00, base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_THREE);
writel(0x00, base + REG_DSI_10nm_PHY_PLL_DSM_DIVIDER);
writel(0x4e, base + REG_DSI_10nm_PHY_PLL_FEEDBACK_DIVIDER);
writel(0x40, base + REG_DSI_10nm_PHY_PLL_CALIBRATION_SETTINGS);
writel(0xba, base + REG_DSI_10nm_PHY_PLL_BAND_SEL_CAL_SETTINGS_THREE);
writel(0x0c, base + REG_DSI_10nm_PHY_PLL_FREQ_DETECT_SETTINGS_ONE);
writel(0x00, base + REG_DSI_10nm_PHY_PLL_OUTDIV);
writel(0x00, base + REG_DSI_10nm_PHY_PLL_CORE_OVERRIDE);
writel(0x08, base + REG_DSI_10nm_PHY_PLL_PLL_DIGITAL_TIMERS_TWO);
writel(0x08, base + REG_DSI_10nm_PHY_PLL_PLL_PROP_GAIN_RATE_1);
writel(0xc0, base + REG_DSI_10nm_PHY_PLL_PLL_BAND_SET_RATE_1);
writel(0xfa, base + REG_DSI_10nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1);
writel(0x4c, base + REG_DSI_10nm_PHY_PLL_PLL_FL_INT_GAIN_PFILT_BAND_1);
writel(0x80, base + REG_DSI_10nm_PHY_PLL_PLL_LOCK_OVERRIDE);
writel(0x29, base + REG_DSI_10nm_PHY_PLL_PFILT);
writel(0x3f, base + REG_DSI_10nm_PHY_PLL_IFILT);
}
/* * To avoid any stray glitches while abruptly powering down the PLL * make sure to gate the clock using the clock enable bit before * powering down the PLL
*/
dsi_pll_disable_global_clk(pll_10nm);
writel(0, pll_10nm->phy->base + REG_DSI_10nm_PHY_CMN_PLL_CNTRL);
dsi_pll_disable_sub(pll_10nm); if (pll_10nm->slave) {
dsi_pll_disable_global_clk(pll_10nm->slave);
dsi_pll_disable_sub(pll_10nm->slave);
} /* flush, ensure all register writes are done */
wmb();
pll_10nm->phy->pll_on = false;
}
val = readl(pll_10nm->phy->pll_base + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
val &= ~0x3;
val |= cached->pll_out_div;
writel(val, pll_10nm->phy->pll_base + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
switch (phy->usecase) { case MSM_DSI_PHY_STANDALONE: break; case MSM_DSI_PHY_MASTER:
pll_10nm->slave = pll_10nm_list[(pll_10nm->phy->id + 1) % DSI_MAX]; break; case MSM_DSI_PHY_SLAVE:
data = 0x1; /* external PLL */ break; default: return -EINVAL;
}
/* set PLL src */
writel(data << 2, base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
return 0;
}
/* * The post dividers and mux clocks are created using the standard divider and * mux API. Unlike the 14nm PHY, the slave PLL doesn't need its dividers/mux * state to follow the master PLL's divider/mux state. Therefore, we don't * require special clock ops that also configure the slave PLL registers
*/ staticint pll_10nm_register(struct dsi_pll_10nm *pll_10nm, struct clk_hw **provided_clocks)
{ char clk_name[32]; struct clk_init_data vco_init = {
.parent_data = &(conststruct clk_parent_data) {
.fw_name = "ref",
},
.num_parents = 1,
.name = clk_name,
.flags = CLK_IGNORE_UNUSED,
.ops = &clk_ops_dsi_pll_10nm_vco,
}; struct device *dev = &pll_10nm->phy->pdev->dev; struct clk_hw *hw, *pll_out_div, *pll_bit, *pll_by_2_bit; struct clk_hw *pll_post_out_div, *pclk_mux; int ret;
pll_10nm = devm_kzalloc(&pdev->dev, sizeof(*pll_10nm), GFP_KERNEL); if (!pll_10nm) return -ENOMEM;
DBG("DSI PLL%d", phy->id);
pll_10nm_list[phy->id] = pll_10nm;
spin_lock_init(&pll_10nm->postdiv_lock);
pll_10nm->phy = phy;
ret = pll_10nm_register(pll_10nm, phy->provided_clocks->hws); if (ret) {
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret); return ret;
}
phy->vco_hw = &pll_10nm->clk_hw;
/* TODO: Remove this when we have proper display handover support */
msm_dsi_phy_pll_save_state(phy);
/* * Store also proper vco_current_rate, because its value will be used in * dsi_10nm_pll_restore_state().
*/ if (!dsi_pll_10nm_vco_recalc_rate(&pll_10nm->clk_hw, VCO_REF_CLK_RATE))
pll_10nm->vco_current_rate = pll_10nm->phy->cfg->min_pll_rate;
data = readl(base + REG_DSI_10nm_PHY_CMN_PLL_CNTRL);
mb(); /* make sure read happened */
return (data & BIT(0));
}
staticvoid dsi_phy_hw_v3_0_config_lpcdrx(struct msm_dsi_phy *phy, bool enable)
{ void __iomem *lane_base = phy->lane_base; int phy_lane_0 = 0; /* TODO: Support all lane swap configs */
/* * LPRX and CDRX need to enabled only for physical data lane * corresponding to the logical data lane 0
*/ if (enable)
writel(0x3, lane_base + REG_DSI_10nm_PHY_LN_LPRX_CTRL(phy_lane_0)); else
writel(0, lane_base + REG_DSI_10nm_PHY_LN_LPRX_CTRL(phy_lane_0));
}
if (phy->cfg->quirks & DSI_PHY_10NM_QUIRK_OLD_TIMINGS)
tx_dctrl[3] = 0x02;
/* Strength ctrl settings */ for (i = 0; i < 5; i++) {
writel(0x55, lane_base + REG_DSI_10nm_PHY_LN_LPTX_STR_CTRL(i)); /* * Disable LPRX and CDRX for all lanes. And later on, it will * be only enabled for the physical data lane corresponding * to the logical data lane 0
*/
writel(0, lane_base + REG_DSI_10nm_PHY_LN_LPRX_CTRL(i));
writel(0x0, lane_base + REG_DSI_10nm_PHY_LN_PIN_SWAP(i));
writel(0x88, lane_base + REG_DSI_10nm_PHY_LN_HSTX_STR_CTRL(i));
}
dsi_phy_hw_v3_0_config_lpcdrx(phy, true);
/* other settings */ for (i = 0; i < 5; i++) {
writel(0, lane_base + REG_DSI_10nm_PHY_LN_CFG0(i));
writel(0, lane_base + REG_DSI_10nm_PHY_LN_CFG1(i));
writel(0, lane_base + REG_DSI_10nm_PHY_LN_CFG2(i));
writel(i == 4 ? 0x80 : 0x0, lane_base + REG_DSI_10nm_PHY_LN_CFG3(i));
if (dsi_phy_hw_v3_0_is_pll_on(phy))
pr_warn("PLL turned on before configuring PHY\n");
/* wait for REFGEN READY */
ret = readl_poll_timeout_atomic(base + REG_DSI_10nm_PHY_CMN_PHY_STATUS,
status, (status & BIT(0)),
delay_us, timeout_us); if (ret) {
pr_err("Ref gen not ready. Aborting\n"); return -EINVAL;
}
/* de-assert digital and pll power down */
data = BIT(6) | BIT(5);
writel(data, base + REG_DSI_10nm_PHY_CMN_CTRL_0);
/* Assert PLL core reset */
writel(0x00, base + REG_DSI_10nm_PHY_CMN_PLL_CNTRL);
/* turn off resync FIFO */
writel(0x00, base + REG_DSI_10nm_PHY_CMN_RBUF_CTRL);
/* Select MS1 byte-clk */
writel(0x10, base + REG_DSI_10nm_PHY_CMN_GLBL_CTRL);
/* Enable LDO with platform specific drive level/amplitude adjustment */
writel(tuning_cfg->vreg_ctrl, base + REG_DSI_10nm_PHY_CMN_VREG_CTRL);
/* Configure PHY lane swap (TODO: we need to calculate this) */
writel(0x21, base + REG_DSI_10nm_PHY_CMN_LANE_CFG0);
writel(0x84, base + REG_DSI_10nm_PHY_CMN_LANE_CFG1);
/* DSI PHY timings */
writel(timing->hs_halfbyte_en, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_0);
writel(timing->clk_zero, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_1);
writel(timing->clk_prepare, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_2);
writel(timing->clk_trail, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_3);
writel(timing->hs_exit, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_4);
writel(timing->hs_zero, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_5);
writel(timing->hs_prepare, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_6);
writel(timing->hs_trail, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_7);
writel(timing->hs_rqst, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_8);
writel(timing->ta_go | (timing->ta_sure << 3), base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_9);
writel(timing->ta_get, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_10);
writel(0x00, base + REG_DSI_10nm_PHY_CMN_TIMING_CTRL_11);
/* Remove power down from all blocks */
writel(0x7f, base + REG_DSI_10nm_PHY_CMN_CTRL_0);
/* power up lanes */
data = readl(base + REG_DSI_10nm_PHY_CMN_CTRL_0);
/* TODO: only power up lanes that are used */
data |= 0x1F;
writel(data, base + REG_DSI_10nm_PHY_CMN_CTRL_0);
writel(0x1F, base + REG_DSI_10nm_PHY_CMN_LANE_CTRL0);
/* Select full-rate mode */
writel(0x40, base + REG_DSI_10nm_PHY_CMN_CTRL_2);
ret = dsi_10nm_set_usecase(phy); if (ret) {
DRM_DEV_ERROR(&phy->pdev->dev, "%s: set pll usecase failed, %d\n",
__func__, ret); return ret;
}
/* DSI lane settings */
dsi_phy_hw_v3_0_lane_settings(phy);
tuning_cfg = devm_kzalloc(dev, sizeof(*tuning_cfg), GFP_KERNEL); if (!tuning_cfg) return -ENOMEM;
/* Drive strength adjustment parameters */
ret = of_property_read_u8_array(dev->of_node, "qcom,phy-rescode-offset-top",
offset_top, DSI_LANE_MAX); if (ret && ret != -EINVAL) {
DRM_DEV_ERROR(dev, "failed to parse qcom,phy-rescode-offset-top, %d\n", ret); return ret;
}
for (i = 0; i < DSI_LANE_MAX; i++) { if (offset_top[i] < -32 || offset_top[i] > 31) {
DRM_DEV_ERROR(dev, "qcom,phy-rescode-offset-top value %d is not in range [-32..31]\n",
offset_top[i]); return -EINVAL;
}
tuning_cfg->rescode_offset_top[i] = 0x3f & offset_top[i];
}
ret = of_property_read_u8_array(dev->of_node, "qcom,phy-rescode-offset-bot",
offset_bot, DSI_LANE_MAX); if (ret && ret != -EINVAL) {
DRM_DEV_ERROR(dev, "failed to parse qcom,phy-rescode-offset-bot, %d\n", ret); return ret;
}
for (i = 0; i < DSI_LANE_MAX; i++) { if (offset_bot[i] < -32 || offset_bot[i] > 31) {
DRM_DEV_ERROR(dev, "qcom,phy-rescode-offset-bot value %d is not in range [-32..31]\n",
offset_bot[i]); return -EINVAL;
}
tuning_cfg->rescode_offset_bot[i] = 0x3f & offset_bot[i];
}
/* Drive level/amplitude adjustment parameters */
ret = of_property_read_u32(dev->of_node, "qcom,phy-drive-ldo-level", &ldo_level); if (ret && ret != -EINVAL) {
DRM_DEV_ERROR(dev, "failed to parse qcom,phy-drive-ldo-level, %d\n", ret); return ret;
}
switch (ldo_level) { case 375:
level = 0; break; case 400:
level = 1; break; case 425:
level = 2; break; case 450:
level = 3; break; case 475:
level = 4; break; case 500:
level = 5; break; default:
DRM_DEV_ERROR(dev, "qcom,phy-drive-ldo-level %d is not supported\n", ldo_level); return -EINVAL;
}
tuning_cfg->vreg_ctrl = 0x58 | (0x7 & level);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.