Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
// 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);
}