gpu: host1x: Add initial runtime PM and OPP support
authorDmitry Osipenko <digetx@gmail.com>
Tue, 30 Nov 2021 23:23:15 +0000 (02:23 +0300)
committerThierry Reding <treding@nvidia.com>
Thu, 16 Dec 2021 13:07:07 +0000 (14:07 +0100)
Add runtime PM and OPP support to the Host1x driver. For the starter we
will keep host1x always-on because dynamic power management require a major
refactoring of the driver code since lot's of code paths are missing the
RPM handling and we're going to remove some of these paths in the future.

Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30
Tested-by: Paul Fertser <fercerpav@gmail.com> # PAZ00 T20
Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/host1x/debug.c
drivers/gpu/host1x/dev.c
drivers/gpu/host1x/dev.h
drivers/gpu/host1x/hw/channel_hw.c
drivers/gpu/host1x/intr.c
drivers/gpu/host1x/syncpt.c

index 8a14880c61bbda495743dd6002e6edf7282e3990..18d9c8d206e3fda9c46818780bbe62408adc585c 100644 (file)
@@ -7,6 +7,7 @@
  */
 
 #include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
 #include <linux/seq_file.h>
 #include <linux/uaccess.h>
 
@@ -52,6 +53,11 @@ static int show_channel(struct host1x_channel *ch, void *data, bool show_fifo)
 {
        struct host1x *m = dev_get_drvdata(ch->dev->parent);
        struct output *o = data;
+       int err;
+
+       err = pm_runtime_resume_and_get(m->dev);
+       if (err < 0)
+               return err;
 
        mutex_lock(&ch->cdma.lock);
        mutex_lock(&debug_lock);
@@ -64,6 +70,8 @@ static int show_channel(struct host1x_channel *ch, void *data, bool show_fifo)
        mutex_unlock(&debug_lock);
        mutex_unlock(&ch->cdma.lock);
 
+       pm_runtime_put(m->dev);
+
        return 0;
 }
 
@@ -71,9 +79,14 @@ static void show_syncpts(struct host1x *m, struct output *o)
 {
        struct list_head *pos;
        unsigned int i;
+       int err;
 
        host1x_debug_output(o, "---- syncpts ----\n");
 
+       err = pm_runtime_resume_and_get(m->dev);
+       if (err < 0)
+               return;
+
        for (i = 0; i < host1x_syncpt_nb_pts(m); i++) {
                u32 max = host1x_syncpt_read_max(m->syncpt + i);
                u32 min = host1x_syncpt_load(m->syncpt + i);
@@ -101,6 +114,8 @@ static void show_syncpts(struct host1x *m, struct output *o)
                                            base_val);
        }
 
+       pm_runtime_put(m->dev);
+
        host1x_debug_output(o, "\n");
 }
 
index 3d4cabdbc78db8839d936893b45864414fb66169..14c1ea7506fb2efc8bae34b4913c5e8e74a42a18 100644 (file)
@@ -6,14 +6,18 @@
  */
 
 #include <linux/clk.h>
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/of.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
+#include <soc/tegra/common.h>
+
 #define CREATE_TRACE_POINTS
 #include <trace/events/host1x.h>
 #undef CREATE_TRACE_POINTS
@@ -208,6 +212,9 @@ static void host1x_setup_sid_table(struct host1x *host)
        const struct host1x_info *info = host->info;
        unsigned int i;
 
+       if (!info->has_hypervisor)
+               return;
+
        for (i = 0; i < info->num_sid_entries; i++) {
                const struct host1x_sid_entry *entry = &info->sid_table[i];
 
@@ -365,6 +372,27 @@ static void host1x_iommu_exit(struct host1x *host)
        }
 }
 
+static int host1x_get_resets(struct host1x *host)
+{
+       int err;
+
+       host->resets[0].id = "mc";
+       host->resets[1].id = "host1x";
+       host->nresets = ARRAY_SIZE(host->resets);
+
+       err = devm_reset_control_bulk_get_optional_exclusive_released(
+                               host->dev, host->nresets, host->resets);
+       if (err) {
+               dev_err(host->dev, "failed to get reset: %d\n", err);
+               return err;
+       }
+
+       if (WARN_ON(!host->resets[1].rstc))
+               return -ENOENT;
+
+       return 0;
+}
+
 static int host1x_probe(struct platform_device *pdev)
 {
        struct host1x *host;
@@ -442,12 +470,9 @@ static int host1x_probe(struct platform_device *pdev)
                return err;
        }
 
-       host->rst = devm_reset_control_get(&pdev->dev, "host1x");
-       if (IS_ERR(host->rst)) {
-               err = PTR_ERR(host->rst);
-               dev_err(&pdev->dev, "failed to get reset: %d\n", err);
+       err = host1x_get_resets(host);
+       if (err)
                return err;
-       }
 
        err = host1x_iommu_init(host);
        if (err < 0) {
@@ -462,22 +487,10 @@ static int host1x_probe(struct platform_device *pdev)
                goto iommu_exit;
        }
 
-       err = clk_prepare_enable(host->clk);
-       if (err < 0) {
-               dev_err(&pdev->dev, "failed to enable clock\n");
-               goto free_channels;
-       }
-
-       err = reset_control_deassert(host->rst);
-       if (err < 0) {
-               dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
-               goto unprepare_disable;
-       }
-
        err = host1x_syncpt_init(host);
        if (err) {
                dev_err(&pdev->dev, "failed to initialize syncpts\n");
-               goto reset_assert;
+               goto free_channels;
        }
 
        err = host1x_intr_init(host, syncpt_irq);
@@ -486,10 +499,18 @@ static int host1x_probe(struct platform_device *pdev)
                goto deinit_syncpt;
        }
 
-       host1x_debug_init(host);
+       pm_runtime_enable(&pdev->dev);
+
+       err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+       if (err)
+               goto pm_disable;
 
-       if (host->info->has_hypervisor)
-               host1x_setup_sid_table(host);
+       /* the driver's code isn't ready yet for the dynamic RPM */
+       err = pm_runtime_resume_and_get(&pdev->dev);
+       if (err)
+               goto pm_disable;
+
+       host1x_debug_init(host);
 
        err = host1x_register(host);
        if (err < 0)
@@ -505,13 +526,14 @@ unregister:
        host1x_unregister(host);
 deinit_debugfs:
        host1x_debug_deinit(host);
+
+       pm_runtime_put_sync_suspend(&pdev->dev);
+pm_disable:
+       pm_runtime_disable(&pdev->dev);
+
        host1x_intr_deinit(host);
 deinit_syncpt:
        host1x_syncpt_deinit(host);
-reset_assert:
-       reset_control_assert(host->rst);
-unprepare_disable:
-       clk_disable_unprepare(host->clk);
 free_channels:
        host1x_channel_list_free(&host->channel_list);
 iommu_exit:
@@ -526,20 +548,94 @@ static int host1x_remove(struct platform_device *pdev)
 
        host1x_unregister(host);
        host1x_debug_deinit(host);
+
+       pm_runtime_force_suspend(&pdev->dev);
+
        host1x_intr_deinit(host);
        host1x_syncpt_deinit(host);
-       reset_control_assert(host->rst);
-       clk_disable_unprepare(host->clk);
        host1x_iommu_exit(host);
        host1x_bo_cache_destroy(&host->cache);
 
        return 0;
 }
 
+static int __maybe_unused host1x_runtime_suspend(struct device *dev)
+{
+       struct host1x *host = dev_get_drvdata(dev);
+       int err;
+
+       host1x_intr_stop(host);
+       host1x_syncpt_save(host);
+
+       err = reset_control_bulk_assert(host->nresets, host->resets);
+       if (err) {
+               dev_err(dev, "failed to assert reset: %d\n", err);
+               goto resume_host1x;
+       }
+
+       usleep_range(1000, 2000);
+
+       clk_disable_unprepare(host->clk);
+       reset_control_bulk_release(host->nresets, host->resets);
+
+       return 0;
+
+resume_host1x:
+       host1x_setup_sid_table(host);
+       host1x_syncpt_restore(host);
+       host1x_intr_start(host);
+
+       return err;
+}
+
+static int __maybe_unused host1x_runtime_resume(struct device *dev)
+{
+       struct host1x *host = dev_get_drvdata(dev);
+       int err;
+
+       err = reset_control_bulk_acquire(host->nresets, host->resets);
+       if (err) {
+               dev_err(dev, "failed to acquire reset: %d\n", err);
+               return err;
+       }
+
+       err = clk_prepare_enable(host->clk);
+       if (err) {
+               dev_err(dev, "failed to enable clock: %d\n", err);
+               goto release_reset;
+       }
+
+       err = reset_control_bulk_deassert(host->nresets, host->resets);
+       if (err < 0) {
+               dev_err(dev, "failed to deassert reset: %d\n", err);
+               goto disable_clk;
+       }
+
+       host1x_setup_sid_table(host);
+       host1x_syncpt_restore(host);
+       host1x_intr_start(host);
+
+       return 0;
+
+disable_clk:
+       clk_disable_unprepare(host->clk);
+release_reset:
+       reset_control_bulk_release(host->nresets, host->resets);
+
+       return err;
+}
+
+static const struct dev_pm_ops host1x_pm_ops = {
+       SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
+                          NULL)
+       /* TODO: add system suspend-resume once driver will be ready for that */
+};
+
 static struct platform_driver tegra_host1x_driver = {
        .driver = {
                .name = "tegra-host1x",
                .of_match_table = host1x_of_match,
+               .pm = &host1x_pm_ops,
        },
        .probe = host1x_probe,
        .remove = host1x_remove,
index 5b7fdea5d1699dc5ecb718f9f31b2d207a09896c..ca4b082f0cd494ef85eb1a2f295cb963cc24b96a 100644 (file)
@@ -118,7 +118,8 @@ struct host1x {
        struct host1x_syncpt_base *bases;
        struct device *dev;
        struct clk *clk;
-       struct reset_control *rst;
+       struct reset_control_bulk_data resets[2];
+       unsigned int nresets;
 
        struct iommu_group *group;
        struct iommu_domain *domain;
index 1999780a7203a649b20ee33e3cbc7d8cb2b78f76..6b40e9af1e8860b2b57f62347c46da258067d6d2 100644 (file)
@@ -159,6 +159,27 @@ static void host1x_channel_set_streamid(struct host1x_channel *channel)
 #endif
 }
 
+static void host1x_enable_gather_filter(struct host1x_channel *ch)
+{
+#if HOST1X_HW >= 6
+       struct host1x *host = dev_get_drvdata(ch->dev->parent);
+       u32 val;
+
+       if (!host->hv_regs)
+               return;
+
+       val = host1x_hypervisor_readl(
+               host, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
+       val |= BIT(ch->id % 32);
+       host1x_hypervisor_writel(
+               host, val, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
+#elif HOST1X_HW >= 4
+       host1x_ch_writel(ch,
+                        HOST1X_CHANNEL_CHANNELCTRL_KERNEL_FILTER_GBUFFER(1),
+                        HOST1X_CHANNEL_CHANNELCTRL);
+#endif
+}
+
 static int channel_submit(struct host1x_job *job)
 {
        struct host1x_channel *ch = job->channel;
@@ -190,6 +211,7 @@ static int channel_submit(struct host1x_job *job)
        }
 
        host1x_channel_set_streamid(ch);
+       host1x_enable_gather_filter(ch);
 
        /* begin a CDMA submit */
        err = host1x_cdma_begin(&ch->cdma, job);
@@ -249,27 +271,6 @@ error:
        return err;
 }
 
-static void enable_gather_filter(struct host1x *host,
-                                struct host1x_channel *ch)
-{
-#if HOST1X_HW >= 6
-       u32 val;
-
-       if (!host->hv_regs)
-               return;
-
-       val = host1x_hypervisor_readl(
-               host, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
-       val |= BIT(ch->id % 32);
-       host1x_hypervisor_writel(
-               host, val, HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(ch->id / 32));
-#elif HOST1X_HW >= 4
-       host1x_ch_writel(ch,
-                        HOST1X_CHANNEL_CHANNELCTRL_KERNEL_FILTER_GBUFFER(1),
-                        HOST1X_CHANNEL_CHANNELCTRL);
-#endif
-}
-
 static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
                               unsigned int index)
 {
@@ -278,7 +279,6 @@ static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
 #else
        ch->regs = dev->regs + index * 0x100;
 #endif
-       enable_gather_filter(dev, ch);
        return 0;
 }
 
index 45b6be927ec4d7ebfdcfe763af8627c42a6f2932..965ba21818b15dd885694962e54aed2f26edbcc1 100644 (file)
@@ -297,14 +297,11 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
                         "host1x_sp_%02u", id);
        }
 
-       host1x_intr_start(host);
-
        return 0;
 }
 
 void host1x_intr_deinit(struct host1x *host)
 {
-       host1x_intr_stop(host);
 }
 
 void host1x_intr_start(struct host1x *host)
index d198a10848c6b46bcc5445e67ee3d3ada1fac97e..e08e331e46aea7a5e3484cea2f8f4012d474e542 100644 (file)
@@ -143,6 +143,8 @@ void host1x_syncpt_restore(struct host1x *host)
        for (i = 0; i < host1x_syncpt_nb_bases(host); i++)
                host1x_hw_syncpt_restore_wait_base(host, sp_base + i);
 
+       host1x_hw_syncpt_enable_protection(host);
+
        wmb();
 }
 
@@ -366,9 +368,6 @@ int host1x_syncpt_init(struct host1x *host)
        host->syncpt = syncpt;
        host->bases = bases;
 
-       host1x_syncpt_restore(host);
-       host1x_hw_syncpt_enable_protection(host);
-
        /* Allocate sync point to use for clearing waits for expired fences */
        host->nop_sp = host1x_syncpt_alloc(host, 0, "reserved-nop");
        if (!host->nop_sp)