119969707SMartin Blumenstingl // SPDX-License-Identifier: GPL-2.0
219969707SMartin Blumenstingl /*
319969707SMartin Blumenstingl * Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
419969707SMartin Blumenstingl *
519969707SMartin Blumenstingl * Based on panfrost_devfreq.c:
619969707SMartin Blumenstingl * Copyright 2019 Collabora ltd.
719969707SMartin Blumenstingl */
819969707SMartin Blumenstingl #include <linux/clk.h>
919969707SMartin Blumenstingl #include <linux/devfreq.h>
1019969707SMartin Blumenstingl #include <linux/devfreq_cooling.h>
1119969707SMartin Blumenstingl #include <linux/device.h>
1219969707SMartin Blumenstingl #include <linux/platform_device.h>
1319969707SMartin Blumenstingl #include <linux/pm_opp.h>
1419969707SMartin Blumenstingl #include <linux/property.h>
1519969707SMartin Blumenstingl
1619969707SMartin Blumenstingl #include "lima_device.h"
1719969707SMartin Blumenstingl #include "lima_devfreq.h"
1819969707SMartin Blumenstingl
lima_devfreq_update_utilization(struct lima_devfreq * devfreq)1919969707SMartin Blumenstingl static void lima_devfreq_update_utilization(struct lima_devfreq *devfreq)
2019969707SMartin Blumenstingl {
2119969707SMartin Blumenstingl ktime_t now, last;
2219969707SMartin Blumenstingl
2319969707SMartin Blumenstingl now = ktime_get();
2419969707SMartin Blumenstingl last = devfreq->time_last_update;
2519969707SMartin Blumenstingl
2619969707SMartin Blumenstingl if (devfreq->busy_count > 0)
2719969707SMartin Blumenstingl devfreq->busy_time += ktime_sub(now, last);
2819969707SMartin Blumenstingl else
2919969707SMartin Blumenstingl devfreq->idle_time += ktime_sub(now, last);
3019969707SMartin Blumenstingl
3119969707SMartin Blumenstingl devfreq->time_last_update = now;
3219969707SMartin Blumenstingl }
3319969707SMartin Blumenstingl
lima_devfreq_target(struct device * dev,unsigned long * freq,u32 flags)3419969707SMartin Blumenstingl static int lima_devfreq_target(struct device *dev, unsigned long *freq,
3519969707SMartin Blumenstingl u32 flags)
3619969707SMartin Blumenstingl {
3719969707SMartin Blumenstingl struct dev_pm_opp *opp;
3819969707SMartin Blumenstingl
3919969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, freq, flags);
4019969707SMartin Blumenstingl if (IS_ERR(opp))
4119969707SMartin Blumenstingl return PTR_ERR(opp);
4219969707SMartin Blumenstingl dev_pm_opp_put(opp);
4319969707SMartin Blumenstingl
448f9d7ef3SLiu Shixin return dev_pm_opp_set_rate(dev, *freq);
4519969707SMartin Blumenstingl }
4619969707SMartin Blumenstingl
lima_devfreq_reset(struct lima_devfreq * devfreq)4719969707SMartin Blumenstingl static void lima_devfreq_reset(struct lima_devfreq *devfreq)
4819969707SMartin Blumenstingl {
4919969707SMartin Blumenstingl devfreq->busy_time = 0;
5019969707SMartin Blumenstingl devfreq->idle_time = 0;
5119969707SMartin Blumenstingl devfreq->time_last_update = ktime_get();
5219969707SMartin Blumenstingl }
5319969707SMartin Blumenstingl
lima_devfreq_get_dev_status(struct device * dev,struct devfreq_dev_status * status)5419969707SMartin Blumenstingl static int lima_devfreq_get_dev_status(struct device *dev,
5519969707SMartin Blumenstingl struct devfreq_dev_status *status)
5619969707SMartin Blumenstingl {
5719969707SMartin Blumenstingl struct lima_device *ldev = dev_get_drvdata(dev);
5819969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq;
5919969707SMartin Blumenstingl unsigned long irqflags;
6019969707SMartin Blumenstingl
6119969707SMartin Blumenstingl status->current_frequency = clk_get_rate(ldev->clk_gpu);
6219969707SMartin Blumenstingl
6319969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags);
6419969707SMartin Blumenstingl
6519969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq);
6619969707SMartin Blumenstingl
6719969707SMartin Blumenstingl status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time,
6819969707SMartin Blumenstingl devfreq->idle_time));
6919969707SMartin Blumenstingl status->busy_time = ktime_to_ns(devfreq->busy_time);
7019969707SMartin Blumenstingl
7119969707SMartin Blumenstingl lima_devfreq_reset(devfreq);
7219969707SMartin Blumenstingl
7319969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags);
7419969707SMartin Blumenstingl
7519969707SMartin Blumenstingl dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n",
7619969707SMartin Blumenstingl status->busy_time, status->total_time,
7719969707SMartin Blumenstingl status->busy_time / (status->total_time / 100),
7819969707SMartin Blumenstingl status->current_frequency / 1000 / 1000);
7919969707SMartin Blumenstingl
8019969707SMartin Blumenstingl return 0;
8119969707SMartin Blumenstingl }
8219969707SMartin Blumenstingl
8319969707SMartin Blumenstingl static struct devfreq_dev_profile lima_devfreq_profile = {
84904beebbSLukasz Luba .timer = DEVFREQ_TIMER_DELAYED,
8519969707SMartin Blumenstingl .polling_ms = 50, /* ~3 frames */
8619969707SMartin Blumenstingl .target = lima_devfreq_target,
8719969707SMartin Blumenstingl .get_dev_status = lima_devfreq_get_dev_status,
8819969707SMartin Blumenstingl };
8919969707SMartin Blumenstingl
lima_devfreq_fini(struct lima_device * ldev)9019969707SMartin Blumenstingl void lima_devfreq_fini(struct lima_device *ldev)
9119969707SMartin Blumenstingl {
9219969707SMartin Blumenstingl struct lima_devfreq *devfreq = &ldev->devfreq;
9319969707SMartin Blumenstingl
9419969707SMartin Blumenstingl if (devfreq->cooling) {
9519969707SMartin Blumenstingl devfreq_cooling_unregister(devfreq->cooling);
9619969707SMartin Blumenstingl devfreq->cooling = NULL;
9719969707SMartin Blumenstingl }
9819969707SMartin Blumenstingl
9919969707SMartin Blumenstingl if (devfreq->devfreq) {
1002ce216edSRobin Murphy devm_devfreq_remove_device(ldev->dev, devfreq->devfreq);
10119969707SMartin Blumenstingl devfreq->devfreq = NULL;
10219969707SMartin Blumenstingl }
10319969707SMartin Blumenstingl }
10419969707SMartin Blumenstingl
lima_devfreq_init(struct lima_device * ldev)10519969707SMartin Blumenstingl int lima_devfreq_init(struct lima_device *ldev)
10619969707SMartin Blumenstingl {
10719969707SMartin Blumenstingl struct thermal_cooling_device *cooling;
1082ce216edSRobin Murphy struct device *dev = ldev->dev;
10919969707SMartin Blumenstingl struct devfreq *devfreq;
11019969707SMartin Blumenstingl struct lima_devfreq *ldevfreq = &ldev->devfreq;
11119969707SMartin Blumenstingl struct dev_pm_opp *opp;
11219969707SMartin Blumenstingl unsigned long cur_freq;
11319969707SMartin Blumenstingl int ret;
11487686cc8SViresh Kumar const char *regulator_names[] = { "mali", NULL };
11519969707SMartin Blumenstingl
11619969707SMartin Blumenstingl if (!device_property_present(dev, "operating-points-v2"))
11719969707SMartin Blumenstingl /* Optional, continue without devfreq */
11819969707SMartin Blumenstingl return 0;
11919969707SMartin Blumenstingl
12019969707SMartin Blumenstingl spin_lock_init(&ldevfreq->lock);
12119969707SMartin Blumenstingl
122*e17a025aSErico Nunes /*
123*e17a025aSErico Nunes * clkname is set separately so it is not affected by the optional
124*e17a025aSErico Nunes * regulator setting which may return error.
125*e17a025aSErico Nunes */
126*e17a025aSErico Nunes ret = devm_pm_opp_set_clkname(dev, "core");
127*e17a025aSErico Nunes if (ret)
128*e17a025aSErico Nunes return ret;
129*e17a025aSErico Nunes
130*e17a025aSErico Nunes ret = devm_pm_opp_set_regulators(dev, regulator_names);
131864a2701SYangtao Li if (ret) {
13219969707SMartin Blumenstingl /* Continue if the optional regulator is missing */
13319969707SMartin Blumenstingl if (ret != -ENODEV)
134864a2701SYangtao Li return ret;
13519969707SMartin Blumenstingl }
13619969707SMartin Blumenstingl
137864a2701SYangtao Li ret = devm_pm_opp_of_add_table(dev);
13819969707SMartin Blumenstingl if (ret)
139864a2701SYangtao Li return ret;
14019969707SMartin Blumenstingl
14119969707SMartin Blumenstingl lima_devfreq_reset(ldevfreq);
14219969707SMartin Blumenstingl
14319969707SMartin Blumenstingl cur_freq = clk_get_rate(ldev->clk_gpu);
14419969707SMartin Blumenstingl
14519969707SMartin Blumenstingl opp = devfreq_recommended_opp(dev, &cur_freq, 0);
146864a2701SYangtao Li if (IS_ERR(opp))
147864a2701SYangtao Li return PTR_ERR(opp);
14819969707SMartin Blumenstingl
14919969707SMartin Blumenstingl lima_devfreq_profile.initial_freq = cur_freq;
15019969707SMartin Blumenstingl dev_pm_opp_put(opp);
15119969707SMartin Blumenstingl
1521d048afeSChristian Hewitt /*
1531d048afeSChristian Hewitt * Setup default thresholds for the simple_ondemand governor.
1541d048afeSChristian Hewitt * The values are chosen based on experiments.
1551d048afeSChristian Hewitt */
1561d048afeSChristian Hewitt ldevfreq->gov_data.upthreshold = 30;
1571d048afeSChristian Hewitt ldevfreq->gov_data.downdifferential = 5;
1581d048afeSChristian Hewitt
15919969707SMartin Blumenstingl devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile,
1601d048afeSChristian Hewitt DEVFREQ_GOV_SIMPLE_ONDEMAND,
1611d048afeSChristian Hewitt &ldevfreq->gov_data);
16219969707SMartin Blumenstingl if (IS_ERR(devfreq)) {
16319969707SMartin Blumenstingl dev_err(dev, "Couldn't initialize GPU devfreq\n");
164864a2701SYangtao Li return PTR_ERR(devfreq);
16519969707SMartin Blumenstingl }
16619969707SMartin Blumenstingl
16719969707SMartin Blumenstingl ldevfreq->devfreq = devfreq;
16819969707SMartin Blumenstingl
16919969707SMartin Blumenstingl cooling = of_devfreq_cooling_register(dev->of_node, devfreq);
17019969707SMartin Blumenstingl if (IS_ERR(cooling))
17119969707SMartin Blumenstingl dev_info(dev, "Failed to register cooling device\n");
17219969707SMartin Blumenstingl else
17319969707SMartin Blumenstingl ldevfreq->cooling = cooling;
17419969707SMartin Blumenstingl
17519969707SMartin Blumenstingl return 0;
17619969707SMartin Blumenstingl }
17719969707SMartin Blumenstingl
lima_devfreq_record_busy(struct lima_devfreq * devfreq)17819969707SMartin Blumenstingl void lima_devfreq_record_busy(struct lima_devfreq *devfreq)
17919969707SMartin Blumenstingl {
18019969707SMartin Blumenstingl unsigned long irqflags;
18119969707SMartin Blumenstingl
18219969707SMartin Blumenstingl if (!devfreq->devfreq)
18319969707SMartin Blumenstingl return;
18419969707SMartin Blumenstingl
18519969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags);
18619969707SMartin Blumenstingl
18719969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq);
18819969707SMartin Blumenstingl
18919969707SMartin Blumenstingl devfreq->busy_count++;
19019969707SMartin Blumenstingl
19119969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags);
19219969707SMartin Blumenstingl }
19319969707SMartin Blumenstingl
lima_devfreq_record_idle(struct lima_devfreq * devfreq)19419969707SMartin Blumenstingl void lima_devfreq_record_idle(struct lima_devfreq *devfreq)
19519969707SMartin Blumenstingl {
19619969707SMartin Blumenstingl unsigned long irqflags;
19719969707SMartin Blumenstingl
19819969707SMartin Blumenstingl if (!devfreq->devfreq)
19919969707SMartin Blumenstingl return;
20019969707SMartin Blumenstingl
20119969707SMartin Blumenstingl spin_lock_irqsave(&devfreq->lock, irqflags);
20219969707SMartin Blumenstingl
20319969707SMartin Blumenstingl lima_devfreq_update_utilization(devfreq);
20419969707SMartin Blumenstingl
20519969707SMartin Blumenstingl WARN_ON(--devfreq->busy_count < 0);
20619969707SMartin Blumenstingl
20719969707SMartin Blumenstingl spin_unlock_irqrestore(&devfreq->lock, irqflags);
20819969707SMartin Blumenstingl }
2094836cf04SQiang Yu
lima_devfreq_resume(struct lima_devfreq * devfreq)2104836cf04SQiang Yu int lima_devfreq_resume(struct lima_devfreq *devfreq)
2114836cf04SQiang Yu {
2124836cf04SQiang Yu unsigned long irqflags;
2134836cf04SQiang Yu
2144836cf04SQiang Yu if (!devfreq->devfreq)
2154836cf04SQiang Yu return 0;
2164836cf04SQiang Yu
2174836cf04SQiang Yu spin_lock_irqsave(&devfreq->lock, irqflags);
2184836cf04SQiang Yu
2194836cf04SQiang Yu lima_devfreq_reset(devfreq);
2204836cf04SQiang Yu
2214836cf04SQiang Yu spin_unlock_irqrestore(&devfreq->lock, irqflags);
2224836cf04SQiang Yu
2234836cf04SQiang Yu return devfreq_resume_device(devfreq->devfreq);
2244836cf04SQiang Yu }
2254836cf04SQiang Yu
lima_devfreq_suspend(struct lima_devfreq * devfreq)2264836cf04SQiang Yu int lima_devfreq_suspend(struct lima_devfreq *devfreq)
2274836cf04SQiang Yu {
2284836cf04SQiang Yu if (!devfreq->devfreq)
2294836cf04SQiang Yu return 0;
2304836cf04SQiang Yu
2314836cf04SQiang Yu return devfreq_suspend_device(devfreq->devfreq);
2324836cf04SQiang Yu }
233