memory: tegra20-emc: Add support for memory bandwidth PM QoS
authorDmitry Osipenko <digetx@gmail.com>
Sun, 30 Sep 2018 14:46:02 +0000 (17:46 +0300)
committerDmitry Osipenko <digetx@gmail.com>
Sat, 9 Feb 2019 19:15:25 +0000 (22:15 +0300)
Power management core provides QoS API for requesting memory bandwidth.
This patch turns tegra20-emc driver into memory bandwidth provider and
thus allows to dynamically scale DRAM clock rate based on actual demands
of peripherals in the system, resulting in a good power savings during
idle.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
drivers/memory/tegra/tegra20-emc.c

index 9ee5bef..7a7bc86 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/pm_qos.h>
 #include <linux/sort.h>
 #include <linux/types.h>
 
@@ -136,6 +137,7 @@ struct emc_timing {
 struct tegra_emc {
        struct device *dev;
        struct completion clk_handshake_complete;
+       struct notifier_block qos_nb;
        struct notifier_block clk_nb;
        struct clk *backup_clk;
        struct clk *emc_mux;
@@ -275,6 +277,30 @@ static int tegra_emc_clk_change_notify(struct notifier_block *nb,
        return notifier_from_errno(err);
 }
 
+static int tegra_emc_qos_request_notify(struct notifier_block *nb,
+                                       unsigned long memory_bandwidth,
+                                       void *data)
+{
+       struct tegra_emc *emc = container_of(nb, struct tegra_emc, qos_nb);
+       unsigned long long max_rate;
+       unsigned long long rate;
+       int err;
+
+       /* get maximum possible rate */
+       max_rate = emc->timings[emc->num_timings - 1].rate;
+
+       /* DDR: 8 bytes per clock, 1 Mbit/s: 125000 B/s, EMC: 2x BUS rate */
+       rate = memory_bandwidth * 125000 / 8 * 2;
+
+       /* clamp rate to maximum possible */
+       rate = min(rate, max_rate);
+
+       /* update EMC clock rate */
+       err = clk_set_rate(emc->clk, rate);
+
+       return notifier_from_errno(err);
+}
+
 static int load_one_timing_from_dt(struct tegra_emc *emc,
                                   struct emc_timing *timing,
                                   struct device_node *node)
@@ -492,6 +518,7 @@ static int tegra_emc_probe(struct platform_device *pdev)
 
        init_completion(&emc->clk_handshake_complete);
        emc->clk_nb.notifier_call = tegra_emc_clk_change_notify;
+       emc->qos_nb.notifier_call = tegra_emc_qos_request_notify;
        emc->dev = &pdev->dev;
 
        err = tegra_emc_load_timings_from_dt(emc, np);
@@ -555,12 +582,19 @@ static int tegra_emc_probe(struct platform_device *pdev)
        if (err) {
                dev_err(&pdev->dev, "failed to initialize EMC clock rate: %d\n",
                        err);
-               goto unreg_notifier;
+               goto unreg_clk_notifier;
+       }
+
+       err = pm_qos_add_notifier(PM_QOS_MEMORY_BANDWIDTH, &emc->qos_nb);
+       if (err) {
+               dev_err(&pdev->dev, "failed to register QoS notifier: %d\n",
+                       err);
+               goto unreg_clk_notifier;
        }
 
        return 0;
 
-unreg_notifier:
+unreg_clk_notifier:
        clk_notifier_unregister(emc->clk, &emc->clk_nb);
 put_backup:
        clk_put(emc->backup_clk);