summaryrefslogtreecommitdiff
path: root/drivers/cpuidle/lpm-workarounds.c
blob: 104def7e71e48f0ec1922e81ba63b2b54fc15652 (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
/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
 *
 * 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/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/rpm-smd-regulator.h>

static struct regulator *lpm_cx_reg;
static struct work_struct dummy_vote_work;
static struct workqueue_struct *lpm_wa_wq;
static bool lpm_wa_cx_turbo_unvote;

/* While exiting from RPM assisted power collapse on some targets like MSM8939
 * the CX is bumped to turbo mode by RPM. To reduce the power impact, APSS
 * low power driver need to remove the CX turbo vote.
 */
static void send_dummy_cx_vote(struct work_struct *w)
{
	if (lpm_cx_reg) {
		regulator_set_voltage(lpm_cx_reg,
			RPM_REGULATOR_CORNER_SUPER_TURBO,
			RPM_REGULATOR_CORNER_SUPER_TURBO);

		regulator_set_voltage(lpm_cx_reg,
			RPM_REGULATOR_CORNER_NONE,
			RPM_REGULATOR_CORNER_SUPER_TURBO);
	}
}

/*
 * lpm_wa_cx_unvote_send(): Unvote for CX turbo mode
 */
void lpm_wa_cx_unvote_send(void)
{
	if (lpm_wa_cx_turbo_unvote)
		queue_work(lpm_wa_wq, &dummy_vote_work);
}
EXPORT_SYMBOL(lpm_wa_cx_unvote_send);

static int lpm_wa_cx_unvote_init(struct platform_device *pdev)
{
	int ret = 0;

	lpm_cx_reg = devm_regulator_get(&pdev->dev, "lpm-cx");
	if (IS_ERR(lpm_cx_reg)) {
		ret = PTR_ERR(lpm_cx_reg);
		if (ret != -EPROBE_DEFER)
			pr_err("Unable to get the CX regulator\n");
		return ret;
	}

	INIT_WORK(&dummy_vote_work, send_dummy_cx_vote);

	lpm_wa_wq = alloc_workqueue("lpm-wa",
				WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 1);

	return ret;
}

static int lpm_wa_cx_unvote_exit(void)
{
	if (lpm_wa_wq)
		destroy_workqueue(lpm_wa_wq);

	return 0;
}

static int lpm_wa_probe(struct platform_device *pdev)
{
	int ret = 0;

	lpm_wa_cx_turbo_unvote = of_property_read_bool(pdev->dev.of_node,
					"qcom,lpm-wa-cx-turbo-unvote");
	if (lpm_wa_cx_turbo_unvote) {
		ret = lpm_wa_cx_unvote_init(pdev);
		if (ret) {
			pr_err("%s: Failed to initialize lpm_wa_cx_unvote (%d)\n",
				__func__, ret);
			return ret;
		}
	}

	return ret;
}

static int lpm_wa_remove(struct platform_device *pdev)
{
	int ret = 0;
	if (lpm_wa_cx_turbo_unvote)
		ret = lpm_wa_cx_unvote_exit();

	return ret;
}

static struct of_device_id lpm_wa_mtch_tbl[] = {
	{.compatible = "qcom,lpm-workarounds"},
	{},
};

static struct platform_driver lpm_wa_driver = {
	.probe = lpm_wa_probe,
	.remove = lpm_wa_remove,
	.driver = {
		.name = "lpm-workarounds",
		.owner = THIS_MODULE,
		.of_match_table = lpm_wa_mtch_tbl,
	},
};

static int __init lpm_wa_module_init(void)
{
	int ret;
	ret = platform_driver_register(&lpm_wa_driver);
	if (ret)
		pr_info("Error registering %s\n", lpm_wa_driver.driver.name);

	return ret;
}
late_initcall(lpm_wa_module_init);