diff --git a/drivers/gpu/drm/tegra/nvjpg.c b/drivers/gpu/drm/tegra/nvjpg.c index f6942ac9..7950a7f6 100644 --- a/drivers/gpu/drm/tegra/nvjpg.c +++ b/drivers/gpu/drm/tegra/nvjpg.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include @@ -39,6 +41,8 @@ struct nvjpg { struct host1x_channel *channel; struct device *dev; struct clk *clk; + struct devfreq *devfreq; + struct devfreq_dev_profile *devfreq_profile; /* Platform configuration */ const struct nvjpg_config *config; @@ -95,6 +99,91 @@ static int nvjpg_boot(struct nvjpg *nvjpg) return 0; } +static int nvjpg_devfreq_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct nvjpg *nvjpg = dev_get_drvdata(dev); + int err; + + err = nvjpg_set_rate(nvjpg, *freq); + if (err < 0) { + dev_err(dev, "failed to set clock rate\n"); + return err; + } + + *freq = clk_get_rate(nvjpg->clk); + + return 0; +} + +static int nvjpg_devfreq_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) +{ + struct nvjpg *nvjpg = dev_get_drvdata(dev); + struct host1x_client *client = &nvjpg->client.base; + unsigned long usage; + + /* Update load information */ + host1x_actmon_read_active_norm(client, &usage); + stat->total_time = 1; + stat->busy_time = usage; + + /* Update device frequency */ + stat->current_frequency = clk_get_rate(nvjpg->clk); + + return 0; +} + +static int nvjpg_devfreq_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct nvjpg *nvjpg = dev_get_drvdata(dev); + + *freq = clk_get_rate(nvjpg->clk); + + return 0; +} + +static struct devfreq_dev_profile nvjpg_devfreq_profile = { + .target = nvjpg_devfreq_target, + .get_dev_status = nvjpg_devfreq_get_dev_status, + .get_cur_freq = nvjpg_devfreq_get_cur_freq, + .polling_ms = 100, +}; + +static int nvjpg_devfreq_init(struct nvjpg *nvjpg) +{ + unsigned long max_rate = clk_round_rate(nvjpg->clk, ULONG_MAX); + unsigned long min_rate = clk_round_rate(nvjpg->clk, 0); + unsigned long margin = clk_round_rate(nvjpg->clk, min_rate + 1) - min_rate; + unsigned long rate = min_rate; + struct devfreq *devfreq; + + while (rate <= max_rate) { + dev_pm_opp_add(nvjpg->dev, rate, 0); + rate += margin; + } + + devfreq = devm_devfreq_add_device(nvjpg->dev, + &nvjpg_devfreq_profile, + DEVFREQ_GOV_USERSPACE, + NULL); + if (IS_ERR(devfreq)) + return PTR_ERR(devfreq); + + devfreq->suspend_freq = min_rate; + devfreq->resume_freq = min_rate; + nvjpg->devfreq = devfreq; + + return 0; +} + +static void nvjpg_devfreq_deinit(struct nvjpg *nvjpg) +{ + if (!nvjpg->devfreq) + return; + + devm_devfreq_remove_device(nvjpg->dev, nvjpg->devfreq); + nvjpg->devfreq = NULL; +} + static int nvjpg_init(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); @@ -279,6 +368,8 @@ static __maybe_unused int nvjpg_runtime_resume(struct device *dev) if (err < 0) goto disable; + devfreq_resume_device(nvjpg->devfreq); + return 0; disable: @@ -290,6 +381,8 @@ static __maybe_unused int nvjpg_runtime_suspend(struct device *dev) { struct nvjpg *nvjpg = dev_get_drvdata(dev); + devfreq_suspend_device(nvjpg->devfreq); + host1x_channel_stop(nvjpg->channel); clk_disable_unprepare(nvjpg->clk); @@ -464,6 +557,12 @@ static int nvjpg_probe(struct platform_device *pdev) return err; } + err = nvjpg_devfreq_init(nvjpg); + if (err < 0) { + dev_err(&pdev->dev, "failed to init devfreq: %d\n", err); + return err; + } + pm_runtime_enable(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 500); @@ -483,6 +582,8 @@ static int nvjpg_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + nvjpg_devfreq_deinit(nvjpg); + err = host1x_actmon_unregister(&nvjpg->client.base); if (err < 0) dev_info(&pdev->dev, "failed to unregister host1x actmon: %d\n",