brintos

brintos / linux-shallow public Read only

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