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