diff mbox series

[v2,34/48] gpu: host1x: Support power management

Message ID 20201217180638.22748-35-digetx@gmail.com
State New
Headers show
Series [v2,01/48] dt-bindings: memory: tegra20: emc: Replace core regulator with power domain | expand

Commit Message

Dmitry Osipenko Dec. 17, 2020, 6:06 p.m. UTC
Add suspend/resume and generic power domain support to the Host1x driver.
This is required for enabling system-wide DVFS and supporting dynamic
power management using a generic power domain.

Tested-by: Peter Geis <pgwipeout@gmail.com>
Tested-by: Nicolas Chauvet <kwizart@gmail.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
 drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 91 insertions(+), 11 deletions(-)

Comments

Mikko Perttunen Dec. 17, 2020, 6:21 p.m. UTC | #1
On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
> Add suspend/resume and generic power domain support to the Host1x driver.
> This is required for enabling system-wide DVFS and supporting dynamic
> power management using a generic power domain.
> 
> Tested-by: Peter Geis <pgwipeout@gmail.com>
> Tested-by: Nicolas Chauvet <kwizart@gmail.com>
> Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
> ---
>   drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
>   1 file changed, 91 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
> index d0ebb70e2fdd..c1525cffe7b1 100644
> --- a/drivers/gpu/host1x/dev.c
> +++ b/drivers/gpu/host1x/dev.c
> @@ -12,6 +12,7 @@
>   #include <linux/module.h>
>   #include <linux/of_device.h>
>   #include <linux/of.h>
> +#include <linux/pm_runtime.h>
>   #include <linux/slab.h>
>   
>   #define CREATE_TRACE_POINTS
> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
>   		return err;
>   	}
>   
> -	host->rst = devm_reset_control_get(&pdev->dev, "host1x");
> +	host->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "host1x");
>   	if (IS_ERR(host->rst)) {
>   		err = PTR_ERR(host->rst);
>   		dev_err(&pdev->dev, "failed to get reset: %d\n", err);
> @@ -437,16 +438,15 @@ 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;
> -	}
> +	pm_runtime_enable(&pdev->dev);
> +	err = pm_runtime_get_sync(&pdev->dev);
> +	if (err < 0)
> +		goto rpm_disable;
>   
>   	err = reset_control_deassert(host->rst);
>   	if (err < 0) {
>   		dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
> -		goto unprepare_disable;
> +		goto rpm_disable;
>   	}
>   
>   	err = host1x_syncpt_init(host);
> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device *pdev)
>   	host1x_syncpt_deinit(host);
>   reset_assert:
>   	reset_control_assert(host->rst);
> -unprepare_disable:
> -	clk_disable_unprepare(host->clk);
> -free_channels:
> +rpm_disable:
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
>   	host1x_channel_list_free(&host->channel_list);
>   iommu_exit:
>   	host1x_iommu_exit(host);
> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device *pdev)
>   	host1x_intr_deinit(host);
>   	host1x_syncpt_deinit(host);
>   	reset_control_assert(host->rst);
> -	clk_disable_unprepare(host->clk);
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
>   	host1x_iommu_exit(host);
>   
>   	return 0;
>   }
>   
> +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
> +{
> +	struct host1x *host = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(host->clk);
> +	reset_control_release(host->rst);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
> +{
> +	struct host1x *host = dev_get_drvdata(dev);
> +	int err;
> +
> +	err = reset_control_acquire(host->rst);
> +	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;
> +	}
> +
> +	return 0;
> +
> +release_reset:
> +	reset_control_release(host->rst);
> +
> +	return err;
> +}
> +
> +static __maybe_unused int host1x_suspend(struct device *dev)
> +{
> +	struct host1x *host = dev_get_drvdata(dev);
> +	int err;
> +
> +	host1x_syncpt_save(host);
> +
> +	err = pm_runtime_force_suspend(dev);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static __maybe_unused int host1x_resume(struct device *dev)
> +{
> +	struct host1x *host = dev_get_drvdata(dev);
> +	struct host1x_channel *channel;
> +	unsigned int index;
> +	int err;
> +
> +	err = pm_runtime_force_resume(dev);
> +	if (err < 0)
> +		return err;
> +
> +	host1x_syncpt_restore(host);

We also need to execute 'host1x_setup_sid_table' upon resume.

cheers,
Mikko

> +
> +	for_each_set_bit(index, host->channel_list.allocated_channels,
> +			 host->info->nb_channels) {
> +		channel = &host->channel_list.channels[index];
> +		host1x_hw_channel_init(host, channel, channel->id);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops host1x_pm = {
> +	SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
> +			   NULL)
> +	SET_SYSTEM_SLEEP_PM_OPS(host1x_suspend, host1x_resume)
> +};
> +
>   static struct platform_driver tegra_host1x_driver = {
>   	.driver = {
>   		.name = "tegra-host1x",
>   		.of_match_table = host1x_of_match,
> +		.pm = &host1x_pm,
>   	},
>   	.probe = host1x_probe,
>   	.remove = host1x_remove,
>
Dmitry Osipenko Dec. 17, 2020, 6:45 p.m. UTC | #2
17.12.2020 21:21, Mikko Perttunen пишет:
> On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
>> Add suspend/resume and generic power domain support to the Host1x driver.
>> This is required for enabling system-wide DVFS and supporting dynamic
>> power management using a generic power domain.
>>
>> Tested-by: Peter Geis <pgwipeout@gmail.com>
>> Tested-by: Nicolas Chauvet <kwizart@gmail.com>
>> Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
>> ---
>>   drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
>>   1 file changed, 91 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
>> index d0ebb70e2fdd..c1525cffe7b1 100644
>> --- a/drivers/gpu/host1x/dev.c
>> +++ b/drivers/gpu/host1x/dev.c
>> @@ -12,6 +12,7 @@
>>   #include <linux/module.h>
>>   #include <linux/of_device.h>
>>   #include <linux/of.h>
>> +#include <linux/pm_runtime.h>
>>   #include <linux/slab.h>
>>     #define CREATE_TRACE_POINTS
>> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
>>           return err;
>>       }
>>   -    host->rst = devm_reset_control_get(&pdev->dev, "host1x");
>> +    host->rst = devm_reset_control_get_exclusive_released(&pdev->dev,
>> "host1x");
>>       if (IS_ERR(host->rst)) {
>>           err = PTR_ERR(host->rst);
>>           dev_err(&pdev->dev, "failed to get reset: %d\n", err);
>> @@ -437,16 +438,15 @@ 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;
>> -    }
>> +    pm_runtime_enable(&pdev->dev);
>> +    err = pm_runtime_get_sync(&pdev->dev);
>> +    if (err < 0)
>> +        goto rpm_disable;
>>         err = reset_control_deassert(host->rst);
>>       if (err < 0) {
>>           dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
>> -        goto unprepare_disable;
>> +        goto rpm_disable;
>>       }
>>         err = host1x_syncpt_init(host);
>> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device
>> *pdev)
>>       host1x_syncpt_deinit(host);
>>   reset_assert:
>>       reset_control_assert(host->rst);
>> -unprepare_disable:
>> -    clk_disable_unprepare(host->clk);
>> -free_channels:
>> +rpm_disable:
>> +    pm_runtime_put(&pdev->dev);
>> +    pm_runtime_disable(&pdev->dev);
>> +
>>       host1x_channel_list_free(&host->channel_list);
>>   iommu_exit:
>>       host1x_iommu_exit(host);
>> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device
>> *pdev)
>>       host1x_intr_deinit(host);
>>       host1x_syncpt_deinit(host);
>>       reset_control_assert(host->rst);
>> -    clk_disable_unprepare(host->clk);
>> +    pm_runtime_put(&pdev->dev);
>> +    pm_runtime_disable(&pdev->dev);
>>       host1x_iommu_exit(host);
>>         return 0;
>>   }
>>   +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +
>> +    clk_disable_unprepare(host->clk);
>> +    reset_control_release(host->rst);
>> +
>> +    return 0;
>> +}
>> +
>> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    int err;
>> +
>> +    err = reset_control_acquire(host->rst);
>> +    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;
>> +    }
>> +
>> +    return 0;
>> +
>> +release_reset:
>> +    reset_control_release(host->rst);
>> +
>> +    return err;
>> +}
>> +
>> +static __maybe_unused int host1x_suspend(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    int err;
>> +
>> +    host1x_syncpt_save(host);
>> +
>> +    err = pm_runtime_force_suspend(dev);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    return 0;
>> +}
>> +
>> +static __maybe_unused int host1x_resume(struct device *dev)
>> +{
>> +    struct host1x *host = dev_get_drvdata(dev);
>> +    struct host1x_channel *channel;
>> +    unsigned int index;
>> +    int err;
>> +
>> +    err = pm_runtime_force_resume(dev);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    host1x_syncpt_restore(host);
> 
> We also need to execute 'host1x_setup_sid_table' upon resume.

Indeed, thanks. I'll correct it in the next revision.

Perhaps the actual save/restore needs to be moved to the runtime
callbacks. At least I can't remember right now why this wasn't done in
the first place.
Dmitry Osipenko Dec. 17, 2020, 8:58 p.m. UTC | #3
17.12.2020 21:45, Dmitry Osipenko пишет:
> 17.12.2020 21:21, Mikko Perttunen пишет:
>> On 12/17/20 8:06 PM, Dmitry Osipenko wrote:
>>> Add suspend/resume and generic power domain support to the Host1x driver.
>>> This is required for enabling system-wide DVFS and supporting dynamic
>>> power management using a generic power domain.
>>>
>>> Tested-by: Peter Geis <pgwipeout@gmail.com>
>>> Tested-by: Nicolas Chauvet <kwizart@gmail.com>
>>> Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
>>> ---
>>>   drivers/gpu/host1x/dev.c | 102 ++++++++++++++++++++++++++++++++++-----
>>>   1 file changed, 91 insertions(+), 11 deletions(-)
>>>
>>> diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
>>> index d0ebb70e2fdd..c1525cffe7b1 100644
>>> --- a/drivers/gpu/host1x/dev.c
>>> +++ b/drivers/gpu/host1x/dev.c
>>> @@ -12,6 +12,7 @@
>>>   #include <linux/module.h>
>>>   #include <linux/of_device.h>
>>>   #include <linux/of.h>
>>> +#include <linux/pm_runtime.h>
>>>   #include <linux/slab.h>
>>>     #define CREATE_TRACE_POINTS
>>> @@ -417,7 +418,7 @@ static int host1x_probe(struct platform_device *pdev)
>>>           return err;
>>>       }
>>>   -    host->rst = devm_reset_control_get(&pdev->dev, "host1x");
>>> +    host->rst = devm_reset_control_get_exclusive_released(&pdev->dev,
>>> "host1x");
>>>       if (IS_ERR(host->rst)) {
>>>           err = PTR_ERR(host->rst);
>>>           dev_err(&pdev->dev, "failed to get reset: %d\n", err);
>>> @@ -437,16 +438,15 @@ 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;
>>> -    }
>>> +    pm_runtime_enable(&pdev->dev);
>>> +    err = pm_runtime_get_sync(&pdev->dev);
>>> +    if (err < 0)
>>> +        goto rpm_disable;
>>>         err = reset_control_deassert(host->rst);
>>>       if (err < 0) {
>>>           dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
>>> -        goto unprepare_disable;
>>> +        goto rpm_disable;
>>>       }
>>>         err = host1x_syncpt_init(host);
>>> @@ -485,9 +485,10 @@ static int host1x_probe(struct platform_device
>>> *pdev)
>>>       host1x_syncpt_deinit(host);
>>>   reset_assert:
>>>       reset_control_assert(host->rst);
>>> -unprepare_disable:
>>> -    clk_disable_unprepare(host->clk);
>>> -free_channels:
>>> +rpm_disable:
>>> +    pm_runtime_put(&pdev->dev);
>>> +    pm_runtime_disable(&pdev->dev);
>>> +
>>>       host1x_channel_list_free(&host->channel_list);
>>>   iommu_exit:
>>>       host1x_iommu_exit(host);
>>> @@ -504,16 +505,95 @@ static int host1x_remove(struct platform_device
>>> *pdev)
>>>       host1x_intr_deinit(host);
>>>       host1x_syncpt_deinit(host);
>>>       reset_control_assert(host->rst);
>>> -    clk_disable_unprepare(host->clk);
>>> +    pm_runtime_put(&pdev->dev);
>>> +    pm_runtime_disable(&pdev->dev);
>>>       host1x_iommu_exit(host);
>>>         return 0;
>>>   }
>>>   +static int __maybe_unused host1x_runtime_suspend(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +
>>> +    clk_disable_unprepare(host->clk);
>>> +    reset_control_release(host->rst);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int __maybe_unused host1x_runtime_resume(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    int err;
>>> +
>>> +    err = reset_control_acquire(host->rst);
>>> +    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;
>>> +    }
>>> +
>>> +    return 0;
>>> +
>>> +release_reset:
>>> +    reset_control_release(host->rst);
>>> +
>>> +    return err;
>>> +}
>>> +
>>> +static __maybe_unused int host1x_suspend(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    int err;
>>> +
>>> +    host1x_syncpt_save(host);
>>> +
>>> +    err = pm_runtime_force_suspend(dev);
>>> +    if (err < 0)
>>> +        return err;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static __maybe_unused int host1x_resume(struct device *dev)
>>> +{
>>> +    struct host1x *host = dev_get_drvdata(dev);
>>> +    struct host1x_channel *channel;
>>> +    unsigned int index;
>>> +    int err;
>>> +
>>> +    err = pm_runtime_force_resume(dev);
>>> +    if (err < 0)
>>> +        return err;
>>> +
>>> +    host1x_syncpt_restore(host);
>>
>> We also need to execute 'host1x_setup_sid_table' upon resume.
> 
> Indeed, thanks. I'll correct it in the next revision.
> 
> Perhaps the actual save/restore needs to be moved to the runtime
> callbacks. At least I can't remember right now why this wasn't done in
> the first place.
> 

I looked at the save/restore once again and recalled why it's done so.
The reason is that the host1x touches hardware during the driver probe,
and thus, RPM needs to be resumed first. It will be a bigger change to
properly decouple the hardware initialization so that it all could be
put it into the RPM callback. I'll try to do it in v3.
diff mbox series

Patch

diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index d0ebb70e2fdd..c1525cffe7b1 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -12,6 +12,7 @@ 
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/of.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
 #define CREATE_TRACE_POINTS
@@ -417,7 +418,7 @@  static int host1x_probe(struct platform_device *pdev)
 		return err;
 	}
 
-	host->rst = devm_reset_control_get(&pdev->dev, "host1x");
+	host->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "host1x");
 	if (IS_ERR(host->rst)) {
 		err = PTR_ERR(host->rst);
 		dev_err(&pdev->dev, "failed to get reset: %d\n", err);
@@ -437,16 +438,15 @@  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;
-	}
+	pm_runtime_enable(&pdev->dev);
+	err = pm_runtime_get_sync(&pdev->dev);
+	if (err < 0)
+		goto rpm_disable;
 
 	err = reset_control_deassert(host->rst);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
-		goto unprepare_disable;
+		goto rpm_disable;
 	}
 
 	err = host1x_syncpt_init(host);
@@ -485,9 +485,10 @@  static int host1x_probe(struct platform_device *pdev)
 	host1x_syncpt_deinit(host);
 reset_assert:
 	reset_control_assert(host->rst);
-unprepare_disable:
-	clk_disable_unprepare(host->clk);
-free_channels:
+rpm_disable:
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
 	host1x_channel_list_free(&host->channel_list);
 iommu_exit:
 	host1x_iommu_exit(host);
@@ -504,16 +505,95 @@  static int host1x_remove(struct platform_device *pdev)
 	host1x_intr_deinit(host);
 	host1x_syncpt_deinit(host);
 	reset_control_assert(host->rst);
-	clk_disable_unprepare(host->clk);
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
 	host1x_iommu_exit(host);
 
 	return 0;
 }
 
+static int __maybe_unused host1x_runtime_suspend(struct device *dev)
+{
+	struct host1x *host = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(host->clk);
+	reset_control_release(host->rst);
+
+	return 0;
+}
+
+static int __maybe_unused host1x_runtime_resume(struct device *dev)
+{
+	struct host1x *host = dev_get_drvdata(dev);
+	int err;
+
+	err = reset_control_acquire(host->rst);
+	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;
+	}
+
+	return 0;
+
+release_reset:
+	reset_control_release(host->rst);
+
+	return err;
+}
+
+static __maybe_unused int host1x_suspend(struct device *dev)
+{
+	struct host1x *host = dev_get_drvdata(dev);
+	int err;
+
+	host1x_syncpt_save(host);
+
+	err = pm_runtime_force_suspend(dev);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static __maybe_unused int host1x_resume(struct device *dev)
+{
+	struct host1x *host = dev_get_drvdata(dev);
+	struct host1x_channel *channel;
+	unsigned int index;
+	int err;
+
+	err = pm_runtime_force_resume(dev);
+	if (err < 0)
+		return err;
+
+	host1x_syncpt_restore(host);
+
+	for_each_set_bit(index, host->channel_list.allocated_channels,
+			 host->info->nb_channels) {
+		channel = &host->channel_list.channels[index];
+		host1x_hw_channel_init(host, channel, channel->id);
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops host1x_pm = {
+	SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume,
+			   NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(host1x_suspend, host1x_resume)
+};
+
 static struct platform_driver tegra_host1x_driver = {
 	.driver = {
 		.name = "tegra-host1x",
 		.of_match_table = host1x_of_match,
+		.pm = &host1x_pm,
 	},
 	.probe = host1x_probe,
 	.remove = host1x_remove,