summaryrefslogtreecommitdiff
path: root/drivers/net/wwan/iosm/iosm_ipc_pm.c
blob: 413601c72dcd7e268edad8cc392c9e0e68c93241 (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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2020-21 Intel Corporation.
 */

#include "iosm_ipc_protocol.h"

/* Timeout value in MS for the PM to wait for device to reach active state */
#define IPC_PM_ACTIVE_TIMEOUT_MS (500)

/* Note that here "active" has the value 1, as compared to the enums
 * ipc_mem_host_pm_state or ipc_mem_dev_pm_state, where "active" is 0
 */
#define IPC_PM_SLEEP (0)
#define CONSUME_STATE (0)
#define IPC_PM_ACTIVE (1)

void ipc_pm_signal_hpda_doorbell(struct iosm_pm *ipc_pm, u32 identifier,
				 bool host_slp_check)
{
	if (host_slp_check && ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE &&
	    ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE_WAIT) {
		ipc_pm->pending_hpda_update = true;
		dev_dbg(ipc_pm->dev,
			"Pend HPDA update set. Host PM_State: %d identifier:%d",
			ipc_pm->host_pm_state, identifier);
		return;
	}

	if (!ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, true)) {
		ipc_pm->pending_hpda_update = true;
		dev_dbg(ipc_pm->dev, "Pending HPDA update set. identifier:%d",
			identifier);
		return;
	}
	ipc_pm->pending_hpda_update = false;

	/* Trigger the irq towards CP */
	ipc_cp_irq_hpda_update(ipc_pm->pcie, identifier);

	ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, false);
}

/* Wake up the device if it is in low power mode. */
static bool ipc_pm_link_activate(struct iosm_pm *ipc_pm)
{
	if (ipc_pm->cp_state == IPC_MEM_DEV_PM_ACTIVE)
		return true;

	if (ipc_pm->cp_state == IPC_MEM_DEV_PM_SLEEP) {
		if (ipc_pm->ap_state == IPC_MEM_DEV_PM_SLEEP) {
			/* Wake up the device. */
			ipc_cp_irq_sleep_control(ipc_pm->pcie,
						 IPC_MEM_DEV_PM_WAKEUP);
			ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE_WAIT;

			goto not_active;
		}

		if (ipc_pm->ap_state == IPC_MEM_DEV_PM_ACTIVE_WAIT)
			goto not_active;

		return true;
	}

not_active:
	/* link is not ready */
	return false;
}

bool ipc_pm_wait_for_device_active(struct iosm_pm *ipc_pm)
{
	bool ret_val = false;

	if (ipc_pm->ap_state != IPC_MEM_DEV_PM_ACTIVE) {
		/* Complete all memory stores before setting bit */
		smp_mb__before_atomic();

		/* Wait for IPC_PM_ACTIVE_TIMEOUT_MS for Device sleep state
		 * machine to enter ACTIVE state.
		 */
		set_bit(0, &ipc_pm->host_sleep_pend);

		/* Complete all memory stores after setting bit */
		smp_mb__after_atomic();

		if (!wait_for_completion_interruptible_timeout
		   (&ipc_pm->host_sleep_complete,
		    msecs_to_jiffies(IPC_PM_ACTIVE_TIMEOUT_MS))) {
			dev_err(ipc_pm->dev,
				"PM timeout. Expected State:%d. Actual: %d",
				IPC_MEM_DEV_PM_ACTIVE, ipc_pm->ap_state);
			goto  active_timeout;
		}
	}

	ret_val = true;
active_timeout:
	/* Complete all memory stores before clearing bit */
	smp_mb__before_atomic();

	/* Reset the atomic variable in any case as device sleep
	 * state machine change is no longer of interest.
	 */
	clear_bit(0, &ipc_pm->host_sleep_pend);

	/* Complete all memory stores after clearing bit */
	smp_mb__after_atomic();

	return ret_val;
}

static void ipc_pm_on_link_sleep(struct iosm_pm *ipc_pm)
{
	/* pending sleep ack and all conditions are cleared
	 * -> signal SLEEP__ACK to CP
	 */
	ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
	ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;

	ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_SLEEP);
}

static void ipc_pm_on_link_wake(struct iosm_pm *ipc_pm, bool ack)
{
	ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;

	if (ack) {
		ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;

		ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_ACTIVE);

		/* check the consume state !!! */
		if (test_bit(CONSUME_STATE, &ipc_pm->host_sleep_pend))
			complete(&ipc_pm->host_sleep_complete);
	}

	/* Check for pending HPDA update.
	 * Pending HP update could be because of sending message was
	 * put on hold due to Device sleep state or due to TD update
	 * which could be because of Device Sleep and Host Sleep
	 * states.
	 */
	if (ipc_pm->pending_hpda_update &&
	    ipc_pm->host_pm_state == IPC_MEM_HOST_PM_ACTIVE)
		ipc_pm_signal_hpda_doorbell(ipc_pm, IPC_HP_PM_TRIGGER, true);
}

bool ipc_pm_trigger(struct iosm_pm *ipc_pm, enum ipc_pm_unit unit, bool active)
{
	union ipc_pm_cond old_cond;
	union ipc_pm_cond new_cond;
	bool link_active;

	/* Save the current D3 state. */
	new_cond = ipc_pm->pm_cond;
	old_cond = ipc_pm->pm_cond;

	/* Calculate the power state only in the runtime phase. */
	switch (unit) {
	case IPC_PM_UNIT_IRQ: /* CP irq */
		new_cond.irq = active;
		break;

	case IPC_PM_UNIT_LINK: /* Device link state. */
		new_cond.link = active;
		break;

	case IPC_PM_UNIT_HS: /* Host sleep trigger requires Link. */
		new_cond.hs = active;
		break;

	default:
		break;
	}

	/* Something changed ? */
	if (old_cond.raw == new_cond.raw) {
		/* Stay in the current PM state. */
		link_active = old_cond.link == IPC_PM_ACTIVE;
		goto ret;
	}

	ipc_pm->pm_cond = new_cond;

	if (new_cond.link)
		ipc_pm_on_link_wake(ipc_pm, unit == IPC_PM_UNIT_LINK);
	else if (unit == IPC_PM_UNIT_LINK)
		ipc_pm_on_link_sleep(ipc_pm);

	if (old_cond.link == IPC_PM_SLEEP && new_cond.raw) {
		link_active = ipc_pm_link_activate(ipc_pm);
		goto ret;
	}

	link_active = old_cond.link == IPC_PM_ACTIVE;

ret:
	return link_active;
}

bool ipc_pm_prepare_host_sleep(struct iosm_pm *ipc_pm)
{
	/* suspend not allowed if host_pm_state is not IPC_MEM_HOST_PM_ACTIVE */
	if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE) {
		dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
			ipc_pm->host_pm_state, IPC_MEM_HOST_PM_ACTIVE);
		return false;
	}

	ipc_pm->host_pm_state = IPC_MEM_HOST_PM_SLEEP_WAIT_D3;

	return true;
}

bool ipc_pm_prepare_host_active(struct iosm_pm *ipc_pm)
{
	if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_SLEEP) {
		dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
			ipc_pm->host_pm_state, IPC_MEM_HOST_PM_SLEEP);
		return false;
	}

	/* Sending Sleep Exit message to CP. Update the state */
	ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE_WAIT;

	return true;
}

void ipc_pm_set_s2idle_sleep(struct iosm_pm *ipc_pm, bool sleep)
{
	if (sleep) {
		ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;
		ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
		ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_SLEEP;
	} else {
		ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
		ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
		ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_ACTIVE;
		ipc_pm->pm_cond.link = IPC_PM_ACTIVE;
	}
}

bool ipc_pm_dev_slp_notification(struct iosm_pm *ipc_pm, u32 cp_pm_req)
{
	if (cp_pm_req == ipc_pm->device_sleep_notification)
		return false;

	ipc_pm->device_sleep_notification = cp_pm_req;

	/* Evaluate the PM request. */
	switch (ipc_pm->cp_state) {
	case IPC_MEM_DEV_PM_ACTIVE:
		switch (cp_pm_req) {
		case IPC_MEM_DEV_PM_ACTIVE:
			break;

		case IPC_MEM_DEV_PM_SLEEP:
			/* Inform the PM that the device link can go down. */
			ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, false);
			return true;

		default:
			dev_err(ipc_pm->dev,
				"loc-pm=%d active: confused req-pm=%d",
				ipc_pm->cp_state, cp_pm_req);
			break;
		}
		break;

	case IPC_MEM_DEV_PM_SLEEP:
		switch (cp_pm_req) {
		case IPC_MEM_DEV_PM_ACTIVE:
			/* Inform the PM that the device link is active. */
			ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, true);
			break;

		case IPC_MEM_DEV_PM_SLEEP:
			break;

		default:
			dev_err(ipc_pm->dev,
				"loc-pm=%d sleep: confused req-pm=%d",
				ipc_pm->cp_state, cp_pm_req);
			break;
		}
		break;

	default:
		dev_err(ipc_pm->dev, "confused loc-pm=%d, req-pm=%d",
			ipc_pm->cp_state, cp_pm_req);
		break;
	}

	return false;
}

void ipc_pm_init(struct iosm_protocol *ipc_protocol)
{
	struct iosm_imem *ipc_imem = ipc_protocol->imem;
	struct iosm_pm *ipc_pm = &ipc_protocol->pm;

	ipc_pm->pcie = ipc_imem->pcie;
	ipc_pm->dev = ipc_imem->dev;

	ipc_pm->pm_cond.irq = IPC_PM_SLEEP;
	ipc_pm->pm_cond.hs = IPC_PM_SLEEP;
	ipc_pm->pm_cond.link = IPC_PM_ACTIVE;

	ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
	ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
	ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE;

	/* Create generic wait-for-completion handler for Host Sleep
	 * and device sleep coordination.
	 */
	init_completion(&ipc_pm->host_sleep_complete);

	/* Complete all memory stores before clearing bit */
	smp_mb__before_atomic();

	clear_bit(0, &ipc_pm->host_sleep_pend);

	/* Complete all memory stores after clearing bit */
	smp_mb__after_atomic();
}

void ipc_pm_deinit(struct iosm_protocol *proto)
{
	struct iosm_pm *ipc_pm = &proto->pm;

	complete(&ipc_pm->host_sleep_complete);
}