From patchwork Tue Aug 20 15:45:10 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 11104145 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E7FD414DB for ; Tue, 20 Aug 2019 15:46:48 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C0F0722CE3 for ; Tue, 20 Aug 2019 15:46:48 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="rjTluMqE" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org C0F0722CE3 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=nxp.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=xLK4CC4uFtAk5JTgmv3T64lvb8LNZhgDU+3ZX277wgk=; b=rjTluMqESVznd73X9gHGzj7G+S mGNfsEwHlnBwPsaR29sOYLcOr4OtK/WI84uYLCSHv1b3WdK5vo7JcIahdSXLUYztrMzVOvbrP+ChU jAhqLtO0v2CASFDfmYexijzp38hjjPbLzyCwrmqF0wtTPgB0KmdrAblGmrcekVbxnmSLsbsH6vOYy LzHaQCumMC5K9I9WNiP8PyVk9miDBe/dX/8X2dmXa0PrOv1p2S97EAwMp/UUGO6pA5jmBnoWVWOaD WDhyw2sSTbIwrLwo0yylTQgZ+3/hfMkIukYdk5VlAFaLsfz6k28AadytT1wcwY+L0t72gjDwJKZvd oC5CdV/Q==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92 #3 (Red Hat Linux)) id 1i06LQ-0008RQ-45; Tue, 20 Aug 2019 15:46:48 +0000 Received: from inva020.nxp.com ([92.121.34.13]) by bombadil.infradead.org with esmtps (Exim 4.92 #3 (Red Hat Linux)) id 1i06K7-00073Q-Bb for linux-arm-kernel@lists.infradead.org; Tue, 20 Aug 2019 15:45:29 +0000 Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 1A83F1A027A; Tue, 20 Aug 2019 17:45:26 +0200 (CEST) Received: from inva024.eu-rdc02.nxp.com (inva024.eu-rdc02.nxp.com [134.27.226.22]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 0C4BB1A0100; Tue, 20 Aug 2019 17:45:26 +0200 (CEST) Received: from fsr-ub1864-112.ea.freescale.net (fsr-ub1864-112.ea.freescale.net [10.171.82.98]) by inva024.eu-rdc02.nxp.com (Postfix) with ESMTP id 098B320604; Tue, 20 Aug 2019 17:45:25 +0200 (CEST) From: Leonard Crestez To: Stephen Boyd , Shawn Guo , MyungJoo Ham , Chanwoo Choi , Rob Herring Subject: [PATCH v2 5/7] PM / devfreq: Add dynamic scaling for imx ddr controller Date: Tue, 20 Aug 2019 18:45:10 +0300 Message-Id: <9edd80a4dcde2f141724fd3b15514aaf95c6b21e.1566315740.git.leonard.crestez@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: References: In-Reply-To: References: X-Virus-Scanned: ClamAV using ClamSMTP X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20190820_084527_836629_52A49BD5 X-CRM114-Status: GOOD ( 22.13 ) X-Spam-Score: -2.3 (--) X-Spam-Report: SpamAssassin version 3.4.2 on bombadil.infradead.org summary: Content analysis details: (-2.3 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [92.121.34.13 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Rutland , =?utf-8?b?QXJ0dXIgxZp3aWdvxYQ=?= , Abel Vesa , Saravana Kannan , Anson Huang , linux-arm-kernel@lists.infradead.org, Viresh Kumar , Michael Turquette , linux-pm@vger.kernel.org, linux-imx@nxp.com, Krzysztof Kozlowski , linux-clk@vger.kernel.org, Kyungmin Park , Alexandre Bailon , kernel@pengutronix.de, Dong Aisheng , Fabio Estevam , Georgi Djakov , devicetree@vger.kernel.org, Jacky Bai MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org Add driver for dynamic scaling the DDR Controller on imx8m chips. Actual frequency switching is implemented in TF-A, this driver just wraps the SMC calls and updates the clk tree. Signed-off-by: Leonard Crestez --- drivers/devfreq/Makefile | 2 +- drivers/devfreq/imx-ddrc.c | 374 +++++++++++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 drivers/devfreq/imx-ddrc.c diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index c2463ed4c934..eba138914c74 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -7,11 +7,11 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o # DEVFREQ Drivers obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o -obj-$(CONFIG_ARM_IMX_DEVFREQ) += imx-devfreq.o +obj-$(CONFIG_ARM_IMX_DEVFREQ) += imx-devfreq.o imx-ddrc.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o # DEVFREQ Event Drivers diff --git a/drivers/devfreq/imx-ddrc.c b/drivers/devfreq/imx-ddrc.c new file mode 100644 index 000000000000..253138f608e7 --- /dev/null +++ b/drivers/devfreq/imx-ddrc.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX_SIP_DDR_DVFS 0xc2000004 + +/* Values starting from 0 switch to specific frequency */ +#define IMX_SIP_DDR_FREQ_SET_HIGH 0x00 + +/* Deprecated after moving IRQ handling to ATF */ +#define IMX_SIP_DDR_DVFS_WAIT_CHANGE 0x0F + +/* Query available frequencies. */ +#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10 +#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11 + +/* + * This should be in a 1:1 mapping with devicetree OPPs but + * firmware provides additional info. + */ +struct imx_ddrc_freq { + unsigned long rate; + unsigned long smcarg; + int dram_core_parent_index; + int dram_alt_parent_index; + int dram_apb_parent_index; +}; + +/* Hardware limitation */ +#define IMX_DDRC_MAX_FREQ_COUNT 4 + +/* + * imx DRAM controller + * + * imx 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 imx_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 imx_ddrc_freq freq_table[IMX_DDRC_MAX_FREQ_COUNT]; +}; + +static struct imx_ddrc_freq *imx_ddrc_find_freq(struct imx_ddrc *priv, + unsigned long rate) +{ + int i; + + for (i = 0; i < priv->freq_count; ++i) + if (priv->freq_table[i].rate == rate) + return &priv->freq_table[i]; + + return NULL; +} + +static void imx_ddrc_smc_set_freq(int target_freq) +{ + struct arm_smccc_res res; + u32 online_cpus = 0; + int cpu; + + local_irq_disable(); + + for_each_online_cpu(cpu) + online_cpus |= (1 << (cpu * 8)); + + /* change the ddr freqency */ + arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); +} + +struct clk *clk_get_parent_by_index(struct clk *clk, int index) +{ + struct clk_hw *hw; + + hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index); + + return hw ? hw->clk : NULL; +} + +static int imx_ddrc_set_freq(struct device *dev, struct imx_ddrc_freq *freq) +{ + struct imx_ddrc *priv = dev_get_drvdata(dev); + struct clk *new_dram_core_parent; + struct clk *new_dram_alt_parent; + struct clk *new_dram_apb_parent; + int ret; + + new_dram_core_parent = clk_get_parent_by_index( + priv->dram_core, freq->dram_core_parent_index - 1); + new_dram_alt_parent = clk_get_parent_by_index( + priv->dram_alt, freq->dram_alt_parent_index - 1); + new_dram_apb_parent = clk_get_parent_by_index( + priv->dram_apb, freq->dram_apb_parent_index - 1); + + /* increase reference counts and ensure clks are ON before switch */ + ret = clk_prepare_enable(new_dram_core_parent); + if (ret) { + dev_err(dev, "failed enable new dram_core parent: %d\n", ret); + goto out; + } + ret = clk_prepare_enable(new_dram_alt_parent); + if (ret) { + dev_err(dev, "failed enable new dram_alt parent: %d\n", ret); + goto out_dis_core; + } + ret = clk_prepare_enable(new_dram_apb_parent); + if (ret) { + dev_err(dev, "failed enable new dram_apb parent: %d\n", ret); + goto out_dis_alt; + } + + imx_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_err(dev, "failed 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_err(dev, "failed 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_err(dev, "failed set dram_apb parent: %d\n", ret); + } + + /* + * 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_dis_alt: + clk_disable_unprepare(new_dram_alt_parent); +out_dis_core: + clk_disable_unprepare(new_dram_core_parent); +out: + return ret; +} + +static int imx_ddrc_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct imx_ddrc *priv = dev_get_drvdata(dev); + struct imx_ddrc_freq *freq_info; + struct dev_pm_opp *new_opp; + unsigned long new_freq, cur_freq; + int 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; + } + new_freq = dev_pm_opp_get_freq(new_opp); + dev_pm_opp_put(new_opp); + cur_freq = clk_get_rate(priv->dram_core); + + if (new_freq == cur_freq) + return 0; + + freq_info = imx_ddrc_find_freq(priv, new_freq); + if (!freq_info) + return -EINVAL; + ret = imx_ddrc_set_freq(dev, freq_info); + if (ret) + dev_err(dev, "ddrc failed to change freq %lu to %lu\n", + cur_freq, new_freq); + else + dev_dbg(dev, "ddrc changed freq %lu to %lu\n", + cur_freq, new_freq); + + return ret; +} + +static int imx_ddrc_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct imx_ddrc *priv = dev_get_drvdata(dev); + + *freq = clk_get_rate(priv->dram_core); + + return 0; +} + +static int imx_ddrc_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct imx_ddrc *priv = dev_get_drvdata(dev); + + stat->busy_time = 0; + stat->total_time = 0; + stat->current_frequency = clk_get_rate(priv->dram_core); + + return 0; +} + +static int imx_ddrc_init_freq_info(struct device *dev) +{ + struct imx_ddrc *priv = dev_get_drvdata(dev); + struct arm_smccc_res res; + int index; + + /* + * 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 > IMX_DDRC_MAX_FREQ_COUNT) + return -ENODEV; + + for (index = 0; index < priv->freq_count; ++index) { + struct imx_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; + + freq->rate = res.a0 * 250000; + freq->smcarg = index; + freq->dram_core_parent_index = res.a1; + freq->dram_alt_parent_index = res.a2; + freq->dram_apb_parent_index = res.a3; + + /* dram_core has 2 options: dram_pll or dram_alt_root */ + if (freq->dram_core_parent_index != 1 && + freq->dram_core_parent_index != 2) + return -ENODEV; + /* dram_apb and dram_alt have exactly 8 possible parents */ + if (freq->dram_alt_parent_index > 8 || + freq->dram_apb_parent_index > 8) + return -ENODEV; + /* dram_core from alt requires explicit dram_alt parent */ + if (freq->dram_core_parent_index == 2 && + freq->dram_alt_parent_index == 0) + return -ENODEV; + } + + return 0; +} + +static void imx_ddrc_exit(struct device *dev) +{ + dev_pm_opp_of_remove_table(dev); +} + +static int imx_ddrc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_ddrc *priv; + struct device_node *events_node; + const char *gov = DEVFREQ_GOV_USERSPACE; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + ret = imx_ddrc_init_freq_info(dev); + if (ret) { + dev_err(dev, "failed to init firmware freq info: %d\n", ret); + return ret; + } + + priv->dram_core = devm_clk_get(dev, "dram_core"); + priv->dram_pll = devm_clk_get(dev, "dram_pll"); + priv->dram_alt = devm_clk_get(dev, "dram_alt"); + priv->dram_apb = devm_clk_get(dev, "dram_apb"); + if (IS_ERR(priv->dram_core) || + IS_ERR(priv->dram_pll) || + IS_ERR(priv->dram_alt) || + IS_ERR(priv->dram_apb)) { + ret = PTR_ERR(priv->devfreq); + dev_err(dev, "failed to fetch clocks: %d\n", ret); + return ret; + } + + ret = dev_pm_opp_of_add_table(dev); + if (ret < 0) { + dev_err(dev, "failed to get OPP table\n"); + return ret; + } + + priv->profile.polling_ms = 1000; + priv->profile.target = imx_ddrc_target; + priv->profile.get_dev_status = imx_ddrc_get_dev_status; + priv->profile.exit = imx_ddrc_exit; + priv->profile.get_cur_freq = imx_ddrc_get_cur_freq; + priv->profile.initial_freq = clk_get_rate(priv->dram_core); + + priv->devfreq = devm_devfreq_add_device(dev, &priv->profile, + gov, NULL); + if (IS_ERR(priv->devfreq)) { + ret = PTR_ERR(priv->devfreq); + dev_err(dev, "failed to add devfreq device: %d\n", ret); + goto err; + } + + return 0; + +err: + dev_pm_opp_of_remove_table(dev); + return ret; +} + +static const struct of_device_id imx_ddrc_of_match[] = { + { .compatible = "fsl,imx8m-ddrc", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx_ddrc_of_match); + +static struct platform_driver imx_ddrc_platdrv = { + .probe = imx_ddrc_probe, + .driver = { + .name = "imx-ddrc-devfreq", + .of_match_table = of_match_ptr(imx_ddrc_of_match), + }, +}; +module_platform_driver(imx_ddrc_platdrv); + +MODULE_DESCRIPTION("i.MX DDR controller frequency driver"); +MODULE_AUTHOR("Leonard Crestez "); +MODULE_LICENSE("GPL v2");