/* * This should be in a 1:1 mapping with devicetree OPPs but * firmware provides additional info.
*/ struct imx8m_ddrc_freq { unsignedlong rate; unsignedlong smcarg; int dram_core_parent_index; int dram_alt_parent_index; int dram_apb_parent_index;
};
/* * i.MX8M DRAM Controller clocks have the following structure (abridged): * * +----------+ |\ +------+ * | dram_pll |-------|M| dram_core | | * +----------+ |U|---------->| D | * /--|X| | D | * dram_alt_root | |/ | R | * | | C | * +---------+ | | * |FIX DIV/4| | | * +---------+ | | * composite: | | | * +----------+ | | | * | dram_alt |----/ | | * +----------+ | | * | dram_apb |-------------------->| | * +----------+ +------+ * * The dram_pll is used for higher rates and dram_alt is used for lower rates. * * Frequency switching is implemented in TF-A (via SMC call) and can change the * configuration of the clocks, including mux parents. The dram_alt and * dram_apb clocks are "imx composite" and their parent can change too. * * We need to prepare/enable the new mux parents head of switching and update * their information afterwards.
*/ struct imx8m_ddrc { struct devfreq_dev_profile profile; struct devfreq *devfreq;
/* For frequency switching: */ struct clk *dram_core; struct clk *dram_pll; struct clk *dram_alt; struct clk *dram_apb;
int freq_count; struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
};
/* * Firmware reports values in MT/s, so we round-down from Hz * Rounding is extra generous to ensure a match.
*/
rate = DIV_ROUND_CLOSEST(rate, 250000); for (i = 0; i < priv->freq_count; ++i) {
freq = &priv->freq_table[i]; if (freq->rate == rate ||
freq->rate + 1 == rate ||
freq->rate - 1 == rate) return freq;
}
/* * Fetch new parents * * new_dram_alt_parent and new_dram_apb_parent are optional but * new_dram_core_parent is not.
*/
new_dram_core_parent = clk_get_parent_by_index(
priv->dram_core, freq->dram_core_parent_index - 1); if (!new_dram_core_parent) {
dev_err(dev, "failed to fetch new dram_core parent\n"); return -EINVAL;
} if (freq->dram_alt_parent_index) {
new_dram_alt_parent = clk_get_parent_by_index(
priv->dram_alt,
freq->dram_alt_parent_index - 1); if (!new_dram_alt_parent) {
dev_err(dev, "failed to fetch new dram_alt parent\n"); return -EINVAL;
}
} else
new_dram_alt_parent = NULL;
if (freq->dram_apb_parent_index) {
new_dram_apb_parent = clk_get_parent_by_index(
priv->dram_apb,
freq->dram_apb_parent_index - 1); if (!new_dram_apb_parent) {
dev_err(dev, "failed to fetch new dram_apb parent\n"); return -EINVAL;
}
} else
new_dram_apb_parent = NULL;
/* increase reference counts and ensure clks are ON before switch */
ret = clk_prepare_enable(new_dram_core_parent); if (ret) {
dev_err(dev, "failed to enable new dram_core parent: %d\n",
ret); goto out;
}
ret = clk_prepare_enable(new_dram_alt_parent); if (ret) {
dev_err(dev, "failed to enable new dram_alt parent: %d\n",
ret); goto out_disable_core_parent;
}
ret = clk_prepare_enable(new_dram_apb_parent); if (ret) {
dev_err(dev, "failed to enable new dram_apb parent: %d\n",
ret); goto out_disable_alt_parent;
}
imx8m_ddrc_smc_set_freq(freq->smcarg);
/* update parents in clk tree after switch. */
ret = clk_set_parent(priv->dram_core, new_dram_core_parent); if (ret)
dev_warn(dev, "failed to set dram_core parent: %d\n", ret); if (new_dram_alt_parent) {
ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent); if (ret)
dev_warn(dev, "failed to set dram_alt parent: %d\n",
ret);
} if (new_dram_apb_parent) {
ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent); if (ret)
dev_warn(dev, "failed to set dram_apb parent: %d\n",
ret);
}
/* * Explicitly refresh dram PLL rate. * * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be * automatically refreshed when clk_get_rate is called on children.
*/
clk_get_rate(priv->dram_pll);
/* * clk_set_parent transfer the reference count from old parent. * now we drop extra reference counts used during the switch
*/
clk_disable_unprepare(new_dram_apb_parent);
out_disable_alt_parent:
clk_disable_unprepare(new_dram_alt_parent);
out_disable_core_parent:
clk_disable_unprepare(new_dram_core_parent);
out: return ret;
}
new_opp = devfreq_recommended_opp(dev, freq, flags); if (IS_ERR(new_opp)) {
ret = PTR_ERR(new_opp);
dev_err(dev, "failed to get recommended opp: %d\n", ret); return ret;
}
dev_pm_opp_put(new_opp);
old_freq = clk_get_rate(priv->dram_core); if (*freq == old_freq) return 0;
freq_info = imx8m_ddrc_find_freq(priv, *freq); if (!freq_info) return -EINVAL;
/* * Read back the clk rate to verify switch was correct and so that * we can report it on all error paths.
*/
ret = imx8m_ddrc_set_freq(dev, freq_info);
new_freq = clk_get_rate(priv->dram_core); if (ret)
dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
*freq, old_freq, ret, new_freq); elseif (*freq != new_freq)
dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
*freq, old_freq, new_freq); else
dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
*freq, old_freq);
/* An error here means DDR DVFS API not supported by firmware */
arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
0, 0, 0, 0, 0, 0, &res);
priv->freq_count = res.a0; if (priv->freq_count <= 0 ||
priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT) return -ENODEV;
for (index = 0; index < priv->freq_count; ++index) { struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
index, 0, 0, 0, 0, 0, &res); /* Result should be strictly positive */ if ((long)res.a0 <= 0) return -ENODEV;
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.