summaryrefslogtreecommitdiff
path: root/drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.c
blob: 73fdec4b791d195eabbd35cade12d4702c3cbd45 (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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 */

#include <media/cec.h>

#include "cec-splitter.h"

/*
 * Helper function to reply to a received message with a Feature Abort
 * message.
 */
static int cec_feature_abort_reason(struct cec_adapter *adap,
				    struct cec_msg *msg, u8 reason)
{
	struct cec_msg tx_msg = { };

	/*
	 * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT
	 * message!
	 */
	if (msg->msg[1] == CEC_MSG_FEATURE_ABORT)
		return 0;
	/* Don't Feature Abort messages from 'Unregistered' */
	if (cec_msg_initiator(msg) == CEC_LOG_ADDR_UNREGISTERED)
		return 0;
	cec_msg_set_reply_to(&tx_msg, msg);
	cec_msg_feature_abort(&tx_msg, msg->msg[1], reason);
	return cec_transmit_msg(adap, &tx_msg, false);
}

/* Transmit an Active Source message from this output port to a sink */
static void cec_port_out_active_source(struct cec_splitter_port *p)
{
	struct cec_adapter *adap = p->adap;
	struct cec_msg msg;

	if (!adap->is_configured)
		return;
	p->is_active_source = true;
	cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
	cec_msg_active_source(&msg, adap->phys_addr);
	cec_transmit_msg(adap, &msg, false);
}

/* Transmit Active Source messages from all output ports to the sinks */
static void cec_out_active_source(struct cec_splitter *splitter)
{
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++)
		cec_port_out_active_source(splitter->ports[i]);
}

/* Transmit a Standby message from this output port to a sink */
static void cec_port_out_standby(struct cec_splitter_port *p)
{
	struct cec_adapter *adap = p->adap;
	struct cec_msg msg;

	if (!adap->is_configured)
		return;
	cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
	cec_msg_standby(&msg);
	cec_transmit_msg(adap, &msg, false);
}

/* Transmit Standby messages from all output ports to the sinks */
static void cec_out_standby(struct cec_splitter *splitter)
{
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++)
		cec_port_out_standby(splitter->ports[i]);
}

/* Transmit an Image/Text View On message from this output port to a sink */
static void cec_port_out_wakeup(struct cec_splitter_port *p, u8 opcode)
{
	struct cec_adapter *adap = p->adap;
	u8 la = adap->log_addrs.log_addr[0];
	struct cec_msg msg;

	if (la == CEC_LOG_ADDR_INVALID)
		la = CEC_LOG_ADDR_UNREGISTERED;
	cec_msg_init(&msg, la, 0);
	msg.len = 2;
	msg.msg[1] = opcode;
	cec_transmit_msg(adap, &msg, false);
}

/* Transmit Image/Text View On messages from all output ports to the sinks */
static void cec_out_wakeup(struct cec_splitter *splitter, u8 opcode)
{
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++)
		cec_port_out_wakeup(splitter->ports[i], opcode);
}

/*
 * Update the power state of the unconfigured CEC device to either
 * Off or On depending on the current state of the splitter.
 * This keeps the outputs in a consistent state.
 */
void cec_splitter_unconfigured_output(struct cec_splitter_port *p)
{
	p->video_latency = 1;
	p->power_status = p->splitter->is_standby ?
		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;

	/* The adapter was unconfigured, so clear the sequence and ts values */
	p->out_give_device_power_status_seq = 0;
	p->out_give_device_power_status_ts = ktime_set(0, 0);
	p->out_request_current_latency_seq = 0;
	p->out_request_current_latency_ts = ktime_set(0, 0);
}

/*
 * Update the power state of the newly configured CEC device to either
 * Off or On depending on the current state of the splitter.
 * This keeps the outputs in a consistent state.
 */
void cec_splitter_configured_output(struct cec_splitter_port *p)
{
	p->video_latency = 1;
	p->power_status = p->splitter->is_standby ?
		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;

	if (p->splitter->is_standby) {
		/*
		 * Some sinks only obey Standby if it comes from the
		 * active source.
		 */
		cec_port_out_active_source(p);
		cec_port_out_standby(p);
	} else {
		cec_port_out_wakeup(p, CEC_MSG_IMAGE_VIEW_ON);
	}
}

/* Pass the in_msg on to all output ports */
static void cec_out_passthrough(struct cec_splitter *splitter,
				const struct cec_msg *in_msg)
{
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;
		struct cec_msg msg;

		if (!adap->is_configured)
			continue;
		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
		msg.len = in_msg->len;
		memcpy(msg.msg + 1, in_msg->msg + 1, msg.len - 1);
		cec_transmit_msg(adap, &msg, false);
	}
}

/*
 * See if all output ports received the Report Current Latency message,
 * and if so, transmit the result from the input port to the video source.
 */
static void cec_out_report_current_latency(struct cec_splitter *splitter,
					   struct cec_adapter *input_adap)
{
	struct cec_msg reply = {};
	unsigned int reply_lat = 0;
	unsigned int cnt = 0;
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;

		/* Skip unconfigured ports */
		if (!adap->is_configured)
			continue;
		/* Return if a port is still waiting for a reply */
		if (p->out_request_current_latency_seq)
			return;
		reply_lat += p->video_latency - 1;
		cnt++;
	}

	/*
	 * All ports that can reply, replied, so clear the sequence
	 * and timestamp values.
	 */
	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];

		p->out_request_current_latency_seq = 0;
		p->out_request_current_latency_ts = ktime_set(0, 0);
	}

	/*
	 * Return if there were no replies or the input port is no longer
	 * configured.
	 */
	if (!cnt || !input_adap->is_configured)
		return;

	/* Reply with the average latency */
	reply_lat = 1 + reply_lat / cnt;
	cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
		     splitter->request_current_latency_dest);
	cec_msg_report_current_latency(&reply, input_adap->phys_addr,
				       reply_lat, 1, 1, 1);
	cec_transmit_msg(input_adap, &reply, false);
}

/* Transmit Request Current Latency to all output ports */
static int cec_out_request_current_latency(struct cec_splitter *splitter)
{
	ktime_t now = ktime_get();
	bool error = true;
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;

		if (!adap->is_configured) {
			/* Clear if not configured */
			p->out_request_current_latency_seq = 0;
			p->out_request_current_latency_ts = ktime_set(0, 0);
		} else if (!p->out_request_current_latency_seq) {
			/*
			 * Keep the old ts if an earlier request is still
			 * pending. This ensures that the request will
			 * eventually time out based on the timestamp of
			 * the first request if the sink is unresponsive.
			 */
			p->out_request_current_latency_ts = now;
		}
	}

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;
		struct cec_msg msg;

		if (!adap->is_configured)
			continue;
		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
		cec_msg_request_current_latency(&msg, true, adap->phys_addr);
		if (cec_transmit_msg(adap, &msg, false))
			continue;
		p->out_request_current_latency_seq = msg.sequence | (1U << 31);
		error = false;
	}
	return error ? -ENODEV : 0;
}

/*
 * See if all output ports received the Report Power Status message,
 * and if so, transmit the result from the input port to the video source.
 */
static void cec_out_report_power_status(struct cec_splitter *splitter,
					struct cec_adapter *input_adap)
{
	struct cec_msg reply = {};
	/* The target power status of the splitter itself */
	u8 splitter_pwr = splitter->is_standby ?
		CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
	/*
	 * The transient power status of the splitter, used if not all
	 * output report the target power status.
	 */
	u8 splitter_transient_pwr = splitter->is_standby ?
		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
	u8 reply_pwr = splitter_pwr;
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];

		/* Skip if no sink was found (HPD was low for more than 5s) */
		if (!p->found_sink)
			continue;

		/* Return if a port is still waiting for a reply */
		if (p->out_give_device_power_status_seq)
			return;
		if (p->power_status != splitter_pwr)
			reply_pwr = splitter_transient_pwr;
	}

	/*
	 * All ports that can reply, replied, so clear the sequence
	 * and timestamp values.
	 */
	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];

		p->out_give_device_power_status_seq = 0;
		p->out_give_device_power_status_ts = ktime_set(0, 0);
	}

	/* Return if the input port is no longer configured. */
	if (!input_adap->is_configured)
		return;

	/* Reply with the new power status */
	cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
		     splitter->give_device_power_status_dest);
	cec_msg_report_power_status(&reply, reply_pwr);
	cec_transmit_msg(input_adap, &reply, false);
}

/* Transmit Give Device Power Status to all output ports */
static int cec_out_give_device_power_status(struct cec_splitter *splitter)
{
	ktime_t now = ktime_get();
	bool error = true;
	unsigned int i;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;

		/*
		 * Keep the old ts if an earlier request is still
		 * pending. This ensures that the request will
		 * eventually time out based on the timestamp of
		 * the first request if the sink is unresponsive.
		 */
		if (adap->is_configured && !p->out_give_device_power_status_seq)
			p->out_give_device_power_status_ts = now;
	}

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		struct cec_adapter *adap = p->adap;
		struct cec_msg msg;

		if (!adap->is_configured)
			continue;

		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
		cec_msg_give_device_power_status(&msg, true);
		if (cec_transmit_msg(adap, &msg, false))
			continue;
		p->out_give_device_power_status_seq = msg.sequence | (1U << 31);
		error = false;
	}
	return error ? -ENODEV : 0;
}

/*
 * CEC messages received on the HDMI input of the splitter are
 * forwarded (if relevant) to the HDMI outputs of the splitter.
 */
int cec_splitter_received_input(struct cec_splitter_port *p, struct cec_msg *msg)
{
	if (!cec_msg_status_is_ok(msg))
		return 0;

	if (msg->len < 2)
		return -ENOMSG;

	switch (msg->msg[1]) {
	case CEC_MSG_DEVICE_VENDOR_ID:
	case CEC_MSG_REPORT_POWER_STATUS:
	case CEC_MSG_SET_STREAM_PATH:
	case CEC_MSG_ROUTING_CHANGE:
	case CEC_MSG_REQUEST_ACTIVE_SOURCE:
	case CEC_MSG_SYSTEM_AUDIO_MODE_STATUS:
		return 0;

	case CEC_MSG_STANDBY:
		p->splitter->is_standby = true;
		cec_out_standby(p->splitter);
		return 0;

	case CEC_MSG_IMAGE_VIEW_ON:
	case CEC_MSG_TEXT_VIEW_ON:
		p->splitter->is_standby = false;
		cec_out_wakeup(p->splitter, msg->msg[1]);
		return 0;

	case CEC_MSG_ACTIVE_SOURCE:
		cec_out_active_source(p->splitter);
		return 0;

	case CEC_MSG_SET_SYSTEM_AUDIO_MODE:
		cec_out_passthrough(p->splitter, msg);
		return 0;

	case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
		p->splitter->give_device_power_status_dest =
			cec_msg_initiator(msg);
		if (cec_out_give_device_power_status(p->splitter))
			cec_feature_abort_reason(p->adap, msg,
						 CEC_OP_ABORT_INCORRECT_MODE);
		return 0;

	case CEC_MSG_REQUEST_CURRENT_LATENCY: {
		u16 pa;

		p->splitter->request_current_latency_dest =
			cec_msg_initiator(msg);
		cec_ops_request_current_latency(msg, &pa);
		if (pa == p->adap->phys_addr &&
		    cec_out_request_current_latency(p->splitter))
			cec_feature_abort_reason(p->adap, msg,
						 CEC_OP_ABORT_INCORRECT_MODE);
		return 0;
	}

	default:
		return -ENOMSG;
	}
	return -ENOMSG;
}

void cec_splitter_nb_transmit_canceled_output(struct cec_splitter_port *p,
					      const struct cec_msg *msg,
					      struct cec_adapter *input_adap)
{
	struct cec_splitter *splitter = p->splitter;
	u32 seq = msg->sequence | (1U << 31);

	/*
	 * If this is the result of a failed non-blocking transmit, or it is
	 * the result of the failed reply to a non-blocking transmit, then
	 * check if the original transmit was to get the current power status
	 * or latency and, if so, assume that the remove device is for one
	 * reason or another unavailable and assume that it is in the same
	 * power status as the splitter, or has no video latency.
	 */
	if ((cec_msg_recv_is_tx_result(msg) && !(msg->tx_status & CEC_TX_STATUS_OK)) ||
	    (cec_msg_recv_is_rx_result(msg) && !(msg->rx_status & CEC_RX_STATUS_OK))) {
		u8 tx_op = msg->msg[1];

		if (msg->len < 2)
			return;
		if (cec_msg_recv_is_rx_result(msg) &&
		    (msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT))
			tx_op = msg->msg[2];
		switch (tx_op) {
		case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
			if (p->out_give_device_power_status_seq != seq)
				break;
			p->out_give_device_power_status_seq = 0;
			p->out_give_device_power_status_ts = ktime_set(0, 0);
			p->power_status = splitter->is_standby ?
					  CEC_OP_POWER_STATUS_STANDBY :
					  CEC_OP_POWER_STATUS_ON;
			cec_out_report_power_status(splitter, input_adap);
			break;
		case CEC_MSG_REQUEST_CURRENT_LATENCY:
			if (p->out_request_current_latency_seq != seq)
				break;
			p->video_latency = 1;
			p->out_request_current_latency_seq = 0;
			p->out_request_current_latency_ts = ktime_set(0, 0);
			cec_out_report_current_latency(splitter, input_adap);
			break;
		}
		return;
	}

	if (cec_msg_recv_is_tx_result(msg)) {
		if (p->out_request_current_latency_seq != seq)
			return;
		p->out_request_current_latency_ts = ns_to_ktime(msg->tx_ts);
		return;
	}
}

/*
 * CEC messages received on an HDMI output of the splitter
 * are processed here.
 */
int cec_splitter_received_output(struct cec_splitter_port *p, struct cec_msg *msg,
				 struct cec_adapter *input_adap)
{
	struct cec_adapter *adap = p->adap;
	struct cec_splitter *splitter = p->splitter;
	u32 seq = msg->sequence | (1U << 31);
	struct cec_msg reply = {};
	u16 pa;

	if (!adap->is_configured || msg->len < 2)
		return -ENOMSG;

	switch (msg->msg[1]) {
	case CEC_MSG_REPORT_POWER_STATUS: {
		u8 pwr;

		cec_ops_report_power_status(msg, &pwr);
		if (pwr > CEC_OP_POWER_STATUS_TO_STANDBY)
			pwr = splitter->is_standby ?
				CEC_OP_POWER_STATUS_TO_STANDBY :
				CEC_OP_POWER_STATUS_TO_ON;
		p->power_status = pwr;
		if (p->out_give_device_power_status_seq == seq) {
			p->out_give_device_power_status_seq = 0;
			p->out_give_device_power_status_ts = ktime_set(0, 0);
		}
		cec_out_report_power_status(splitter, input_adap);
		return 0;
	}

	case CEC_MSG_REPORT_CURRENT_LATENCY: {
		u8 video_lat;
		u8 low_lat_mode;
		u8 audio_out_comp;
		u8 audio_out_delay;

		cec_ops_report_current_latency(msg, &pa,
					       &video_lat, &low_lat_mode,
					       &audio_out_comp, &audio_out_delay);
		if (!video_lat || video_lat >= 252)
			video_lat = 1;
		p->video_latency = video_lat;
		if (p->out_request_current_latency_seq == seq) {
			p->out_request_current_latency_seq = 0;
			p->out_request_current_latency_ts = ktime_set(0, 0);
		}
		cec_out_report_current_latency(splitter, input_adap);
		return 0;
	}

	case CEC_MSG_STANDBY:
	case CEC_MSG_ROUTING_CHANGE:
	case CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS:
		return 0;

	case CEC_MSG_ACTIVE_SOURCE:
		cec_ops_active_source(msg, &pa);
		if (pa == 0)
			p->is_active_source = false;
		return 0;

	case CEC_MSG_REQUEST_ACTIVE_SOURCE:
		if (!p->is_active_source)
			return 0;
		cec_msg_set_reply_to(&reply, msg);
		cec_msg_active_source(&reply, adap->phys_addr);
		cec_transmit_msg(adap, &reply, false);
		return 0;

	case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
		cec_msg_set_reply_to(&reply, msg);
		cec_msg_report_power_status(&reply, splitter->is_standby ?
					    CEC_OP_POWER_STATUS_STANDBY :
					    CEC_OP_POWER_STATUS_ON);
		cec_transmit_msg(adap, &reply, false);
		return 0;

	case CEC_MSG_SET_STREAM_PATH:
		cec_ops_set_stream_path(msg, &pa);
		if (pa == adap->phys_addr) {
			cec_msg_set_reply_to(&reply, msg);
			cec_msg_active_source(&reply, pa);
			cec_transmit_msg(adap, &reply, false);
		}
		return 0;

	default:
		return -ENOMSG;
	}
	return -ENOMSG;
}

/*
 * Called every second to check for timed out messages and whether there
 * still is a video sink connected or not.
 *
 * Returns true if sinks were lost.
 */
bool cec_splitter_poll(struct cec_splitter *splitter,
		       struct cec_adapter *input_adap, bool debug)
{
	ktime_t now = ktime_get();
	u8 pwr = splitter->is_standby ?
		 CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
	unsigned int max_delay_ms = input_adap->xfer_timeout_ms + 2000;
	unsigned int i;
	bool res = false;

	for (i = 0; i < splitter->num_out_ports; i++) {
		struct cec_splitter_port *p = splitter->ports[i];
		s64 pwr_delta, lat_delta;
		bool pwr_timeout, lat_timeout;

		if (!p)
			continue;

		pwr_delta = ktime_ms_delta(now, p->out_give_device_power_status_ts);
		pwr_timeout = p->out_give_device_power_status_seq &&
			      pwr_delta >= max_delay_ms;
		lat_delta = ktime_ms_delta(now, p->out_request_current_latency_ts);
		lat_timeout = p->out_request_current_latency_seq &&
			      lat_delta >= max_delay_ms;

		/*
		 * If the HPD is low for more than 5 seconds, then assume no display
		 * is connected.
		 */
		if (p->found_sink && ktime_to_ns(p->lost_sink_ts) &&
		    ktime_ms_delta(now, p->lost_sink_ts) > 5000) {
			if (debug)
				dev_info(splitter->dev,
					 "port %u: HPD low for more than 5s, assume no sink is connected.\n",
					 p->port);
			p->found_sink = false;
			p->lost_sink_ts = ktime_set(0, 0);
			res = true;
		}

		/*
		 * If the power status request timed out, then set the port's
		 * power status to that of the splitter, ensuring a consistent
		 * power state.
		 */
		if (pwr_timeout) {
			mutex_lock(&p->adap->lock);
			if (debug)
				dev_info(splitter->dev,
					 "port %u: give up on power status for seq %u\n",
					 p->port,
					 p->out_give_device_power_status_seq & ~(1 << 31));
			p->power_status = pwr;
			p->out_give_device_power_status_seq = 0;
			p->out_give_device_power_status_ts = ktime_set(0, 0);
			mutex_unlock(&p->adap->lock);
			cec_out_report_power_status(splitter, input_adap);
		}

		/*
		 * If the current latency request timed out, then set the port's
		 * latency to 1.
		 */
		if (lat_timeout) {
			mutex_lock(&p->adap->lock);
			if (debug)
				dev_info(splitter->dev,
					 "port %u: give up on latency for seq %u\n",
					 p->port,
					 p->out_request_current_latency_seq & ~(1 << 31));
			p->video_latency = 1;
			p->out_request_current_latency_seq = 0;
			p->out_request_current_latency_ts = ktime_set(0, 0);
			mutex_unlock(&p->adap->lock);
			cec_out_report_current_latency(splitter, input_adap);
		}
	}
	return res;
}