aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-ux500/pm/runtime.c
blob: 265faf3085df8c03f0d145f91355b6129b9116eb (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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
 * Copyright (C) ST-Ericsson SA 2011
 *
 * Author: Rabin Vincent <rabin.vincent@stericsson> for ST-Ericsson
 *
 * Based on:
 *  Runtime PM support code for SuperH Mobile ARM
 *  Copyright (C) 2009-2010 Magnus Damm
 *
 * License terms: GNU General Public License (GPL) version 2
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include <plat/pincfg.h>
#include <linux/regulator/dbx500-prcmu.h>

#include "../pins.h"

#ifdef CONFIG_PM_RUNTIME
#define BIT_ONCE		0
#define BIT_ACTIVE		1
#define BIT_ENABLED	2

struct pm_runtime_data {
	unsigned long flags;
	struct ux500_regulator *regulator;
	struct ux500_pins *pins;
};

static void __devres_release(struct device *dev, void *res)
{
	struct pm_runtime_data *prd = res;

	dev_dbg(dev, "__devres_release()\n");

	if (test_bit(BIT_ENABLED, &prd->flags)) {
		if (prd->pins)
			ux500_pins_disable(prd->pins);
		if (prd->regulator)
			ux500_regulator_atomic_disable(prd->regulator);
	}

	if (test_bit(BIT_ACTIVE, &prd->flags)) {
		if (prd->pins)
			ux500_pins_put(prd->pins);
		if (prd->regulator)
			ux500_regulator_put(prd->regulator);
	}
}

static struct pm_runtime_data *__to_prd(struct device *dev)
{
	return devres_find(dev, __devres_release, NULL, NULL);
}

static void platform_pm_runtime_init(struct device *dev,
				     struct pm_runtime_data *prd)
{
	prd->pins = ux500_pins_get(dev_name(dev));

	prd->regulator = ux500_regulator_get(dev);
	if (IS_ERR(prd->regulator))
		prd->regulator = NULL;

	if (prd->pins || prd->regulator) {
		dev_info(dev, "managed by runtime pm: %s%s\n",
			 prd->pins ? "pins " : "",
			 prd->regulator ? "regulator " : "");

		set_bit(BIT_ACTIVE, &prd->flags);
	}
}

static void platform_pm_runtime_bug(struct device *dev,
				    struct pm_runtime_data *prd)
{
	if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags))
		dev_err(dev, "runtime pm suspend before resume\n");
}

static void platform_pm_runtime_used(struct device *dev,
				       struct pm_runtime_data *prd)
{
	if (prd)
		set_bit(BIT_ONCE, &prd->flags);
}

static int ux500_pd_runtime_suspend(struct device *dev)
{
	struct pm_runtime_data *prd = __to_prd(dev);

	dev_vdbg(dev, "%s()\n", __func__);

	platform_pm_runtime_bug(dev, prd);

	if (prd && test_bit(BIT_ACTIVE, &prd->flags)) {
		if (prd->pins)
			ux500_pins_disable(prd->pins);

		if (prd->regulator)
			ux500_regulator_atomic_disable(prd->regulator);

		clear_bit(BIT_ENABLED, &prd->flags);
	}

	return 0;
}

static int ux500_pd_runtime_resume(struct device *dev)
{
	struct pm_runtime_data *prd = __to_prd(dev);

	dev_vdbg(dev, "%s()\n", __func__);

	platform_pm_runtime_used(dev, prd);

	if (prd && test_bit(BIT_ACTIVE, &prd->flags)) {
		if (prd->pins)
			ux500_pins_enable(prd->pins);

		if (prd->regulator)
			ux500_regulator_atomic_enable(prd->regulator);

		set_bit(BIT_ENABLED, &prd->flags);
	}

	return 0;
}

static int ux500_pd_suspend_noirq(struct device *dev)
{
	struct pm_runtime_data *prd = __to_prd(dev);
	int ret;

	dev_vdbg(dev, "%s()\n", __func__);

	/* Only handle devices that use runtime pm */
	if (!prd || !test_bit(BIT_ONCE, &prd->flags))
		return 0;

	/* Already is runtime suspended?  Nothing to do. */
	if (pm_runtime_suspended(dev))
		return 0;

	/*
	 * We get here only if the device was not runtime suspended for some
	 * reason.  We still need to do the power save stuff when going into
	 * suspend, so force it here.
	 */
	ret = pm_generic_runtime_suspend(dev);
	if (ret)
		return ret;

	return ux500_pd_runtime_suspend(dev);
}

static int ux500_pd_resume_noirq(struct device *dev)
{
	struct pm_runtime_data *prd = __to_prd(dev);

	dev_vdbg(dev, "%s()\n", __func__);

	/* Only handle devices that use runtime pm */
	if (!prd || !test_bit(BIT_ONCE, &prd->flags))
		return 0;

	/*
	 * Already was runtime suspended?  No need to resume here, runtime
	 * resume will take care of it.
	 */
	if (pm_runtime_suspended(dev))
		return 0;

	/*
	 * We get here only if the device was not runtime suspended,
	 * but we forced it down in suspend_noirq above.  Bring it
	 * up since pm-runtime thinks it is not suspended.
	 */
	ux500_pd_runtime_resume(dev);

	return pm_generic_runtime_resume(dev);
}

static int ux500_pd_bus_notify(struct notifier_block *nb,
			       unsigned long action, void *data)
{
	struct device *dev = data;
	struct pm_runtime_data *prd;

	dev_dbg(dev, "%s() %ld !\n", __func__, action);

	if (action == BUS_NOTIFY_BIND_DRIVER) {
		prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL);
		if (prd) {
			devres_add(dev, prd);
			platform_pm_runtime_init(dev, prd);
		} else
			dev_err(dev, "unable to alloc memory for runtime pm\n");
	}

	return 0;
}

#else /* CONFIG_PM_RUNTIME */

#define ux500_pd_suspend_noirq	NULL
#define ux500_pd_resume_noirq	NULL
#define ux500_pd_runtime_suspend	NULL
#define ux500_pd_runtime_resume	NULL

static int ux500_pd_bus_notify(struct notifier_block *nb,
			       unsigned long action, void *data)
{
	struct ux500_regulator *regulator = NULL;
	struct ux500_pins *pins = NULL;
	struct device *dev = data;
	const char *onoff = NULL;

	dev_dbg(dev, "%s() %ld !\n", __func__, action);

	switch (action) {
	case BUS_NOTIFY_BIND_DRIVER:
		pins = ux500_pins_get(dev_name(dev));
		if (pins) {
			ux500_pins_enable(pins);
			ux500_pins_put(pins);
		}

		regulator = ux500_regulator_get(dev);
		if (IS_ERR(regulator))
			regulator = NULL;
		else {
			ux500_regulator_atomic_enable(regulator);
			ux500_regulator_put(regulator);
		}

		onoff = "on";
		break;
	case BUS_NOTIFY_UNBOUND_DRIVER:
		pins = ux500_pins_get(dev_name(dev));
		if (pins) {
			ux500_pins_disable(pins);
			ux500_pins_put(pins);
		}

		regulator = ux500_regulator_get(dev);
		if (IS_ERR(regulator))
			regulator = NULL;
		else {
			ux500_regulator_atomic_disable(regulator);
			ux500_regulator_put(regulator);
		}

		onoff = "off";
		break;
	}

	if (pins || regulator) {
		dev_info(dev, "runtime pm disabled, forced %s: %s%s\n",
			 onoff,
			 pins ? "pins " : "",
			 regulator ? "regulator " : "");
	}

	return 0;
}

#endif /* CONFIG_PM_RUNTIME */

struct dev_power_domain ux500_dev_power_domain = {
	.ops = {
		.suspend_noirq		= ux500_pd_suspend_noirq,
		.resume_noirq		= ux500_pd_resume_noirq,
		.runtime_suspend	= ux500_pd_runtime_suspend,
		.runtime_resume		= ux500_pd_runtime_resume,
	},
};

static struct notifier_block ux500_pd_platform_notifier = {
	.notifier_call = ux500_pd_bus_notify,
};

static struct notifier_block ux500_pd_amba_notifier = {
	.notifier_call = ux500_pd_bus_notify,
};

static int __init ux500_pm_runtime_platform_init(void)
{
	bus_register_notifier(&platform_bus_type, &ux500_pd_platform_notifier);
	return 0;
}
core_initcall(ux500_pm_runtime_platform_init);

/*
 * The amba bus itself gets registered in a core_initcall, so we can't use
 * that.
 */
static int __init ux500_pm_runtime_amba_init(void)
{
	bus_register_notifier(&amba_bustype, &ux500_pd_amba_notifier);
	return 0;
}
arch_initcall(ux500_pm_runtime_amba_init);