xref: /linux/drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
1*056f2821SHans Verkuil // SPDX-License-Identifier: GPL-2.0-only
2*056f2821SHans Verkuil 
3*056f2821SHans Verkuil /*
4*056f2821SHans Verkuil  * Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5*056f2821SHans Verkuil  */
6*056f2821SHans Verkuil 
7*056f2821SHans Verkuil #include <media/cec.h>
8*056f2821SHans Verkuil 
9*056f2821SHans Verkuil #include "cec-splitter.h"
10*056f2821SHans Verkuil 
11*056f2821SHans Verkuil /*
12*056f2821SHans Verkuil  * Helper function to reply to a received message with a Feature Abort
13*056f2821SHans Verkuil  * message.
14*056f2821SHans Verkuil  */
cec_feature_abort_reason(struct cec_adapter * adap,struct cec_msg * msg,u8 reason)15*056f2821SHans Verkuil static int cec_feature_abort_reason(struct cec_adapter *adap,
16*056f2821SHans Verkuil 				    struct cec_msg *msg, u8 reason)
17*056f2821SHans Verkuil {
18*056f2821SHans Verkuil 	struct cec_msg tx_msg = { };
19*056f2821SHans Verkuil 
20*056f2821SHans Verkuil 	/*
21*056f2821SHans Verkuil 	 * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT
22*056f2821SHans Verkuil 	 * message!
23*056f2821SHans Verkuil 	 */
24*056f2821SHans Verkuil 	if (msg->msg[1] == CEC_MSG_FEATURE_ABORT)
25*056f2821SHans Verkuil 		return 0;
26*056f2821SHans Verkuil 	/* Don't Feature Abort messages from 'Unregistered' */
27*056f2821SHans Verkuil 	if (cec_msg_initiator(msg) == CEC_LOG_ADDR_UNREGISTERED)
28*056f2821SHans Verkuil 		return 0;
29*056f2821SHans Verkuil 	cec_msg_set_reply_to(&tx_msg, msg);
30*056f2821SHans Verkuil 	cec_msg_feature_abort(&tx_msg, msg->msg[1], reason);
31*056f2821SHans Verkuil 	return cec_transmit_msg(adap, &tx_msg, false);
32*056f2821SHans Verkuil }
33*056f2821SHans Verkuil 
34*056f2821SHans Verkuil /* Transmit an Active Source message from this output port to a sink */
cec_port_out_active_source(struct cec_splitter_port * p)35*056f2821SHans Verkuil static void cec_port_out_active_source(struct cec_splitter_port *p)
36*056f2821SHans Verkuil {
37*056f2821SHans Verkuil 	struct cec_adapter *adap = p->adap;
38*056f2821SHans Verkuil 	struct cec_msg msg;
39*056f2821SHans Verkuil 
40*056f2821SHans Verkuil 	if (!adap->is_configured)
41*056f2821SHans Verkuil 		return;
42*056f2821SHans Verkuil 	p->is_active_source = true;
43*056f2821SHans Verkuil 	cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
44*056f2821SHans Verkuil 	cec_msg_active_source(&msg, adap->phys_addr);
45*056f2821SHans Verkuil 	cec_transmit_msg(adap, &msg, false);
46*056f2821SHans Verkuil }
47*056f2821SHans Verkuil 
48*056f2821SHans Verkuil /* Transmit Active Source messages from all output ports to the sinks */
cec_out_active_source(struct cec_splitter * splitter)49*056f2821SHans Verkuil static void cec_out_active_source(struct cec_splitter *splitter)
50*056f2821SHans Verkuil {
51*056f2821SHans Verkuil 	unsigned int i;
52*056f2821SHans Verkuil 
53*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++)
54*056f2821SHans Verkuil 		cec_port_out_active_source(splitter->ports[i]);
55*056f2821SHans Verkuil }
56*056f2821SHans Verkuil 
57*056f2821SHans Verkuil /* Transmit a Standby message from this output port to a sink */
cec_port_out_standby(struct cec_splitter_port * p)58*056f2821SHans Verkuil static void cec_port_out_standby(struct cec_splitter_port *p)
59*056f2821SHans Verkuil {
60*056f2821SHans Verkuil 	struct cec_adapter *adap = p->adap;
61*056f2821SHans Verkuil 	struct cec_msg msg;
62*056f2821SHans Verkuil 
63*056f2821SHans Verkuil 	if (!adap->is_configured)
64*056f2821SHans Verkuil 		return;
65*056f2821SHans Verkuil 	cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
66*056f2821SHans Verkuil 	cec_msg_standby(&msg);
67*056f2821SHans Verkuil 	cec_transmit_msg(adap, &msg, false);
68*056f2821SHans Verkuil }
69*056f2821SHans Verkuil 
70*056f2821SHans Verkuil /* Transmit Standby messages from all output ports to the sinks */
cec_out_standby(struct cec_splitter * splitter)71*056f2821SHans Verkuil static void cec_out_standby(struct cec_splitter *splitter)
72*056f2821SHans Verkuil {
73*056f2821SHans Verkuil 	unsigned int i;
74*056f2821SHans Verkuil 
75*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++)
76*056f2821SHans Verkuil 		cec_port_out_standby(splitter->ports[i]);
77*056f2821SHans Verkuil }
78*056f2821SHans Verkuil 
79*056f2821SHans Verkuil /* Transmit an Image/Text View On message from this output port to a sink */
cec_port_out_wakeup(struct cec_splitter_port * p,u8 opcode)80*056f2821SHans Verkuil static void cec_port_out_wakeup(struct cec_splitter_port *p, u8 opcode)
81*056f2821SHans Verkuil {
82*056f2821SHans Verkuil 	struct cec_adapter *adap = p->adap;
83*056f2821SHans Verkuil 	u8 la = adap->log_addrs.log_addr[0];
84*056f2821SHans Verkuil 	struct cec_msg msg;
85*056f2821SHans Verkuil 
86*056f2821SHans Verkuil 	if (la == CEC_LOG_ADDR_INVALID)
87*056f2821SHans Verkuil 		la = CEC_LOG_ADDR_UNREGISTERED;
88*056f2821SHans Verkuil 	cec_msg_init(&msg, la, 0);
89*056f2821SHans Verkuil 	msg.len = 2;
90*056f2821SHans Verkuil 	msg.msg[1] = opcode;
91*056f2821SHans Verkuil 	cec_transmit_msg(adap, &msg, false);
92*056f2821SHans Verkuil }
93*056f2821SHans Verkuil 
94*056f2821SHans Verkuil /* Transmit Image/Text View On messages from all output ports to the sinks */
cec_out_wakeup(struct cec_splitter * splitter,u8 opcode)95*056f2821SHans Verkuil static void cec_out_wakeup(struct cec_splitter *splitter, u8 opcode)
96*056f2821SHans Verkuil {
97*056f2821SHans Verkuil 	unsigned int i;
98*056f2821SHans Verkuil 
99*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++)
100*056f2821SHans Verkuil 		cec_port_out_wakeup(splitter->ports[i], opcode);
101*056f2821SHans Verkuil }
102*056f2821SHans Verkuil 
103*056f2821SHans Verkuil /*
104*056f2821SHans Verkuil  * Update the power state of the unconfigured CEC device to either
105*056f2821SHans Verkuil  * Off or On depending on the current state of the splitter.
106*056f2821SHans Verkuil  * This keeps the outputs in a consistent state.
107*056f2821SHans Verkuil  */
cec_splitter_unconfigured_output(struct cec_splitter_port * p)108*056f2821SHans Verkuil void cec_splitter_unconfigured_output(struct cec_splitter_port *p)
109*056f2821SHans Verkuil {
110*056f2821SHans Verkuil 	p->video_latency = 1;
111*056f2821SHans Verkuil 	p->power_status = p->splitter->is_standby ?
112*056f2821SHans Verkuil 		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
113*056f2821SHans Verkuil 
114*056f2821SHans Verkuil 	/* The adapter was unconfigured, so clear the sequence and ts values */
115*056f2821SHans Verkuil 	p->out_give_device_power_status_seq = 0;
116*056f2821SHans Verkuil 	p->out_give_device_power_status_ts = ktime_set(0, 0);
117*056f2821SHans Verkuil 	p->out_request_current_latency_seq = 0;
118*056f2821SHans Verkuil 	p->out_request_current_latency_ts = ktime_set(0, 0);
119*056f2821SHans Verkuil }
120*056f2821SHans Verkuil 
121*056f2821SHans Verkuil /*
122*056f2821SHans Verkuil  * Update the power state of the newly configured CEC device to either
123*056f2821SHans Verkuil  * Off or On depending on the current state of the splitter.
124*056f2821SHans Verkuil  * This keeps the outputs in a consistent state.
125*056f2821SHans Verkuil  */
cec_splitter_configured_output(struct cec_splitter_port * p)126*056f2821SHans Verkuil void cec_splitter_configured_output(struct cec_splitter_port *p)
127*056f2821SHans Verkuil {
128*056f2821SHans Verkuil 	p->video_latency = 1;
129*056f2821SHans Verkuil 	p->power_status = p->splitter->is_standby ?
130*056f2821SHans Verkuil 		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
131*056f2821SHans Verkuil 
132*056f2821SHans Verkuil 	if (p->splitter->is_standby) {
133*056f2821SHans Verkuil 		/*
134*056f2821SHans Verkuil 		 * Some sinks only obey Standby if it comes from the
135*056f2821SHans Verkuil 		 * active source.
136*056f2821SHans Verkuil 		 */
137*056f2821SHans Verkuil 		cec_port_out_active_source(p);
138*056f2821SHans Verkuil 		cec_port_out_standby(p);
139*056f2821SHans Verkuil 	} else {
140*056f2821SHans Verkuil 		cec_port_out_wakeup(p, CEC_MSG_IMAGE_VIEW_ON);
141*056f2821SHans Verkuil 	}
142*056f2821SHans Verkuil }
143*056f2821SHans Verkuil 
144*056f2821SHans Verkuil /* Pass the in_msg on to all output ports */
cec_out_passthrough(struct cec_splitter * splitter,const struct cec_msg * in_msg)145*056f2821SHans Verkuil static void cec_out_passthrough(struct cec_splitter *splitter,
146*056f2821SHans Verkuil 				const struct cec_msg *in_msg)
147*056f2821SHans Verkuil {
148*056f2821SHans Verkuil 	unsigned int i;
149*056f2821SHans Verkuil 
150*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
151*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
152*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
153*056f2821SHans Verkuil 		struct cec_msg msg;
154*056f2821SHans Verkuil 
155*056f2821SHans Verkuil 		if (!adap->is_configured)
156*056f2821SHans Verkuil 			continue;
157*056f2821SHans Verkuil 		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
158*056f2821SHans Verkuil 		msg.len = in_msg->len;
159*056f2821SHans Verkuil 		memcpy(msg.msg + 1, in_msg->msg + 1, msg.len - 1);
160*056f2821SHans Verkuil 		cec_transmit_msg(adap, &msg, false);
161*056f2821SHans Verkuil 	}
162*056f2821SHans Verkuil }
163*056f2821SHans Verkuil 
164*056f2821SHans Verkuil /*
165*056f2821SHans Verkuil  * See if all output ports received the Report Current Latency message,
166*056f2821SHans Verkuil  * and if so, transmit the result from the input port to the video source.
167*056f2821SHans Verkuil  */
cec_out_report_current_latency(struct cec_splitter * splitter,struct cec_adapter * input_adap)168*056f2821SHans Verkuil static void cec_out_report_current_latency(struct cec_splitter *splitter,
169*056f2821SHans Verkuil 					   struct cec_adapter *input_adap)
170*056f2821SHans Verkuil {
171*056f2821SHans Verkuil 	struct cec_msg reply = {};
172*056f2821SHans Verkuil 	unsigned int reply_lat = 0;
173*056f2821SHans Verkuil 	unsigned int cnt = 0;
174*056f2821SHans Verkuil 	unsigned int i;
175*056f2821SHans Verkuil 
176*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
177*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
178*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
179*056f2821SHans Verkuil 
180*056f2821SHans Verkuil 		/* Skip unconfigured ports */
181*056f2821SHans Verkuil 		if (!adap->is_configured)
182*056f2821SHans Verkuil 			continue;
183*056f2821SHans Verkuil 		/* Return if a port is still waiting for a reply */
184*056f2821SHans Verkuil 		if (p->out_request_current_latency_seq)
185*056f2821SHans Verkuil 			return;
186*056f2821SHans Verkuil 		reply_lat += p->video_latency - 1;
187*056f2821SHans Verkuil 		cnt++;
188*056f2821SHans Verkuil 	}
189*056f2821SHans Verkuil 
190*056f2821SHans Verkuil 	/*
191*056f2821SHans Verkuil 	 * All ports that can reply, replied, so clear the sequence
192*056f2821SHans Verkuil 	 * and timestamp values.
193*056f2821SHans Verkuil 	 */
194*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
195*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
196*056f2821SHans Verkuil 
197*056f2821SHans Verkuil 		p->out_request_current_latency_seq = 0;
198*056f2821SHans Verkuil 		p->out_request_current_latency_ts = ktime_set(0, 0);
199*056f2821SHans Verkuil 	}
200*056f2821SHans Verkuil 
201*056f2821SHans Verkuil 	/*
202*056f2821SHans Verkuil 	 * Return if there were no replies or the input port is no longer
203*056f2821SHans Verkuil 	 * configured.
204*056f2821SHans Verkuil 	 */
205*056f2821SHans Verkuil 	if (!cnt || !input_adap->is_configured)
206*056f2821SHans Verkuil 		return;
207*056f2821SHans Verkuil 
208*056f2821SHans Verkuil 	/* Reply with the average latency */
209*056f2821SHans Verkuil 	reply_lat = 1 + reply_lat / cnt;
210*056f2821SHans Verkuil 	cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
211*056f2821SHans Verkuil 		     splitter->request_current_latency_dest);
212*056f2821SHans Verkuil 	cec_msg_report_current_latency(&reply, input_adap->phys_addr,
213*056f2821SHans Verkuil 				       reply_lat, 1, 1, 1);
214*056f2821SHans Verkuil 	cec_transmit_msg(input_adap, &reply, false);
215*056f2821SHans Verkuil }
216*056f2821SHans Verkuil 
217*056f2821SHans Verkuil /* Transmit Request Current Latency to all output ports */
cec_out_request_current_latency(struct cec_splitter * splitter)218*056f2821SHans Verkuil static int cec_out_request_current_latency(struct cec_splitter *splitter)
219*056f2821SHans Verkuil {
220*056f2821SHans Verkuil 	ktime_t now = ktime_get();
221*056f2821SHans Verkuil 	bool error = true;
222*056f2821SHans Verkuil 	unsigned int i;
223*056f2821SHans Verkuil 
224*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
225*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
226*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
227*056f2821SHans Verkuil 
228*056f2821SHans Verkuil 		if (!adap->is_configured) {
229*056f2821SHans Verkuil 			/* Clear if not configured */
230*056f2821SHans Verkuil 			p->out_request_current_latency_seq = 0;
231*056f2821SHans Verkuil 			p->out_request_current_latency_ts = ktime_set(0, 0);
232*056f2821SHans Verkuil 		} else if (!p->out_request_current_latency_seq) {
233*056f2821SHans Verkuil 			/*
234*056f2821SHans Verkuil 			 * Keep the old ts if an earlier request is still
235*056f2821SHans Verkuil 			 * pending. This ensures that the request will
236*056f2821SHans Verkuil 			 * eventually time out based on the timestamp of
237*056f2821SHans Verkuil 			 * the first request if the sink is unresponsive.
238*056f2821SHans Verkuil 			 */
239*056f2821SHans Verkuil 			p->out_request_current_latency_ts = now;
240*056f2821SHans Verkuil 		}
241*056f2821SHans Verkuil 	}
242*056f2821SHans Verkuil 
243*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
244*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
245*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
246*056f2821SHans Verkuil 		struct cec_msg msg;
247*056f2821SHans Verkuil 
248*056f2821SHans Verkuil 		if (!adap->is_configured)
249*056f2821SHans Verkuil 			continue;
250*056f2821SHans Verkuil 		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
251*056f2821SHans Verkuil 		cec_msg_request_current_latency(&msg, true, adap->phys_addr);
252*056f2821SHans Verkuil 		if (cec_transmit_msg(adap, &msg, false))
253*056f2821SHans Verkuil 			continue;
254*056f2821SHans Verkuil 		p->out_request_current_latency_seq = msg.sequence | (1U << 31);
255*056f2821SHans Verkuil 		error = false;
256*056f2821SHans Verkuil 	}
257*056f2821SHans Verkuil 	return error ? -ENODEV : 0;
258*056f2821SHans Verkuil }
259*056f2821SHans Verkuil 
260*056f2821SHans Verkuil /*
261*056f2821SHans Verkuil  * See if all output ports received the Report Power Status message,
262*056f2821SHans Verkuil  * and if so, transmit the result from the input port to the video source.
263*056f2821SHans Verkuil  */
cec_out_report_power_status(struct cec_splitter * splitter,struct cec_adapter * input_adap)264*056f2821SHans Verkuil static void cec_out_report_power_status(struct cec_splitter *splitter,
265*056f2821SHans Verkuil 					struct cec_adapter *input_adap)
266*056f2821SHans Verkuil {
267*056f2821SHans Verkuil 	struct cec_msg reply = {};
268*056f2821SHans Verkuil 	/* The target power status of the splitter itself */
269*056f2821SHans Verkuil 	u8 splitter_pwr = splitter->is_standby ?
270*056f2821SHans Verkuil 		CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
271*056f2821SHans Verkuil 	/*
272*056f2821SHans Verkuil 	 * The transient power status of the splitter, used if not all
273*056f2821SHans Verkuil 	 * output report the target power status.
274*056f2821SHans Verkuil 	 */
275*056f2821SHans Verkuil 	u8 splitter_transient_pwr = splitter->is_standby ?
276*056f2821SHans Verkuil 		CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
277*056f2821SHans Verkuil 	u8 reply_pwr = splitter_pwr;
278*056f2821SHans Verkuil 	unsigned int i;
279*056f2821SHans Verkuil 
280*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
281*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
282*056f2821SHans Verkuil 
283*056f2821SHans Verkuil 		/* Skip if no sink was found (HPD was low for more than 5s) */
284*056f2821SHans Verkuil 		if (!p->found_sink)
285*056f2821SHans Verkuil 			continue;
286*056f2821SHans Verkuil 
287*056f2821SHans Verkuil 		/* Return if a port is still waiting for a reply */
288*056f2821SHans Verkuil 		if (p->out_give_device_power_status_seq)
289*056f2821SHans Verkuil 			return;
290*056f2821SHans Verkuil 		if (p->power_status != splitter_pwr)
291*056f2821SHans Verkuil 			reply_pwr = splitter_transient_pwr;
292*056f2821SHans Verkuil 	}
293*056f2821SHans Verkuil 
294*056f2821SHans Verkuil 	/*
295*056f2821SHans Verkuil 	 * All ports that can reply, replied, so clear the sequence
296*056f2821SHans Verkuil 	 * and timestamp values.
297*056f2821SHans Verkuil 	 */
298*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
299*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
300*056f2821SHans Verkuil 
301*056f2821SHans Verkuil 		p->out_give_device_power_status_seq = 0;
302*056f2821SHans Verkuil 		p->out_give_device_power_status_ts = ktime_set(0, 0);
303*056f2821SHans Verkuil 	}
304*056f2821SHans Verkuil 
305*056f2821SHans Verkuil 	/* Return if the input port is no longer configured. */
306*056f2821SHans Verkuil 	if (!input_adap->is_configured)
307*056f2821SHans Verkuil 		return;
308*056f2821SHans Verkuil 
309*056f2821SHans Verkuil 	/* Reply with the new power status */
310*056f2821SHans Verkuil 	cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
311*056f2821SHans Verkuil 		     splitter->give_device_power_status_dest);
312*056f2821SHans Verkuil 	cec_msg_report_power_status(&reply, reply_pwr);
313*056f2821SHans Verkuil 	cec_transmit_msg(input_adap, &reply, false);
314*056f2821SHans Verkuil }
315*056f2821SHans Verkuil 
316*056f2821SHans Verkuil /* Transmit Give Device Power Status to all output ports */
cec_out_give_device_power_status(struct cec_splitter * splitter)317*056f2821SHans Verkuil static int cec_out_give_device_power_status(struct cec_splitter *splitter)
318*056f2821SHans Verkuil {
319*056f2821SHans Verkuil 	ktime_t now = ktime_get();
320*056f2821SHans Verkuil 	bool error = true;
321*056f2821SHans Verkuil 	unsigned int i;
322*056f2821SHans Verkuil 
323*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
324*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
325*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
326*056f2821SHans Verkuil 
327*056f2821SHans Verkuil 		/*
328*056f2821SHans Verkuil 		 * Keep the old ts if an earlier request is still
329*056f2821SHans Verkuil 		 * pending. This ensures that the request will
330*056f2821SHans Verkuil 		 * eventually time out based on the timestamp of
331*056f2821SHans Verkuil 		 * the first request if the sink is unresponsive.
332*056f2821SHans Verkuil 		 */
333*056f2821SHans Verkuil 		if (adap->is_configured && !p->out_give_device_power_status_seq)
334*056f2821SHans Verkuil 			p->out_give_device_power_status_ts = now;
335*056f2821SHans Verkuil 	}
336*056f2821SHans Verkuil 
337*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
338*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
339*056f2821SHans Verkuil 		struct cec_adapter *adap = p->adap;
340*056f2821SHans Verkuil 		struct cec_msg msg;
341*056f2821SHans Verkuil 
342*056f2821SHans Verkuil 		if (!adap->is_configured)
343*056f2821SHans Verkuil 			continue;
344*056f2821SHans Verkuil 
345*056f2821SHans Verkuil 		cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
346*056f2821SHans Verkuil 		cec_msg_give_device_power_status(&msg, true);
347*056f2821SHans Verkuil 		if (cec_transmit_msg(adap, &msg, false))
348*056f2821SHans Verkuil 			continue;
349*056f2821SHans Verkuil 		p->out_give_device_power_status_seq = msg.sequence | (1U << 31);
350*056f2821SHans Verkuil 		error = false;
351*056f2821SHans Verkuil 	}
352*056f2821SHans Verkuil 	return error ? -ENODEV : 0;
353*056f2821SHans Verkuil }
354*056f2821SHans Verkuil 
355*056f2821SHans Verkuil /*
356*056f2821SHans Verkuil  * CEC messages received on the HDMI input of the splitter are
357*056f2821SHans Verkuil  * forwarded (if relevant) to the HDMI outputs of the splitter.
358*056f2821SHans Verkuil  */
cec_splitter_received_input(struct cec_splitter_port * p,struct cec_msg * msg)359*056f2821SHans Verkuil int cec_splitter_received_input(struct cec_splitter_port *p, struct cec_msg *msg)
360*056f2821SHans Verkuil {
361*056f2821SHans Verkuil 	if (!cec_msg_status_is_ok(msg))
362*056f2821SHans Verkuil 		return 0;
363*056f2821SHans Verkuil 
364*056f2821SHans Verkuil 	if (msg->len < 2)
365*056f2821SHans Verkuil 		return -ENOMSG;
366*056f2821SHans Verkuil 
367*056f2821SHans Verkuil 	switch (msg->msg[1]) {
368*056f2821SHans Verkuil 	case CEC_MSG_DEVICE_VENDOR_ID:
369*056f2821SHans Verkuil 	case CEC_MSG_REPORT_POWER_STATUS:
370*056f2821SHans Verkuil 	case CEC_MSG_SET_STREAM_PATH:
371*056f2821SHans Verkuil 	case CEC_MSG_ROUTING_CHANGE:
372*056f2821SHans Verkuil 	case CEC_MSG_REQUEST_ACTIVE_SOURCE:
373*056f2821SHans Verkuil 	case CEC_MSG_SYSTEM_AUDIO_MODE_STATUS:
374*056f2821SHans Verkuil 		return 0;
375*056f2821SHans Verkuil 
376*056f2821SHans Verkuil 	case CEC_MSG_STANDBY:
377*056f2821SHans Verkuil 		p->splitter->is_standby = true;
378*056f2821SHans Verkuil 		cec_out_standby(p->splitter);
379*056f2821SHans Verkuil 		return 0;
380*056f2821SHans Verkuil 
381*056f2821SHans Verkuil 	case CEC_MSG_IMAGE_VIEW_ON:
382*056f2821SHans Verkuil 	case CEC_MSG_TEXT_VIEW_ON:
383*056f2821SHans Verkuil 		p->splitter->is_standby = false;
384*056f2821SHans Verkuil 		cec_out_wakeup(p->splitter, msg->msg[1]);
385*056f2821SHans Verkuil 		return 0;
386*056f2821SHans Verkuil 
387*056f2821SHans Verkuil 	case CEC_MSG_ACTIVE_SOURCE:
388*056f2821SHans Verkuil 		cec_out_active_source(p->splitter);
389*056f2821SHans Verkuil 		return 0;
390*056f2821SHans Verkuil 
391*056f2821SHans Verkuil 	case CEC_MSG_SET_SYSTEM_AUDIO_MODE:
392*056f2821SHans Verkuil 		cec_out_passthrough(p->splitter, msg);
393*056f2821SHans Verkuil 		return 0;
394*056f2821SHans Verkuil 
395*056f2821SHans Verkuil 	case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
396*056f2821SHans Verkuil 		p->splitter->give_device_power_status_dest =
397*056f2821SHans Verkuil 			cec_msg_initiator(msg);
398*056f2821SHans Verkuil 		if (cec_out_give_device_power_status(p->splitter))
399*056f2821SHans Verkuil 			cec_feature_abort_reason(p->adap, msg,
400*056f2821SHans Verkuil 						 CEC_OP_ABORT_INCORRECT_MODE);
401*056f2821SHans Verkuil 		return 0;
402*056f2821SHans Verkuil 
403*056f2821SHans Verkuil 	case CEC_MSG_REQUEST_CURRENT_LATENCY: {
404*056f2821SHans Verkuil 		u16 pa;
405*056f2821SHans Verkuil 
406*056f2821SHans Verkuil 		p->splitter->request_current_latency_dest =
407*056f2821SHans Verkuil 			cec_msg_initiator(msg);
408*056f2821SHans Verkuil 		cec_ops_request_current_latency(msg, &pa);
409*056f2821SHans Verkuil 		if (pa == p->adap->phys_addr &&
410*056f2821SHans Verkuil 		    cec_out_request_current_latency(p->splitter))
411*056f2821SHans Verkuil 			cec_feature_abort_reason(p->adap, msg,
412*056f2821SHans Verkuil 						 CEC_OP_ABORT_INCORRECT_MODE);
413*056f2821SHans Verkuil 		return 0;
414*056f2821SHans Verkuil 	}
415*056f2821SHans Verkuil 
416*056f2821SHans Verkuil 	default:
417*056f2821SHans Verkuil 		return -ENOMSG;
418*056f2821SHans Verkuil 	}
419*056f2821SHans Verkuil 	return -ENOMSG;
420*056f2821SHans Verkuil }
421*056f2821SHans Verkuil 
cec_splitter_nb_transmit_canceled_output(struct cec_splitter_port * p,const struct cec_msg * msg,struct cec_adapter * input_adap)422*056f2821SHans Verkuil void cec_splitter_nb_transmit_canceled_output(struct cec_splitter_port *p,
423*056f2821SHans Verkuil 					      const struct cec_msg *msg,
424*056f2821SHans Verkuil 					      struct cec_adapter *input_adap)
425*056f2821SHans Verkuil {
426*056f2821SHans Verkuil 	struct cec_splitter *splitter = p->splitter;
427*056f2821SHans Verkuil 	u32 seq = msg->sequence | (1U << 31);
428*056f2821SHans Verkuil 
429*056f2821SHans Verkuil 	/*
430*056f2821SHans Verkuil 	 * If this is the result of a failed non-blocking transmit, or it is
431*056f2821SHans Verkuil 	 * the result of the failed reply to a non-blocking transmit, then
432*056f2821SHans Verkuil 	 * check if the original transmit was to get the current power status
433*056f2821SHans Verkuil 	 * or latency and, if so, assume that the remove device is for one
434*056f2821SHans Verkuil 	 * reason or another unavailable and assume that it is in the same
435*056f2821SHans Verkuil 	 * power status as the splitter, or has no video latency.
436*056f2821SHans Verkuil 	 */
437*056f2821SHans Verkuil 	if ((cec_msg_recv_is_tx_result(msg) && !(msg->tx_status & CEC_TX_STATUS_OK)) ||
438*056f2821SHans Verkuil 	    (cec_msg_recv_is_rx_result(msg) && !(msg->rx_status & CEC_RX_STATUS_OK))) {
439*056f2821SHans Verkuil 		u8 tx_op = msg->msg[1];
440*056f2821SHans Verkuil 
441*056f2821SHans Verkuil 		if (msg->len < 2)
442*056f2821SHans Verkuil 			return;
443*056f2821SHans Verkuil 		if (cec_msg_recv_is_rx_result(msg) &&
444*056f2821SHans Verkuil 		    (msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT))
445*056f2821SHans Verkuil 			tx_op = msg->msg[2];
446*056f2821SHans Verkuil 		switch (tx_op) {
447*056f2821SHans Verkuil 		case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
448*056f2821SHans Verkuil 			if (p->out_give_device_power_status_seq != seq)
449*056f2821SHans Verkuil 				break;
450*056f2821SHans Verkuil 			p->out_give_device_power_status_seq = 0;
451*056f2821SHans Verkuil 			p->out_give_device_power_status_ts = ktime_set(0, 0);
452*056f2821SHans Verkuil 			p->power_status = splitter->is_standby ?
453*056f2821SHans Verkuil 					  CEC_OP_POWER_STATUS_STANDBY :
454*056f2821SHans Verkuil 					  CEC_OP_POWER_STATUS_ON;
455*056f2821SHans Verkuil 			cec_out_report_power_status(splitter, input_adap);
456*056f2821SHans Verkuil 			break;
457*056f2821SHans Verkuil 		case CEC_MSG_REQUEST_CURRENT_LATENCY:
458*056f2821SHans Verkuil 			if (p->out_request_current_latency_seq != seq)
459*056f2821SHans Verkuil 				break;
460*056f2821SHans Verkuil 			p->video_latency = 1;
461*056f2821SHans Verkuil 			p->out_request_current_latency_seq = 0;
462*056f2821SHans Verkuil 			p->out_request_current_latency_ts = ktime_set(0, 0);
463*056f2821SHans Verkuil 			cec_out_report_current_latency(splitter, input_adap);
464*056f2821SHans Verkuil 			break;
465*056f2821SHans Verkuil 		}
466*056f2821SHans Verkuil 		return;
467*056f2821SHans Verkuil 	}
468*056f2821SHans Verkuil 
469*056f2821SHans Verkuil 	if (cec_msg_recv_is_tx_result(msg)) {
470*056f2821SHans Verkuil 		if (p->out_request_current_latency_seq != seq)
471*056f2821SHans Verkuil 			return;
472*056f2821SHans Verkuil 		p->out_request_current_latency_ts = ns_to_ktime(msg->tx_ts);
473*056f2821SHans Verkuil 		return;
474*056f2821SHans Verkuil 	}
475*056f2821SHans Verkuil }
476*056f2821SHans Verkuil 
477*056f2821SHans Verkuil /*
478*056f2821SHans Verkuil  * CEC messages received on an HDMI output of the splitter
479*056f2821SHans Verkuil  * are processed here.
480*056f2821SHans Verkuil  */
cec_splitter_received_output(struct cec_splitter_port * p,struct cec_msg * msg,struct cec_adapter * input_adap)481*056f2821SHans Verkuil int cec_splitter_received_output(struct cec_splitter_port *p, struct cec_msg *msg,
482*056f2821SHans Verkuil 				 struct cec_adapter *input_adap)
483*056f2821SHans Verkuil {
484*056f2821SHans Verkuil 	struct cec_adapter *adap = p->adap;
485*056f2821SHans Verkuil 	struct cec_splitter *splitter = p->splitter;
486*056f2821SHans Verkuil 	u32 seq = msg->sequence | (1U << 31);
487*056f2821SHans Verkuil 	struct cec_msg reply = {};
488*056f2821SHans Verkuil 	u16 pa;
489*056f2821SHans Verkuil 
490*056f2821SHans Verkuil 	if (!adap->is_configured || msg->len < 2)
491*056f2821SHans Verkuil 		return -ENOMSG;
492*056f2821SHans Verkuil 
493*056f2821SHans Verkuil 	switch (msg->msg[1]) {
494*056f2821SHans Verkuil 	case CEC_MSG_REPORT_POWER_STATUS: {
495*056f2821SHans Verkuil 		u8 pwr;
496*056f2821SHans Verkuil 
497*056f2821SHans Verkuil 		cec_ops_report_power_status(msg, &pwr);
498*056f2821SHans Verkuil 		if (pwr > CEC_OP_POWER_STATUS_TO_STANDBY)
499*056f2821SHans Verkuil 			pwr = splitter->is_standby ?
500*056f2821SHans Verkuil 				CEC_OP_POWER_STATUS_TO_STANDBY :
501*056f2821SHans Verkuil 				CEC_OP_POWER_STATUS_TO_ON;
502*056f2821SHans Verkuil 		p->power_status = pwr;
503*056f2821SHans Verkuil 		if (p->out_give_device_power_status_seq == seq) {
504*056f2821SHans Verkuil 			p->out_give_device_power_status_seq = 0;
505*056f2821SHans Verkuil 			p->out_give_device_power_status_ts = ktime_set(0, 0);
506*056f2821SHans Verkuil 		}
507*056f2821SHans Verkuil 		cec_out_report_power_status(splitter, input_adap);
508*056f2821SHans Verkuil 		return 0;
509*056f2821SHans Verkuil 	}
510*056f2821SHans Verkuil 
511*056f2821SHans Verkuil 	case CEC_MSG_REPORT_CURRENT_LATENCY: {
512*056f2821SHans Verkuil 		u8 video_lat;
513*056f2821SHans Verkuil 		u8 low_lat_mode;
514*056f2821SHans Verkuil 		u8 audio_out_comp;
515*056f2821SHans Verkuil 		u8 audio_out_delay;
516*056f2821SHans Verkuil 
517*056f2821SHans Verkuil 		cec_ops_report_current_latency(msg, &pa,
518*056f2821SHans Verkuil 					       &video_lat, &low_lat_mode,
519*056f2821SHans Verkuil 					       &audio_out_comp, &audio_out_delay);
520*056f2821SHans Verkuil 		if (!video_lat || video_lat >= 252)
521*056f2821SHans Verkuil 			video_lat = 1;
522*056f2821SHans Verkuil 		p->video_latency = video_lat;
523*056f2821SHans Verkuil 		if (p->out_request_current_latency_seq == seq) {
524*056f2821SHans Verkuil 			p->out_request_current_latency_seq = 0;
525*056f2821SHans Verkuil 			p->out_request_current_latency_ts = ktime_set(0, 0);
526*056f2821SHans Verkuil 		}
527*056f2821SHans Verkuil 		cec_out_report_current_latency(splitter, input_adap);
528*056f2821SHans Verkuil 		return 0;
529*056f2821SHans Verkuil 	}
530*056f2821SHans Verkuil 
531*056f2821SHans Verkuil 	case CEC_MSG_STANDBY:
532*056f2821SHans Verkuil 	case CEC_MSG_ROUTING_CHANGE:
533*056f2821SHans Verkuil 	case CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS:
534*056f2821SHans Verkuil 		return 0;
535*056f2821SHans Verkuil 
536*056f2821SHans Verkuil 	case CEC_MSG_ACTIVE_SOURCE:
537*056f2821SHans Verkuil 		cec_ops_active_source(msg, &pa);
538*056f2821SHans Verkuil 		if (pa == 0)
539*056f2821SHans Verkuil 			p->is_active_source = false;
540*056f2821SHans Verkuil 		return 0;
541*056f2821SHans Verkuil 
542*056f2821SHans Verkuil 	case CEC_MSG_REQUEST_ACTIVE_SOURCE:
543*056f2821SHans Verkuil 		if (!p->is_active_source)
544*056f2821SHans Verkuil 			return 0;
545*056f2821SHans Verkuil 		cec_msg_set_reply_to(&reply, msg);
546*056f2821SHans Verkuil 		cec_msg_active_source(&reply, adap->phys_addr);
547*056f2821SHans Verkuil 		cec_transmit_msg(adap, &reply, false);
548*056f2821SHans Verkuil 		return 0;
549*056f2821SHans Verkuil 
550*056f2821SHans Verkuil 	case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
551*056f2821SHans Verkuil 		cec_msg_set_reply_to(&reply, msg);
552*056f2821SHans Verkuil 		cec_msg_report_power_status(&reply, splitter->is_standby ?
553*056f2821SHans Verkuil 					    CEC_OP_POWER_STATUS_STANDBY :
554*056f2821SHans Verkuil 					    CEC_OP_POWER_STATUS_ON);
555*056f2821SHans Verkuil 		cec_transmit_msg(adap, &reply, false);
556*056f2821SHans Verkuil 		return 0;
557*056f2821SHans Verkuil 
558*056f2821SHans Verkuil 	case CEC_MSG_SET_STREAM_PATH:
559*056f2821SHans Verkuil 		cec_ops_set_stream_path(msg, &pa);
560*056f2821SHans Verkuil 		if (pa == adap->phys_addr) {
561*056f2821SHans Verkuil 			cec_msg_set_reply_to(&reply, msg);
562*056f2821SHans Verkuil 			cec_msg_active_source(&reply, pa);
563*056f2821SHans Verkuil 			cec_transmit_msg(adap, &reply, false);
564*056f2821SHans Verkuil 		}
565*056f2821SHans Verkuil 		return 0;
566*056f2821SHans Verkuil 
567*056f2821SHans Verkuil 	default:
568*056f2821SHans Verkuil 		return -ENOMSG;
569*056f2821SHans Verkuil 	}
570*056f2821SHans Verkuil 	return -ENOMSG;
571*056f2821SHans Verkuil }
572*056f2821SHans Verkuil 
573*056f2821SHans Verkuil /*
574*056f2821SHans Verkuil  * Called every second to check for timed out messages and whether there
575*056f2821SHans Verkuil  * still is a video sink connected or not.
576*056f2821SHans Verkuil  *
577*056f2821SHans Verkuil  * Returns true if sinks were lost.
578*056f2821SHans Verkuil  */
cec_splitter_poll(struct cec_splitter * splitter,struct cec_adapter * input_adap,bool debug)579*056f2821SHans Verkuil bool cec_splitter_poll(struct cec_splitter *splitter,
580*056f2821SHans Verkuil 		       struct cec_adapter *input_adap, bool debug)
581*056f2821SHans Verkuil {
582*056f2821SHans Verkuil 	ktime_t now = ktime_get();
583*056f2821SHans Verkuil 	u8 pwr = splitter->is_standby ?
584*056f2821SHans Verkuil 		 CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
585*056f2821SHans Verkuil 	unsigned int max_delay_ms = input_adap->xfer_timeout_ms + 2000;
586*056f2821SHans Verkuil 	unsigned int i;
587*056f2821SHans Verkuil 	bool res = false;
588*056f2821SHans Verkuil 
589*056f2821SHans Verkuil 	for (i = 0; i < splitter->num_out_ports; i++) {
590*056f2821SHans Verkuil 		struct cec_splitter_port *p = splitter->ports[i];
591*056f2821SHans Verkuil 		s64 pwr_delta, lat_delta;
592*056f2821SHans Verkuil 		bool pwr_timeout, lat_timeout;
593*056f2821SHans Verkuil 
594*056f2821SHans Verkuil 		if (!p)
595*056f2821SHans Verkuil 			continue;
596*056f2821SHans Verkuil 
597*056f2821SHans Verkuil 		pwr_delta = ktime_ms_delta(now, p->out_give_device_power_status_ts);
598*056f2821SHans Verkuil 		pwr_timeout = p->out_give_device_power_status_seq &&
599*056f2821SHans Verkuil 			      pwr_delta >= max_delay_ms;
600*056f2821SHans Verkuil 		lat_delta = ktime_ms_delta(now, p->out_request_current_latency_ts);
601*056f2821SHans Verkuil 		lat_timeout = p->out_request_current_latency_seq &&
602*056f2821SHans Verkuil 			      lat_delta >= max_delay_ms;
603*056f2821SHans Verkuil 
604*056f2821SHans Verkuil 		/*
605*056f2821SHans Verkuil 		 * If the HPD is low for more than 5 seconds, then assume no display
606*056f2821SHans Verkuil 		 * is connected.
607*056f2821SHans Verkuil 		 */
608*056f2821SHans Verkuil 		if (p->found_sink && ktime_to_ns(p->lost_sink_ts) &&
609*056f2821SHans Verkuil 		    ktime_ms_delta(now, p->lost_sink_ts) > 5000) {
610*056f2821SHans Verkuil 			if (debug)
611*056f2821SHans Verkuil 				dev_info(splitter->dev,
612*056f2821SHans Verkuil 					 "port %u: HPD low for more than 5s, assume no sink is connected.\n",
613*056f2821SHans Verkuil 					 p->port);
614*056f2821SHans Verkuil 			p->found_sink = false;
615*056f2821SHans Verkuil 			p->lost_sink_ts = ktime_set(0, 0);
616*056f2821SHans Verkuil 			res = true;
617*056f2821SHans Verkuil 		}
618*056f2821SHans Verkuil 
619*056f2821SHans Verkuil 		/*
620*056f2821SHans Verkuil 		 * If the power status request timed out, then set the port's
621*056f2821SHans Verkuil 		 * power status to that of the splitter, ensuring a consistent
622*056f2821SHans Verkuil 		 * power state.
623*056f2821SHans Verkuil 		 */
624*056f2821SHans Verkuil 		if (pwr_timeout) {
625*056f2821SHans Verkuil 			mutex_lock(&p->adap->lock);
626*056f2821SHans Verkuil 			if (debug)
627*056f2821SHans Verkuil 				dev_info(splitter->dev,
628*056f2821SHans Verkuil 					 "port %u: give up on power status for seq %u\n",
629*056f2821SHans Verkuil 					 p->port,
630*056f2821SHans Verkuil 					 p->out_give_device_power_status_seq & ~(1 << 31));
631*056f2821SHans Verkuil 			p->power_status = pwr;
632*056f2821SHans Verkuil 			p->out_give_device_power_status_seq = 0;
633*056f2821SHans Verkuil 			p->out_give_device_power_status_ts = ktime_set(0, 0);
634*056f2821SHans Verkuil 			mutex_unlock(&p->adap->lock);
635*056f2821SHans Verkuil 			cec_out_report_power_status(splitter, input_adap);
636*056f2821SHans Verkuil 		}
637*056f2821SHans Verkuil 
638*056f2821SHans Verkuil 		/*
639*056f2821SHans Verkuil 		 * If the current latency request timed out, then set the port's
640*056f2821SHans Verkuil 		 * latency to 1.
641*056f2821SHans Verkuil 		 */
642*056f2821SHans Verkuil 		if (lat_timeout) {
643*056f2821SHans Verkuil 			mutex_lock(&p->adap->lock);
644*056f2821SHans Verkuil 			if (debug)
645*056f2821SHans Verkuil 				dev_info(splitter->dev,
646*056f2821SHans Verkuil 					 "port %u: give up on latency for seq %u\n",
647*056f2821SHans Verkuil 					 p->port,
648*056f2821SHans Verkuil 					 p->out_request_current_latency_seq & ~(1 << 31));
649*056f2821SHans Verkuil 			p->video_latency = 1;
650*056f2821SHans Verkuil 			p->out_request_current_latency_seq = 0;
651*056f2821SHans Verkuil 			p->out_request_current_latency_ts = ktime_set(0, 0);
652*056f2821SHans Verkuil 			mutex_unlock(&p->adap->lock);
653*056f2821SHans Verkuil 			cec_out_report_current_latency(splitter, input_adap);
654*056f2821SHans Verkuil 		}
655*056f2821SHans Verkuil 	}
656*056f2821SHans Verkuil 	return res;
657*056f2821SHans Verkuil }
658