aboutsummaryrefslogtreecommitdiff
path: root/drivers/cpufreq/cpu_wake_boost.c
blob: e72322cc409139c8a77ed4d76bd61fb40065d9a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * Copyright (C) 2018, Sultan Alsawaf <sultanxda@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/fb.h>
#include <linux/slab.h>

enum boost_state {
	NO_BOOST,
	UNBOOST,
	BOOST
};

/* The duration in milliseconds for the wake boost */
#define FB_BOOST_MS (3000)

struct wake_boost_info {
	struct workqueue_struct *wq;
	struct work_struct boost_work;
	struct delayed_work unboost_work;
	struct notifier_block cpu_notif;
	struct notifier_block fb_notif;
	enum boost_state state;
};

static void update_online_cpu_policy(void)
{
	int cpu;

	/* Trigger cpufreq notifier for online CPUs */
	get_online_cpus();
	for_each_online_cpu(cpu)
		cpufreq_update_policy(cpu);
	put_online_cpus();
}

static void wake_boost(struct work_struct *work)
{
	struct wake_boost_info *w = container_of(work, typeof(*w), boost_work);

	w->state = BOOST;
	update_online_cpu_policy();

	queue_delayed_work(w->wq, &w->unboost_work,
				msecs_to_jiffies(FB_BOOST_MS));
}

static void wake_unboost(struct work_struct *work)
{
	struct wake_boost_info *w =
		container_of(work, typeof(*w), unboost_work.work);

	w->state = UNBOOST;
	update_online_cpu_policy();
}

static int do_cpu_boost(struct notifier_block *nb,
		unsigned long action, void *data)
{
	struct wake_boost_info *w = container_of(nb, typeof(*w), cpu_notif);
	struct cpufreq_policy *policy = data;

	if (action != CPUFREQ_ADJUST)
		return NOTIFY_OK;

	switch (w->state) {
	case UNBOOST:
		policy->min = policy->cpuinfo.min_freq;
		w->state = NO_BOOST;
		break;
	case BOOST:
		policy->min = policy->max;
		break;
	default:
		break;
	}

	return NOTIFY_OK;
}

static int fb_notifier_callback(struct notifier_block *nb,
		unsigned long action, void *data)
{
	struct wake_boost_info *w = container_of(nb, typeof(*w), fb_notif);
	struct fb_event *evdata = data;
	int *blank = evdata->data;

	/* Parse framebuffer events as soon as they occur */
	if (action != FB_EARLY_EVENT_BLANK)
		return NOTIFY_OK;

	if (*blank == FB_BLANK_UNBLANK) {
		queue_work(w->wq, &w->boost_work);
	} else {
		if (cancel_delayed_work_sync(&w->unboost_work))
			queue_delayed_work(w->wq, &w->unboost_work, 0);
	}

	return NOTIFY_OK;
}

static int __init cpu_wake_boost_init(void)
{
	struct wake_boost_info *w;

	w = kzalloc(sizeof(*w), GFP_KERNEL);
	if (!w)
		return -ENOMEM;

	w->wq = alloc_workqueue("wake_boost_wq", WQ_HIGHPRI, 0);
	if (!w->wq) {
		kfree(w);
		return -ENOMEM;
	}

	INIT_WORK(&w->boost_work, wake_boost);
	INIT_DELAYED_WORK(&w->unboost_work, wake_unboost);

	w->cpu_notif.notifier_call = do_cpu_boost;
	cpufreq_register_notifier(&w->cpu_notif, CPUFREQ_POLICY_NOTIFIER);

	w->fb_notif.notifier_call = fb_notifier_callback;
	w->fb_notif.priority = INT_MAX;
	fb_register_client(&w->fb_notif);

	return 0;
}
late_initcall(cpu_wake_boost_init);