18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2019 NXP 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/module.h> 78c2ecf20Sopenharmony_ci#include <linux/device.h> 88c2ecf20Sopenharmony_ci#include <linux/of_device.h> 98c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 108c2ecf20Sopenharmony_ci#include <linux/devfreq.h> 118c2ecf20Sopenharmony_ci#include <linux/pm_opp.h> 128c2ecf20Sopenharmony_ci#include <linux/clk.h> 138c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 148c2ecf20Sopenharmony_ci#include <linux/arm-smccc.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define IMX_SIP_DDR_DVFS 0xc2000004 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* Query available frequencies. */ 198c2ecf20Sopenharmony_ci#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10 208c2ecf20Sopenharmony_ci#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* 238c2ecf20Sopenharmony_ci * This should be in a 1:1 mapping with devicetree OPPs but 248c2ecf20Sopenharmony_ci * firmware provides additional info. 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_cistruct imx8m_ddrc_freq { 278c2ecf20Sopenharmony_ci unsigned long rate; 288c2ecf20Sopenharmony_ci unsigned long smcarg; 298c2ecf20Sopenharmony_ci int dram_core_parent_index; 308c2ecf20Sopenharmony_ci int dram_alt_parent_index; 318c2ecf20Sopenharmony_ci int dram_apb_parent_index; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* Hardware limitation */ 358c2ecf20Sopenharmony_ci#define IMX8M_DDRC_MAX_FREQ_COUNT 4 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* 388c2ecf20Sopenharmony_ci * i.MX8M DRAM Controller clocks have the following structure (abridged): 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * +----------+ |\ +------+ 418c2ecf20Sopenharmony_ci * | dram_pll |-------|M| dram_core | | 428c2ecf20Sopenharmony_ci * +----------+ |U|---------->| D | 438c2ecf20Sopenharmony_ci * /--|X| | D | 448c2ecf20Sopenharmony_ci * dram_alt_root | |/ | R | 458c2ecf20Sopenharmony_ci * | | C | 468c2ecf20Sopenharmony_ci * +---------+ | | 478c2ecf20Sopenharmony_ci * |FIX DIV/4| | | 488c2ecf20Sopenharmony_ci * +---------+ | | 498c2ecf20Sopenharmony_ci * composite: | | | 508c2ecf20Sopenharmony_ci * +----------+ | | | 518c2ecf20Sopenharmony_ci * | dram_alt |----/ | | 528c2ecf20Sopenharmony_ci * +----------+ | | 538c2ecf20Sopenharmony_ci * | dram_apb |-------------------->| | 548c2ecf20Sopenharmony_ci * +----------+ +------+ 558c2ecf20Sopenharmony_ci * 568c2ecf20Sopenharmony_ci * The dram_pll is used for higher rates and dram_alt is used for lower rates. 578c2ecf20Sopenharmony_ci * 588c2ecf20Sopenharmony_ci * Frequency switching is implemented in TF-A (via SMC call) and can change the 598c2ecf20Sopenharmony_ci * configuration of the clocks, including mux parents. The dram_alt and 608c2ecf20Sopenharmony_ci * dram_apb clocks are "imx composite" and their parent can change too. 618c2ecf20Sopenharmony_ci * 628c2ecf20Sopenharmony_ci * We need to prepare/enable the new mux parents head of switching and update 638c2ecf20Sopenharmony_ci * their information afterwards. 648c2ecf20Sopenharmony_ci */ 658c2ecf20Sopenharmony_cistruct imx8m_ddrc { 668c2ecf20Sopenharmony_ci struct devfreq_dev_profile profile; 678c2ecf20Sopenharmony_ci struct devfreq *devfreq; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci /* For frequency switching: */ 708c2ecf20Sopenharmony_ci struct clk *dram_core; 718c2ecf20Sopenharmony_ci struct clk *dram_pll; 728c2ecf20Sopenharmony_ci struct clk *dram_alt; 738c2ecf20Sopenharmony_ci struct clk *dram_apb; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci int freq_count; 768c2ecf20Sopenharmony_ci struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT]; 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv, 808c2ecf20Sopenharmony_ci unsigned long rate) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct imx8m_ddrc_freq *freq; 838c2ecf20Sopenharmony_ci int i; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* 868c2ecf20Sopenharmony_ci * Firmware reports values in MT/s, so we round-down from Hz 878c2ecf20Sopenharmony_ci * Rounding is extra generous to ensure a match. 888c2ecf20Sopenharmony_ci */ 898c2ecf20Sopenharmony_ci rate = DIV_ROUND_CLOSEST(rate, 250000); 908c2ecf20Sopenharmony_ci for (i = 0; i < priv->freq_count; ++i) { 918c2ecf20Sopenharmony_ci freq = &priv->freq_table[i]; 928c2ecf20Sopenharmony_ci if (freq->rate == rate || 938c2ecf20Sopenharmony_ci freq->rate + 1 == rate || 948c2ecf20Sopenharmony_ci freq->rate - 1 == rate) 958c2ecf20Sopenharmony_ci return freq; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci return NULL; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic void imx8m_ddrc_smc_set_freq(int target_freq) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct arm_smccc_res res; 1048c2ecf20Sopenharmony_ci u32 online_cpus = 0; 1058c2ecf20Sopenharmony_ci int cpu; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci local_irq_disable(); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci for_each_online_cpu(cpu) 1108c2ecf20Sopenharmony_ci online_cpus |= (1 << (cpu * 8)); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* change the ddr freqency */ 1138c2ecf20Sopenharmony_ci arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus, 1148c2ecf20Sopenharmony_ci 0, 0, 0, 0, 0, &res); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci local_irq_enable(); 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic struct clk *clk_get_parent_by_index(struct clk *clk, int index) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct clk_hw *hw; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return hw ? hw->clk : NULL; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 1318c2ecf20Sopenharmony_ci struct clk *new_dram_core_parent; 1328c2ecf20Sopenharmony_ci struct clk *new_dram_alt_parent; 1338c2ecf20Sopenharmony_ci struct clk *new_dram_apb_parent; 1348c2ecf20Sopenharmony_ci int ret; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* 1378c2ecf20Sopenharmony_ci * Fetch new parents 1388c2ecf20Sopenharmony_ci * 1398c2ecf20Sopenharmony_ci * new_dram_alt_parent and new_dram_apb_parent are optional but 1408c2ecf20Sopenharmony_ci * new_dram_core_parent is not. 1418c2ecf20Sopenharmony_ci */ 1428c2ecf20Sopenharmony_ci new_dram_core_parent = clk_get_parent_by_index( 1438c2ecf20Sopenharmony_ci priv->dram_core, freq->dram_core_parent_index - 1); 1448c2ecf20Sopenharmony_ci if (!new_dram_core_parent) { 1458c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch new dram_core parent\n"); 1468c2ecf20Sopenharmony_ci return -EINVAL; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci if (freq->dram_alt_parent_index) { 1498c2ecf20Sopenharmony_ci new_dram_alt_parent = clk_get_parent_by_index( 1508c2ecf20Sopenharmony_ci priv->dram_alt, 1518c2ecf20Sopenharmony_ci freq->dram_alt_parent_index - 1); 1528c2ecf20Sopenharmony_ci if (!new_dram_alt_parent) { 1538c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch new dram_alt parent\n"); 1548c2ecf20Sopenharmony_ci return -EINVAL; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci } else 1578c2ecf20Sopenharmony_ci new_dram_alt_parent = NULL; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (freq->dram_apb_parent_index) { 1608c2ecf20Sopenharmony_ci new_dram_apb_parent = clk_get_parent_by_index( 1618c2ecf20Sopenharmony_ci priv->dram_apb, 1628c2ecf20Sopenharmony_ci freq->dram_apb_parent_index - 1); 1638c2ecf20Sopenharmony_ci if (!new_dram_apb_parent) { 1648c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch new dram_apb parent\n"); 1658c2ecf20Sopenharmony_ci return -EINVAL; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci } else 1688c2ecf20Sopenharmony_ci new_dram_apb_parent = NULL; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* increase reference counts and ensure clks are ON before switch */ 1718c2ecf20Sopenharmony_ci ret = clk_prepare_enable(new_dram_core_parent); 1728c2ecf20Sopenharmony_ci if (ret) { 1738c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable new dram_core parent: %d\n", 1748c2ecf20Sopenharmony_ci ret); 1758c2ecf20Sopenharmony_ci goto out; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci ret = clk_prepare_enable(new_dram_alt_parent); 1788c2ecf20Sopenharmony_ci if (ret) { 1798c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable new dram_alt parent: %d\n", 1808c2ecf20Sopenharmony_ci ret); 1818c2ecf20Sopenharmony_ci goto out_disable_core_parent; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci ret = clk_prepare_enable(new_dram_apb_parent); 1848c2ecf20Sopenharmony_ci if (ret) { 1858c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable new dram_apb parent: %d\n", 1868c2ecf20Sopenharmony_ci ret); 1878c2ecf20Sopenharmony_ci goto out_disable_alt_parent; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci imx8m_ddrc_smc_set_freq(freq->smcarg); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* update parents in clk tree after switch. */ 1938c2ecf20Sopenharmony_ci ret = clk_set_parent(priv->dram_core, new_dram_core_parent); 1948c2ecf20Sopenharmony_ci if (ret) 1958c2ecf20Sopenharmony_ci dev_warn(dev, "failed to set dram_core parent: %d\n", ret); 1968c2ecf20Sopenharmony_ci if (new_dram_alt_parent) { 1978c2ecf20Sopenharmony_ci ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent); 1988c2ecf20Sopenharmony_ci if (ret) 1998c2ecf20Sopenharmony_ci dev_warn(dev, "failed to set dram_alt parent: %d\n", 2008c2ecf20Sopenharmony_ci ret); 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci if (new_dram_apb_parent) { 2038c2ecf20Sopenharmony_ci ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent); 2048c2ecf20Sopenharmony_ci if (ret) 2058c2ecf20Sopenharmony_ci dev_warn(dev, "failed to set dram_apb parent: %d\n", 2068c2ecf20Sopenharmony_ci ret); 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* 2108c2ecf20Sopenharmony_ci * Explicitly refresh dram PLL rate. 2118c2ecf20Sopenharmony_ci * 2128c2ecf20Sopenharmony_ci * Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be 2138c2ecf20Sopenharmony_ci * automatically refreshed when clk_get_rate is called on children. 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_ci clk_get_rate(priv->dram_pll); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci /* 2188c2ecf20Sopenharmony_ci * clk_set_parent transfer the reference count from old parent. 2198c2ecf20Sopenharmony_ci * now we drop extra reference counts used during the switch 2208c2ecf20Sopenharmony_ci */ 2218c2ecf20Sopenharmony_ci clk_disable_unprepare(new_dram_apb_parent); 2228c2ecf20Sopenharmony_ciout_disable_alt_parent: 2238c2ecf20Sopenharmony_ci clk_disable_unprepare(new_dram_alt_parent); 2248c2ecf20Sopenharmony_ciout_disable_core_parent: 2258c2ecf20Sopenharmony_ci clk_disable_unprepare(new_dram_core_parent); 2268c2ecf20Sopenharmony_ciout: 2278c2ecf20Sopenharmony_ci return ret; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 2338c2ecf20Sopenharmony_ci struct imx8m_ddrc_freq *freq_info; 2348c2ecf20Sopenharmony_ci struct dev_pm_opp *new_opp; 2358c2ecf20Sopenharmony_ci unsigned long old_freq, new_freq; 2368c2ecf20Sopenharmony_ci int ret; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci new_opp = devfreq_recommended_opp(dev, freq, flags); 2398c2ecf20Sopenharmony_ci if (IS_ERR(new_opp)) { 2408c2ecf20Sopenharmony_ci ret = PTR_ERR(new_opp); 2418c2ecf20Sopenharmony_ci dev_err(dev, "failed to get recommended opp: %d\n", ret); 2428c2ecf20Sopenharmony_ci return ret; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci dev_pm_opp_put(new_opp); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci old_freq = clk_get_rate(priv->dram_core); 2478c2ecf20Sopenharmony_ci if (*freq == old_freq) 2488c2ecf20Sopenharmony_ci return 0; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci freq_info = imx8m_ddrc_find_freq(priv, *freq); 2518c2ecf20Sopenharmony_ci if (!freq_info) 2528c2ecf20Sopenharmony_ci return -EINVAL; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* 2558c2ecf20Sopenharmony_ci * Read back the clk rate to verify switch was correct and so that 2568c2ecf20Sopenharmony_ci * we can report it on all error paths. 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_ci ret = imx8m_ddrc_set_freq(dev, freq_info); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci new_freq = clk_get_rate(priv->dram_core); 2618c2ecf20Sopenharmony_ci if (ret) 2628c2ecf20Sopenharmony_ci dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n", 2638c2ecf20Sopenharmony_ci *freq, old_freq, ret, new_freq); 2648c2ecf20Sopenharmony_ci else if (*freq != new_freq) 2658c2ecf20Sopenharmony_ci dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n", 2668c2ecf20Sopenharmony_ci *freq, old_freq, new_freq); 2678c2ecf20Sopenharmony_ci else 2688c2ecf20Sopenharmony_ci dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n", 2698c2ecf20Sopenharmony_ci *freq, old_freq); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci return ret; 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci *freq = clk_get_rate(priv->dram_core); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci return 0; 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic int imx8m_ddrc_get_dev_status(struct device *dev, 2848c2ecf20Sopenharmony_ci struct devfreq_dev_status *stat) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci stat->busy_time = 0; 2898c2ecf20Sopenharmony_ci stat->total_time = 0; 2908c2ecf20Sopenharmony_ci stat->current_frequency = clk_get_rate(priv->dram_core); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return 0; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic int imx8m_ddrc_init_freq_info(struct device *dev) 2968c2ecf20Sopenharmony_ci{ 2978c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 2988c2ecf20Sopenharmony_ci struct arm_smccc_res res; 2998c2ecf20Sopenharmony_ci int index; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci /* An error here means DDR DVFS API not supported by firmware */ 3028c2ecf20Sopenharmony_ci arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT, 3038c2ecf20Sopenharmony_ci 0, 0, 0, 0, 0, 0, &res); 3048c2ecf20Sopenharmony_ci priv->freq_count = res.a0; 3058c2ecf20Sopenharmony_ci if (priv->freq_count <= 0 || 3068c2ecf20Sopenharmony_ci priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT) 3078c2ecf20Sopenharmony_ci return -ENODEV; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci for (index = 0; index < priv->freq_count; ++index) { 3108c2ecf20Sopenharmony_ci struct imx8m_ddrc_freq *freq = &priv->freq_table[index]; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO, 3138c2ecf20Sopenharmony_ci index, 0, 0, 0, 0, 0, &res); 3148c2ecf20Sopenharmony_ci /* Result should be strictly positive */ 3158c2ecf20Sopenharmony_ci if ((long)res.a0 <= 0) 3168c2ecf20Sopenharmony_ci return -ENODEV; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci freq->rate = res.a0; 3198c2ecf20Sopenharmony_ci freq->smcarg = index; 3208c2ecf20Sopenharmony_ci freq->dram_core_parent_index = res.a1; 3218c2ecf20Sopenharmony_ci freq->dram_alt_parent_index = res.a2; 3228c2ecf20Sopenharmony_ci freq->dram_apb_parent_index = res.a3; 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci /* dram_core has 2 options: dram_pll or dram_alt_root */ 3258c2ecf20Sopenharmony_ci if (freq->dram_core_parent_index != 1 && 3268c2ecf20Sopenharmony_ci freq->dram_core_parent_index != 2) 3278c2ecf20Sopenharmony_ci return -ENODEV; 3288c2ecf20Sopenharmony_ci /* dram_apb and dram_alt have exactly 8 possible parents */ 3298c2ecf20Sopenharmony_ci if (freq->dram_alt_parent_index > 8 || 3308c2ecf20Sopenharmony_ci freq->dram_apb_parent_index > 8) 3318c2ecf20Sopenharmony_ci return -ENODEV; 3328c2ecf20Sopenharmony_ci /* dram_core from alt requires explicit dram_alt parent */ 3338c2ecf20Sopenharmony_ci if (freq->dram_core_parent_index == 2 && 3348c2ecf20Sopenharmony_ci freq->dram_alt_parent_index == 0) 3358c2ecf20Sopenharmony_ci return -ENODEV; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci return 0; 3398c2ecf20Sopenharmony_ci} 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cistatic int imx8m_ddrc_check_opps(struct device *dev) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv = dev_get_drvdata(dev); 3448c2ecf20Sopenharmony_ci struct imx8m_ddrc_freq *freq_info; 3458c2ecf20Sopenharmony_ci struct dev_pm_opp *opp; 3468c2ecf20Sopenharmony_ci unsigned long freq; 3478c2ecf20Sopenharmony_ci int i, opp_count; 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci /* Enumerate DT OPPs and disable those not supported by firmware */ 3508c2ecf20Sopenharmony_ci opp_count = dev_pm_opp_get_opp_count(dev); 3518c2ecf20Sopenharmony_ci if (opp_count < 0) 3528c2ecf20Sopenharmony_ci return opp_count; 3538c2ecf20Sopenharmony_ci for (i = 0, freq = 0; i < opp_count; ++i, ++freq) { 3548c2ecf20Sopenharmony_ci opp = dev_pm_opp_find_freq_ceil(dev, &freq); 3558c2ecf20Sopenharmony_ci if (IS_ERR(opp)) { 3568c2ecf20Sopenharmony_ci dev_err(dev, "Failed enumerating OPPs: %ld\n", 3578c2ecf20Sopenharmony_ci PTR_ERR(opp)); 3588c2ecf20Sopenharmony_ci return PTR_ERR(opp); 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci dev_pm_opp_put(opp); 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci freq_info = imx8m_ddrc_find_freq(priv, freq); 3638c2ecf20Sopenharmony_ci if (!freq_info) { 3648c2ecf20Sopenharmony_ci dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n", 3658c2ecf20Sopenharmony_ci freq, DIV_ROUND_CLOSEST(freq, 250000)); 3668c2ecf20Sopenharmony_ci dev_pm_opp_disable(dev, freq); 3678c2ecf20Sopenharmony_ci } 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci return 0; 3718c2ecf20Sopenharmony_ci} 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_cistatic void imx8m_ddrc_exit(struct device *dev) 3748c2ecf20Sopenharmony_ci{ 3758c2ecf20Sopenharmony_ci dev_pm_opp_of_remove_table(dev); 3768c2ecf20Sopenharmony_ci} 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_cistatic int imx8m_ddrc_probe(struct platform_device *pdev) 3798c2ecf20Sopenharmony_ci{ 3808c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 3818c2ecf20Sopenharmony_ci struct imx8m_ddrc *priv; 3828c2ecf20Sopenharmony_ci const char *gov = DEVFREQ_GOV_USERSPACE; 3838c2ecf20Sopenharmony_ci int ret; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 3868c2ecf20Sopenharmony_ci if (!priv) 3878c2ecf20Sopenharmony_ci return -ENOMEM; 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci ret = imx8m_ddrc_init_freq_info(dev); 3928c2ecf20Sopenharmony_ci if (ret) { 3938c2ecf20Sopenharmony_ci dev_err(dev, "failed to init firmware freq info: %d\n", ret); 3948c2ecf20Sopenharmony_ci return ret; 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci priv->dram_core = devm_clk_get(dev, "core"); 3988c2ecf20Sopenharmony_ci if (IS_ERR(priv->dram_core)) { 3998c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->dram_core); 4008c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch core clock: %d\n", ret); 4018c2ecf20Sopenharmony_ci return ret; 4028c2ecf20Sopenharmony_ci } 4038c2ecf20Sopenharmony_ci priv->dram_pll = devm_clk_get(dev, "pll"); 4048c2ecf20Sopenharmony_ci if (IS_ERR(priv->dram_pll)) { 4058c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->dram_pll); 4068c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch pll clock: %d\n", ret); 4078c2ecf20Sopenharmony_ci return ret; 4088c2ecf20Sopenharmony_ci } 4098c2ecf20Sopenharmony_ci priv->dram_alt = devm_clk_get(dev, "alt"); 4108c2ecf20Sopenharmony_ci if (IS_ERR(priv->dram_alt)) { 4118c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->dram_alt); 4128c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch alt clock: %d\n", ret); 4138c2ecf20Sopenharmony_ci return ret; 4148c2ecf20Sopenharmony_ci } 4158c2ecf20Sopenharmony_ci priv->dram_apb = devm_clk_get(dev, "apb"); 4168c2ecf20Sopenharmony_ci if (IS_ERR(priv->dram_apb)) { 4178c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->dram_apb); 4188c2ecf20Sopenharmony_ci dev_err(dev, "failed to fetch apb clock: %d\n", ret); 4198c2ecf20Sopenharmony_ci return ret; 4208c2ecf20Sopenharmony_ci } 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci ret = dev_pm_opp_of_add_table(dev); 4238c2ecf20Sopenharmony_ci if (ret < 0) { 4248c2ecf20Sopenharmony_ci dev_err(dev, "failed to get OPP table\n"); 4258c2ecf20Sopenharmony_ci return ret; 4268c2ecf20Sopenharmony_ci } 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci ret = imx8m_ddrc_check_opps(dev); 4298c2ecf20Sopenharmony_ci if (ret < 0) 4308c2ecf20Sopenharmony_ci goto err; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci priv->profile.polling_ms = 1000; 4338c2ecf20Sopenharmony_ci priv->profile.target = imx8m_ddrc_target; 4348c2ecf20Sopenharmony_ci priv->profile.get_dev_status = imx8m_ddrc_get_dev_status; 4358c2ecf20Sopenharmony_ci priv->profile.exit = imx8m_ddrc_exit; 4368c2ecf20Sopenharmony_ci priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq; 4378c2ecf20Sopenharmony_ci priv->profile.initial_freq = clk_get_rate(priv->dram_core); 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci priv->devfreq = devm_devfreq_add_device(dev, &priv->profile, 4408c2ecf20Sopenharmony_ci gov, NULL); 4418c2ecf20Sopenharmony_ci if (IS_ERR(priv->devfreq)) { 4428c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->devfreq); 4438c2ecf20Sopenharmony_ci dev_err(dev, "failed to add devfreq device: %d\n", ret); 4448c2ecf20Sopenharmony_ci goto err; 4458c2ecf20Sopenharmony_ci } 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return 0; 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_cierr: 4508c2ecf20Sopenharmony_ci dev_pm_opp_of_remove_table(dev); 4518c2ecf20Sopenharmony_ci return ret; 4528c2ecf20Sopenharmony_ci} 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_cistatic const struct of_device_id imx8m_ddrc_of_match[] = { 4558c2ecf20Sopenharmony_ci { .compatible = "fsl,imx8m-ddrc", }, 4568c2ecf20Sopenharmony_ci { /* sentinel */ }, 4578c2ecf20Sopenharmony_ci}; 4588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match); 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_cistatic struct platform_driver imx8m_ddrc_platdrv = { 4618c2ecf20Sopenharmony_ci .probe = imx8m_ddrc_probe, 4628c2ecf20Sopenharmony_ci .driver = { 4638c2ecf20Sopenharmony_ci .name = "imx8m-ddrc-devfreq", 4648c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(imx8m_ddrc_of_match), 4658c2ecf20Sopenharmony_ci }, 4668c2ecf20Sopenharmony_ci}; 4678c2ecf20Sopenharmony_cimodule_platform_driver(imx8m_ddrc_platdrv); 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver"); 4708c2ecf20Sopenharmony_ciMODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>"); 4718c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 472