diff options
Diffstat (limited to 'drivers/media/test-drivers/vivid/vivid-cec.c')
-rw-r--r-- | drivers/media/test-drivers/vivid/vivid-cec.c | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/drivers/media/test-drivers/vivid/vivid-cec.c b/drivers/media/test-drivers/vivid/vivid-cec.c new file mode 100644 index 000000000..1f7469ff0 --- /dev/null +++ b/drivers/media/test-drivers/vivid/vivid-cec.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vivid-cec.c - A Virtual Video Test Driver, cec emulation + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +#include <linux/delay.h> +#include <media/cec.h> + +#include "vivid-core.h" +#include "vivid-cec.h" + +#define CEC_START_BIT_US 4500 +#define CEC_DATA_BIT_US 2400 +#define CEC_MARGIN_US 350 + +struct xfer_on_bus { + struct cec_adapter *adap; + u8 status; +}; + +static bool find_dest_adap(struct vivid_dev *dev, + struct cec_adapter *adap, u8 dest) +{ + unsigned int i; + + if (dest >= 0xf) + return false; + + if (adap != dev->cec_rx_adap && dev->cec_rx_adap && + dev->cec_rx_adap->is_configured && + cec_has_log_addr(dev->cec_rx_adap, dest)) + return true; + + for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) { + if (adap == dev->cec_tx_adap[i]) + continue; + if (!dev->cec_tx_adap[i]->is_configured) + continue; + if (cec_has_log_addr(dev->cec_tx_adap[i], dest)) + return true; + } + return false; +} + +static bool xfer_ready(struct vivid_dev *dev) +{ + unsigned int i; + bool ready = false; + + spin_lock(&dev->cec_xfers_slock); + for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { + if (dev->xfers[i].sft && + dev->xfers[i].sft <= dev->cec_sft) { + ready = true; + break; + } + } + spin_unlock(&dev->cec_xfers_slock); + + return ready; +} + +/* + * If an adapter tries to send successive messages, it must wait for the + * longest signal-free time between its transmissions. But, if another + * adapter sends a message in the interim, then the wait can be reduced + * because the messages are no longer successive. Make these adjustments + * if necessary. Should be called holding cec_xfers_slock. + */ +static void adjust_sfts(struct vivid_dev *dev) +{ + unsigned int i; + u8 initiator; + + for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { + if (dev->xfers[i].sft <= CEC_SIGNAL_FREE_TIME_RETRY) + continue; + initiator = dev->xfers[i].msg[0] >> 4; + if (initiator == dev->last_initiator) + dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; + else + dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; + } +} + +/* + * The main emulation of the bus on which CEC adapters attempt to send + * messages to each other. The bus keeps track of how long it has been + * signal-free and accepts a pending transmission only if the state of + * the bus matches the transmission's signal-free requirements. It calls + * cec_transmit_attempt_done() for all transmits that enter the bus and + * cec_received_msg() for successful transmits. + */ +int vivid_cec_bus_thread(void *_dev) +{ + u32 last_sft; + unsigned int i; + unsigned int dest; + ktime_t start, end; + s64 delta_us, retry_us; + struct vivid_dev *dev = _dev; + + dev->cec_sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; + for (;;) { + bool first = true; + int wait_xfer_us = 0; + bool valid_dest = false; + int wait_arb_lost_us = 0; + unsigned int first_idx = 0; + unsigned int first_status = 0; + struct cec_msg first_msg = {}; + struct xfer_on_bus xfers_on_bus[MAX_OUTPUTS] = {}; + + wait_event_interruptible(dev->kthread_waitq_cec, xfer_ready(dev) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + last_sft = dev->cec_sft; + dev->cec_sft = 0; + /* + * Move the messages that are ready onto the bus. The adapter with + * the most leading zeros will win control of the bus and any other + * adapters will lose arbitration. + */ + spin_lock(&dev->cec_xfers_slock); + for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { + if (!dev->xfers[i].sft || dev->xfers[i].sft > last_sft) + continue; + if (first) { + first = false; + first_idx = i; + xfers_on_bus[first_idx].adap = dev->xfers[i].adap; + memcpy(first_msg.msg, dev->xfers[i].msg, dev->xfers[i].len); + first_msg.len = dev->xfers[i].len; + } else { + xfers_on_bus[i].adap = dev->xfers[i].adap; + xfers_on_bus[i].status = CEC_TX_STATUS_ARB_LOST; + /* + * For simplicity wait for all 4 bits of the initiator's + * address even though HDMI specification uses bit-level + * precision. + */ + wait_arb_lost_us = 4 * CEC_DATA_BIT_US + CEC_START_BIT_US; + } + dev->xfers[i].sft = 0; + } + dev->last_initiator = cec_msg_initiator(&first_msg); + adjust_sfts(dev); + spin_unlock(&dev->cec_xfers_slock); + + dest = cec_msg_destination(&first_msg); + valid_dest = cec_msg_is_broadcast(&first_msg); + if (!valid_dest) + valid_dest = find_dest_adap(dev, xfers_on_bus[first_idx].adap, dest); + if (valid_dest) { + first_status = CEC_TX_STATUS_OK; + /* + * Message length is in bytes, but each byte is transmitted in + * a block of 10 bits. + */ + wait_xfer_us = first_msg.len * 10 * CEC_DATA_BIT_US; + } else { + first_status = CEC_TX_STATUS_NACK; + /* + * A message that is not acknowledged stops transmitting after + * the header block of 10 bits. + */ + wait_xfer_us = 10 * CEC_DATA_BIT_US; + } + wait_xfer_us += CEC_START_BIT_US; + xfers_on_bus[first_idx].status = first_status; + + /* Sleep as if sending messages on a real hardware bus. */ + start = ktime_get(); + if (wait_arb_lost_us) { + usleep_range(wait_arb_lost_us - CEC_MARGIN_US, wait_arb_lost_us); + for (i = 0; i < ARRAY_SIZE(xfers_on_bus); i++) { + if (xfers_on_bus[i].status != CEC_TX_STATUS_ARB_LOST) + continue; + cec_transmit_attempt_done(xfers_on_bus[i].adap, + CEC_TX_STATUS_ARB_LOST); + } + if (kthread_should_stop()) + break; + } + wait_xfer_us -= wait_arb_lost_us; + usleep_range(wait_xfer_us - CEC_MARGIN_US, wait_xfer_us); + cec_transmit_attempt_done(xfers_on_bus[first_idx].adap, first_status); + if (kthread_should_stop()) + break; + if (first_status == CEC_TX_STATUS_OK) { + if (xfers_on_bus[first_idx].adap != dev->cec_rx_adap) + cec_received_msg(dev->cec_rx_adap, &first_msg); + for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) + if (xfers_on_bus[first_idx].adap != dev->cec_tx_adap[i]) + cec_received_msg(dev->cec_tx_adap[i], &first_msg); + } + end = ktime_get(); + /* + * If the emulated transfer took more or less time than it should + * have, then compensate by adjusting the wait time needed for the + * bus to be signal-free for 3 bit periods (the retry time). + */ + delta_us = div_s64(end - start, 1000); + delta_us -= wait_xfer_us + wait_arb_lost_us; + retry_us = CEC_SIGNAL_FREE_TIME_RETRY * CEC_DATA_BIT_US - delta_us; + if (retry_us > CEC_MARGIN_US) + usleep_range(retry_us - CEC_MARGIN_US, retry_us); + dev->cec_sft = CEC_SIGNAL_FREE_TIME_RETRY; + /* + * If there are no messages that need to be retried, check if any + * adapters that did not just transmit a message are ready to + * transmit. If none of these adapters are ready, then increase + * the signal-free time so that the bus is available to all + * adapters and go back to waiting for a transmission. + */ + while (dev->cec_sft >= CEC_SIGNAL_FREE_TIME_RETRY && + dev->cec_sft < CEC_SIGNAL_FREE_TIME_NEXT_XFER && + !xfer_ready(dev) && !kthread_should_stop()) { + usleep_range(2 * CEC_DATA_BIT_US - CEC_MARGIN_US, + 2 * CEC_DATA_BIT_US); + dev->cec_sft += 2; + } + } + return 0; +} + +static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + adap->cec_pin_is_high = true; + return 0; +} + +static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + return 0; +} + +static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct vivid_dev *dev = cec_get_drvdata(adap); + u8 idx = cec_msg_initiator(msg); + + spin_lock(&dev->cec_xfers_slock); + dev->xfers[idx].adap = adap; + memcpy(dev->xfers[idx].msg, msg->msg, CEC_MAX_MSG_SIZE); + dev->xfers[idx].len = msg->len; + dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_RETRY; + if (signal_free_time > CEC_SIGNAL_FREE_TIME_RETRY) { + if (idx == dev->last_initiator) + dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; + else + dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; + } + spin_unlock(&dev->cec_xfers_slock); + wake_up_interruptible(&dev->kthread_waitq_cec); + + return 0; +} + +static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg) +{ + struct vivid_dev *dev = cec_get_drvdata(adap); + struct cec_msg reply; + u8 dest = cec_msg_destination(msg); + u8 disp_ctl; + char osd[14]; + + if (cec_msg_is_broadcast(msg)) + dest = adap->log_addrs.log_addr[0]; + cec_msg_init(&reply, dest, cec_msg_initiator(msg)); + + switch (cec_msg_opcode(msg)) { + case CEC_MSG_SET_OSD_STRING: + if (!cec_is_sink(adap)) + return -ENOMSG; + cec_ops_set_osd_string(msg, &disp_ctl, osd); + switch (disp_ctl) { + case CEC_OP_DISP_CTL_DEFAULT: + strscpy(dev->osd, osd, sizeof(dev->osd)); + dev->osd_jiffies = jiffies; + break; + case CEC_OP_DISP_CTL_UNTIL_CLEARED: + strscpy(dev->osd, osd, sizeof(dev->osd)); + dev->osd_jiffies = 0; + break; + case CEC_OP_DISP_CTL_CLEAR: + dev->osd[0] = 0; + dev->osd_jiffies = 0; + break; + default: + cec_msg_feature_abort(&reply, cec_msg_opcode(msg), + CEC_OP_ABORT_INVALID_OP); + cec_transmit_msg(adap, &reply, false); + break; + } + break; + default: + return -ENOMSG; + } + return 0; +} + +static const struct cec_adap_ops vivid_cec_adap_ops = { + .adap_enable = vivid_cec_adap_enable, + .adap_log_addr = vivid_cec_adap_log_addr, + .adap_transmit = vivid_cec_adap_transmit, + .received = vivid_received, +}; + +struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev, + unsigned int idx, + bool is_source) +{ + u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN; + char name[32]; + + snprintf(name, sizeof(name), "vivid-%03d-vid-%s%d", + dev->inst, is_source ? "out" : "cap", idx); + return cec_allocate_adapter(&vivid_cec_adap_ops, dev, + name, caps, CEC_MAX_LOG_ADDRS); +} |