mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
media: cec: extron-da-hd-4k-plus: add the Extron DA HD 4K Plus CEC driver
Add support for the Extron DA HD 4K Plus series of 4K HDMI Distrubution Amplifiers (aka HDMI Splitters). These devices support CEC and this driver adds support for the CEC protocol for both the input and all outputs (2, 4 or 6 outputs, depending on the model). It also exports the EDID from the outputs and allows reading and setting the EDID of the input. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
This commit is contained in:
parent
6bb8ef90c4
commit
056f2821b6
@ -42,10 +42,14 @@ dongles):
|
||||
``persistent_config``: by default this is off, but when set to 1 the driver
|
||||
will store the current settings to the device's internal eeprom and restore
|
||||
it the next time the device is connected to the USB port.
|
||||
|
||||
- RainShadow Tech. Note: this driver does not support the persistent_config
|
||||
module option of the Pulse-Eight driver. The hardware supports it, but I
|
||||
have no plans to add this feature. But I accept patches :-)
|
||||
|
||||
- Extron DA HD 4K PLUS HDMI Distribution Amplifier. See
|
||||
:ref:`extron_da_hd_4k_plus` for more information.
|
||||
|
||||
Miscellaneous:
|
||||
|
||||
- vivid: emulates a CEC receiver and CEC transmitter.
|
||||
@ -378,3 +382,86 @@ it later using ``--analyze-pin``.
|
||||
|
||||
You can also use this as a full-fledged CEC device by configuring it
|
||||
using ``cec-ctl --tv -p0.0.0.0`` or ``cec-ctl --playback -p1.0.0.0``.
|
||||
|
||||
.. _extron_da_hd_4k_plus:
|
||||
|
||||
Extron DA HD 4K PLUS CEC Adapter driver
|
||||
=======================================
|
||||
|
||||
This driver is for the Extron DA HD 4K PLUS series of HDMI Distribution
|
||||
Amplifiers: https://www.extron.com/product/dahd4kplusseries
|
||||
|
||||
The 2, 4 and 6 port models are supported.
|
||||
|
||||
Firmware version 1.02.0001 or higher is required.
|
||||
|
||||
Note that older Extron hardware revisions have a problem with the CEC voltage,
|
||||
which may mean that CEC will not work. This is fixed in hardware revisions
|
||||
E34814 and up.
|
||||
|
||||
The CEC support has two modes: the first is a manual mode where userspace has
|
||||
to manually control CEC for the HDMI Input and all HDMI Outputs. While this gives
|
||||
full control, it is also complicated.
|
||||
|
||||
The second mode is an automatic mode, which is selected if the module option
|
||||
``vendor_id`` is set. In that case the driver controls CEC and CEC messages
|
||||
received in the input will be distributed to the outputs. It is still possible
|
||||
to use the /dev/cecX devices to talk to the connected devices directly, but it is
|
||||
the driver that configures everything and deals with things like Hotplug Detect
|
||||
changes.
|
||||
|
||||
The driver also takes care of the EDIDs: /dev/videoX devices are created to
|
||||
read the EDIDs and (for the HDMI Input port) to set the EDID.
|
||||
|
||||
By default userspace is responsible to set the EDID for the HDMI Input
|
||||
according to the EDIDs of the connected displays. But if the ``manufacturer_name``
|
||||
module option is set, then the driver will take care of setting the EDID
|
||||
of the HDMI Input based on the supported resolutions of the connected displays.
|
||||
Currently the driver only supports resolutions 1080p60 and 4kp60: if all connected
|
||||
displays support 4kp60, then it will advertise 4kp60 on the HDMI input, otherwise
|
||||
it will fall back to an EDID that just reports 1080p60.
|
||||
|
||||
The status of the Extron is reported in ``/sys/kernel/debug/cec/cecX/status``.
|
||||
|
||||
The extron-da-hd-4k-plus driver implements the following module options:
|
||||
|
||||
``debug``
|
||||
---------
|
||||
|
||||
If set to 1, then all serial port traffic is shown.
|
||||
|
||||
``vendor_id``
|
||||
-------------
|
||||
|
||||
The CEC Vendor ID to report to connected displays.
|
||||
|
||||
If set, then the driver will take care of distributing CEC messages received
|
||||
on the input to the HDMI outputs. This is done for the following CEC messages:
|
||||
|
||||
- <Standby>
|
||||
- <Image View On> and <Text View On>
|
||||
- <Give Device Power Status>
|
||||
- <Set System Audio Mode>
|
||||
- <Request Current Latency>
|
||||
|
||||
If not set, then userspace is responsible for this, and it will have to
|
||||
configure the CEC devices for HDMI Input and the HDMI Outputs manually.
|
||||
|
||||
``manufacturer_name``
|
||||
---------------------
|
||||
|
||||
A three character manufacturer name that is used in the EDID for the HDMI
|
||||
Input. If not set, then userspace is reponsible for configuring an EDID.
|
||||
If set, then the driver will update the EDID automatically based on the
|
||||
resolutions supported by the connected displays, and it will not be possible
|
||||
anymore to manually set the EDID for the HDMI Input.
|
||||
|
||||
``hpd_never_low``
|
||||
-----------------
|
||||
|
||||
If set, then the Hotplug Detect pin of the HDMI Input will always be high,
|
||||
even if nothing is connected to the HDMI Outputs. If not set (the default)
|
||||
then the Hotplug Detect pin of the HDMI input will go low if all the detected
|
||||
Hotplug Detect pins of the HDMI Outputs are also low.
|
||||
|
||||
This option may be changed dynamically.
|
||||
|
@ -8461,6 +8461,13 @@ F: lib/bootconfig.c
|
||||
F: tools/bootconfig/*
|
||||
F: tools/bootconfig/scripts/*
|
||||
|
||||
EXTRON DA HD 4K PLUS CEC DRIVER
|
||||
M: Hans Verkuil <hverkuil@xs4all.nl>
|
||||
L: linux-media@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://linuxtv.org/media_tree.git
|
||||
F: drivers/media/cec/usb/extron-da-hd-4k-plus/
|
||||
|
||||
EXYNOS DP DRIVER
|
||||
M: Jingoo Han <jingoohan1@gmail.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
|
@ -3,6 +3,7 @@
|
||||
# USB drivers
|
||||
|
||||
if USB_SUPPORT && TTY
|
||||
source "drivers/media/cec/usb/extron-da-hd-4k-plus/Kconfig"
|
||||
source "drivers/media/cec/usb/pulse8/Kconfig"
|
||||
source "drivers/media/cec/usb/rainshadow/Kconfig"
|
||||
endif
|
||||
|
@ -2,5 +2,6 @@
|
||||
#
|
||||
# Makefile for the CEC USB device drivers.
|
||||
#
|
||||
obj-$(CONFIG_USB_EXTRON_DA_HD_4K_PLUS_CEC) += extron-da-hd-4k-plus/
|
||||
obj-$(CONFIG_USB_PULSE8_CEC) += pulse8/
|
||||
obj-$(CONFIG_USB_RAINSHADOW_CEC) += rainshadow/
|
||||
|
14
drivers/media/cec/usb/extron-da-hd-4k-plus/Kconfig
Normal file
14
drivers/media/cec/usb/extron-da-hd-4k-plus/Kconfig
Normal file
@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config USB_EXTRON_DA_HD_4K_PLUS_CEC
|
||||
tristate "Extron DA HD 4K Plus CEC driver"
|
||||
depends on VIDEO_DEV
|
||||
depends on USB
|
||||
depends on USB_ACM
|
||||
select CEC_CORE
|
||||
select SERIO
|
||||
select SERIO_SERPORT
|
||||
help
|
||||
This is a CEC driver for the Extron DA HD 4K Plus HDMI Splitter.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called extron-da-hd-4k-plus-cec.
|
8
drivers/media/cec/usb/extron-da-hd-4k-plus/Makefile
Normal file
8
drivers/media/cec/usb/extron-da-hd-4k-plus/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
extron-da-hd-4k-plus-cec-objs := extron-da-hd-4k-plus.o cec-splitter.o
|
||||
obj-$(CONFIG_USB_EXTRON_DA_HD_4K_PLUS_CEC) := extron-da-hd-4k-plus-cec.o
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KDIR) M=$(shell pwd) modules
|
||||
|
||||
install:
|
||||
$(MAKE) -C $(KDIR) M=$(shell pwd) modules_install
|
657
drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.c
Normal file
657
drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.c
Normal file
@ -0,0 +1,657 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <media/cec.h>
|
||||
|
||||
#include "cec-splitter.h"
|
||||
|
||||
/*
|
||||
* Helper function to reply to a received message with a Feature Abort
|
||||
* message.
|
||||
*/
|
||||
static int cec_feature_abort_reason(struct cec_adapter *adap,
|
||||
struct cec_msg *msg, u8 reason)
|
||||
{
|
||||
struct cec_msg tx_msg = { };
|
||||
|
||||
/*
|
||||
* Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT
|
||||
* message!
|
||||
*/
|
||||
if (msg->msg[1] == CEC_MSG_FEATURE_ABORT)
|
||||
return 0;
|
||||
/* Don't Feature Abort messages from 'Unregistered' */
|
||||
if (cec_msg_initiator(msg) == CEC_LOG_ADDR_UNREGISTERED)
|
||||
return 0;
|
||||
cec_msg_set_reply_to(&tx_msg, msg);
|
||||
cec_msg_feature_abort(&tx_msg, msg->msg[1], reason);
|
||||
return cec_transmit_msg(adap, &tx_msg, false);
|
||||
}
|
||||
|
||||
/* Transmit an Active Source message from this output port to a sink */
|
||||
static void cec_port_out_active_source(struct cec_splitter_port *p)
|
||||
{
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_msg msg;
|
||||
|
||||
if (!adap->is_configured)
|
||||
return;
|
||||
p->is_active_source = true;
|
||||
cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
|
||||
cec_msg_active_source(&msg, adap->phys_addr);
|
||||
cec_transmit_msg(adap, &msg, false);
|
||||
}
|
||||
|
||||
/* Transmit Active Source messages from all output ports to the sinks */
|
||||
static void cec_out_active_source(struct cec_splitter *splitter)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++)
|
||||
cec_port_out_active_source(splitter->ports[i]);
|
||||
}
|
||||
|
||||
/* Transmit a Standby message from this output port to a sink */
|
||||
static void cec_port_out_standby(struct cec_splitter_port *p)
|
||||
{
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_msg msg;
|
||||
|
||||
if (!adap->is_configured)
|
||||
return;
|
||||
cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
|
||||
cec_msg_standby(&msg);
|
||||
cec_transmit_msg(adap, &msg, false);
|
||||
}
|
||||
|
||||
/* Transmit Standby messages from all output ports to the sinks */
|
||||
static void cec_out_standby(struct cec_splitter *splitter)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++)
|
||||
cec_port_out_standby(splitter->ports[i]);
|
||||
}
|
||||
|
||||
/* Transmit an Image/Text View On message from this output port to a sink */
|
||||
static void cec_port_out_wakeup(struct cec_splitter_port *p, u8 opcode)
|
||||
{
|
||||
struct cec_adapter *adap = p->adap;
|
||||
u8 la = adap->log_addrs.log_addr[0];
|
||||
struct cec_msg msg;
|
||||
|
||||
if (la == CEC_LOG_ADDR_INVALID)
|
||||
la = CEC_LOG_ADDR_UNREGISTERED;
|
||||
cec_msg_init(&msg, la, 0);
|
||||
msg.len = 2;
|
||||
msg.msg[1] = opcode;
|
||||
cec_transmit_msg(adap, &msg, false);
|
||||
}
|
||||
|
||||
/* Transmit Image/Text View On messages from all output ports to the sinks */
|
||||
static void cec_out_wakeup(struct cec_splitter *splitter, u8 opcode)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++)
|
||||
cec_port_out_wakeup(splitter->ports[i], opcode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the power state of the unconfigured CEC device to either
|
||||
* Off or On depending on the current state of the splitter.
|
||||
* This keeps the outputs in a consistent state.
|
||||
*/
|
||||
void cec_splitter_unconfigured_output(struct cec_splitter_port *p)
|
||||
{
|
||||
p->video_latency = 1;
|
||||
p->power_status = p->splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
|
||||
|
||||
/* The adapter was unconfigured, so clear the sequence and ts values */
|
||||
p->out_give_device_power_status_seq = 0;
|
||||
p->out_give_device_power_status_ts = ktime_set(0, 0);
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the power state of the newly configured CEC device to either
|
||||
* Off or On depending on the current state of the splitter.
|
||||
* This keeps the outputs in a consistent state.
|
||||
*/
|
||||
void cec_splitter_configured_output(struct cec_splitter_port *p)
|
||||
{
|
||||
p->video_latency = 1;
|
||||
p->power_status = p->splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
|
||||
|
||||
if (p->splitter->is_standby) {
|
||||
/*
|
||||
* Some sinks only obey Standby if it comes from the
|
||||
* active source.
|
||||
*/
|
||||
cec_port_out_active_source(p);
|
||||
cec_port_out_standby(p);
|
||||
} else {
|
||||
cec_port_out_wakeup(p, CEC_MSG_IMAGE_VIEW_ON);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pass the in_msg on to all output ports */
|
||||
static void cec_out_passthrough(struct cec_splitter *splitter,
|
||||
const struct cec_msg *in_msg)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_msg msg;
|
||||
|
||||
if (!adap->is_configured)
|
||||
continue;
|
||||
cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
|
||||
msg.len = in_msg->len;
|
||||
memcpy(msg.msg + 1, in_msg->msg + 1, msg.len - 1);
|
||||
cec_transmit_msg(adap, &msg, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* See if all output ports received the Report Current Latency message,
|
||||
* and if so, transmit the result from the input port to the video source.
|
||||
*/
|
||||
static void cec_out_report_current_latency(struct cec_splitter *splitter,
|
||||
struct cec_adapter *input_adap)
|
||||
{
|
||||
struct cec_msg reply = {};
|
||||
unsigned int reply_lat = 0;
|
||||
unsigned int cnt = 0;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
|
||||
/* Skip unconfigured ports */
|
||||
if (!adap->is_configured)
|
||||
continue;
|
||||
/* Return if a port is still waiting for a reply */
|
||||
if (p->out_request_current_latency_seq)
|
||||
return;
|
||||
reply_lat += p->video_latency - 1;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
/*
|
||||
* All ports that can reply, replied, so clear the sequence
|
||||
* and timestamp values.
|
||||
*/
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return if there were no replies or the input port is no longer
|
||||
* configured.
|
||||
*/
|
||||
if (!cnt || !input_adap->is_configured)
|
||||
return;
|
||||
|
||||
/* Reply with the average latency */
|
||||
reply_lat = 1 + reply_lat / cnt;
|
||||
cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
|
||||
splitter->request_current_latency_dest);
|
||||
cec_msg_report_current_latency(&reply, input_adap->phys_addr,
|
||||
reply_lat, 1, 1, 1);
|
||||
cec_transmit_msg(input_adap, &reply, false);
|
||||
}
|
||||
|
||||
/* Transmit Request Current Latency to all output ports */
|
||||
static int cec_out_request_current_latency(struct cec_splitter *splitter)
|
||||
{
|
||||
ktime_t now = ktime_get();
|
||||
bool error = true;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
|
||||
if (!adap->is_configured) {
|
||||
/* Clear if not configured */
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
} else if (!p->out_request_current_latency_seq) {
|
||||
/*
|
||||
* Keep the old ts if an earlier request is still
|
||||
* pending. This ensures that the request will
|
||||
* eventually time out based on the timestamp of
|
||||
* the first request if the sink is unresponsive.
|
||||
*/
|
||||
p->out_request_current_latency_ts = now;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_msg msg;
|
||||
|
||||
if (!adap->is_configured)
|
||||
continue;
|
||||
cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
|
||||
cec_msg_request_current_latency(&msg, true, adap->phys_addr);
|
||||
if (cec_transmit_msg(adap, &msg, false))
|
||||
continue;
|
||||
p->out_request_current_latency_seq = msg.sequence | (1U << 31);
|
||||
error = false;
|
||||
}
|
||||
return error ? -ENODEV : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if all output ports received the Report Power Status message,
|
||||
* and if so, transmit the result from the input port to the video source.
|
||||
*/
|
||||
static void cec_out_report_power_status(struct cec_splitter *splitter,
|
||||
struct cec_adapter *input_adap)
|
||||
{
|
||||
struct cec_msg reply = {};
|
||||
/* The target power status of the splitter itself */
|
||||
u8 splitter_pwr = splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
|
||||
/*
|
||||
* The transient power status of the splitter, used if not all
|
||||
* output report the target power status.
|
||||
*/
|
||||
u8 splitter_transient_pwr = splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
|
||||
u8 reply_pwr = splitter_pwr;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
|
||||
/* Skip if no sink was found (HPD was low for more than 5s) */
|
||||
if (!p->found_sink)
|
||||
continue;
|
||||
|
||||
/* Return if a port is still waiting for a reply */
|
||||
if (p->out_give_device_power_status_seq)
|
||||
return;
|
||||
if (p->power_status != splitter_pwr)
|
||||
reply_pwr = splitter_transient_pwr;
|
||||
}
|
||||
|
||||
/*
|
||||
* All ports that can reply, replied, so clear the sequence
|
||||
* and timestamp values.
|
||||
*/
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
|
||||
p->out_give_device_power_status_seq = 0;
|
||||
p->out_give_device_power_status_ts = ktime_set(0, 0);
|
||||
}
|
||||
|
||||
/* Return if the input port is no longer configured. */
|
||||
if (!input_adap->is_configured)
|
||||
return;
|
||||
|
||||
/* Reply with the new power status */
|
||||
cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
|
||||
splitter->give_device_power_status_dest);
|
||||
cec_msg_report_power_status(&reply, reply_pwr);
|
||||
cec_transmit_msg(input_adap, &reply, false);
|
||||
}
|
||||
|
||||
/* Transmit Give Device Power Status to all output ports */
|
||||
static int cec_out_give_device_power_status(struct cec_splitter *splitter)
|
||||
{
|
||||
ktime_t now = ktime_get();
|
||||
bool error = true;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
|
||||
/*
|
||||
* Keep the old ts if an earlier request is still
|
||||
* pending. This ensures that the request will
|
||||
* eventually time out based on the timestamp of
|
||||
* the first request if the sink is unresponsive.
|
||||
*/
|
||||
if (adap->is_configured && !p->out_give_device_power_status_seq)
|
||||
p->out_give_device_power_status_ts = now;
|
||||
}
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_msg msg;
|
||||
|
||||
if (!adap->is_configured)
|
||||
continue;
|
||||
|
||||
cec_msg_init(&msg, adap->log_addrs.log_addr[0], 0);
|
||||
cec_msg_give_device_power_status(&msg, true);
|
||||
if (cec_transmit_msg(adap, &msg, false))
|
||||
continue;
|
||||
p->out_give_device_power_status_seq = msg.sequence | (1U << 31);
|
||||
error = false;
|
||||
}
|
||||
return error ? -ENODEV : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* CEC messages received on the HDMI input of the splitter are
|
||||
* forwarded (if relevant) to the HDMI outputs of the splitter.
|
||||
*/
|
||||
int cec_splitter_received_input(struct cec_splitter_port *p, struct cec_msg *msg)
|
||||
{
|
||||
if (!cec_msg_status_is_ok(msg))
|
||||
return 0;
|
||||
|
||||
if (msg->len < 2)
|
||||
return -ENOMSG;
|
||||
|
||||
switch (msg->msg[1]) {
|
||||
case CEC_MSG_DEVICE_VENDOR_ID:
|
||||
case CEC_MSG_REPORT_POWER_STATUS:
|
||||
case CEC_MSG_SET_STREAM_PATH:
|
||||
case CEC_MSG_ROUTING_CHANGE:
|
||||
case CEC_MSG_REQUEST_ACTIVE_SOURCE:
|
||||
case CEC_MSG_SYSTEM_AUDIO_MODE_STATUS:
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_STANDBY:
|
||||
p->splitter->is_standby = true;
|
||||
cec_out_standby(p->splitter);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_IMAGE_VIEW_ON:
|
||||
case CEC_MSG_TEXT_VIEW_ON:
|
||||
p->splitter->is_standby = false;
|
||||
cec_out_wakeup(p->splitter, msg->msg[1]);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_ACTIVE_SOURCE:
|
||||
cec_out_active_source(p->splitter);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_SET_SYSTEM_AUDIO_MODE:
|
||||
cec_out_passthrough(p->splitter, msg);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
|
||||
p->splitter->give_device_power_status_dest =
|
||||
cec_msg_initiator(msg);
|
||||
if (cec_out_give_device_power_status(p->splitter))
|
||||
cec_feature_abort_reason(p->adap, msg,
|
||||
CEC_OP_ABORT_INCORRECT_MODE);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_REQUEST_CURRENT_LATENCY: {
|
||||
u16 pa;
|
||||
|
||||
p->splitter->request_current_latency_dest =
|
||||
cec_msg_initiator(msg);
|
||||
cec_ops_request_current_latency(msg, &pa);
|
||||
if (pa == p->adap->phys_addr &&
|
||||
cec_out_request_current_latency(p->splitter))
|
||||
cec_feature_abort_reason(p->adap, msg,
|
||||
CEC_OP_ABORT_INCORRECT_MODE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
return -ENOMSG;
|
||||
}
|
||||
return -ENOMSG;
|
||||
}
|
||||
|
||||
void cec_splitter_nb_transmit_canceled_output(struct cec_splitter_port *p,
|
||||
const struct cec_msg *msg,
|
||||
struct cec_adapter *input_adap)
|
||||
{
|
||||
struct cec_splitter *splitter = p->splitter;
|
||||
u32 seq = msg->sequence | (1U << 31);
|
||||
|
||||
/*
|
||||
* If this is the result of a failed non-blocking transmit, or it is
|
||||
* the result of the failed reply to a non-blocking transmit, then
|
||||
* check if the original transmit was to get the current power status
|
||||
* or latency and, if so, assume that the remove device is for one
|
||||
* reason or another unavailable and assume that it is in the same
|
||||
* power status as the splitter, or has no video latency.
|
||||
*/
|
||||
if ((cec_msg_recv_is_tx_result(msg) && !(msg->tx_status & CEC_TX_STATUS_OK)) ||
|
||||
(cec_msg_recv_is_rx_result(msg) && !(msg->rx_status & CEC_RX_STATUS_OK))) {
|
||||
u8 tx_op = msg->msg[1];
|
||||
|
||||
if (msg->len < 2)
|
||||
return;
|
||||
if (cec_msg_recv_is_rx_result(msg) &&
|
||||
(msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT))
|
||||
tx_op = msg->msg[2];
|
||||
switch (tx_op) {
|
||||
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
|
||||
if (p->out_give_device_power_status_seq != seq)
|
||||
break;
|
||||
p->out_give_device_power_status_seq = 0;
|
||||
p->out_give_device_power_status_ts = ktime_set(0, 0);
|
||||
p->power_status = splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_STANDBY :
|
||||
CEC_OP_POWER_STATUS_ON;
|
||||
cec_out_report_power_status(splitter, input_adap);
|
||||
break;
|
||||
case CEC_MSG_REQUEST_CURRENT_LATENCY:
|
||||
if (p->out_request_current_latency_seq != seq)
|
||||
break;
|
||||
p->video_latency = 1;
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
cec_out_report_current_latency(splitter, input_adap);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cec_msg_recv_is_tx_result(msg)) {
|
||||
if (p->out_request_current_latency_seq != seq)
|
||||
return;
|
||||
p->out_request_current_latency_ts = ns_to_ktime(msg->tx_ts);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CEC messages received on an HDMI output of the splitter
|
||||
* are processed here.
|
||||
*/
|
||||
int cec_splitter_received_output(struct cec_splitter_port *p, struct cec_msg *msg,
|
||||
struct cec_adapter *input_adap)
|
||||
{
|
||||
struct cec_adapter *adap = p->adap;
|
||||
struct cec_splitter *splitter = p->splitter;
|
||||
u32 seq = msg->sequence | (1U << 31);
|
||||
struct cec_msg reply = {};
|
||||
u16 pa;
|
||||
|
||||
if (!adap->is_configured || msg->len < 2)
|
||||
return -ENOMSG;
|
||||
|
||||
switch (msg->msg[1]) {
|
||||
case CEC_MSG_REPORT_POWER_STATUS: {
|
||||
u8 pwr;
|
||||
|
||||
cec_ops_report_power_status(msg, &pwr);
|
||||
if (pwr > CEC_OP_POWER_STATUS_TO_STANDBY)
|
||||
pwr = splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_TO_STANDBY :
|
||||
CEC_OP_POWER_STATUS_TO_ON;
|
||||
p->power_status = pwr;
|
||||
if (p->out_give_device_power_status_seq == seq) {
|
||||
p->out_give_device_power_status_seq = 0;
|
||||
p->out_give_device_power_status_ts = ktime_set(0, 0);
|
||||
}
|
||||
cec_out_report_power_status(splitter, input_adap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CEC_MSG_REPORT_CURRENT_LATENCY: {
|
||||
u8 video_lat;
|
||||
u8 low_lat_mode;
|
||||
u8 audio_out_comp;
|
||||
u8 audio_out_delay;
|
||||
|
||||
cec_ops_report_current_latency(msg, &pa,
|
||||
&video_lat, &low_lat_mode,
|
||||
&audio_out_comp, &audio_out_delay);
|
||||
if (!video_lat || video_lat >= 252)
|
||||
video_lat = 1;
|
||||
p->video_latency = video_lat;
|
||||
if (p->out_request_current_latency_seq == seq) {
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
}
|
||||
cec_out_report_current_latency(splitter, input_adap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CEC_MSG_STANDBY:
|
||||
case CEC_MSG_ROUTING_CHANGE:
|
||||
case CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS:
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_ACTIVE_SOURCE:
|
||||
cec_ops_active_source(msg, &pa);
|
||||
if (pa == 0)
|
||||
p->is_active_source = false;
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_REQUEST_ACTIVE_SOURCE:
|
||||
if (!p->is_active_source)
|
||||
return 0;
|
||||
cec_msg_set_reply_to(&reply, msg);
|
||||
cec_msg_active_source(&reply, adap->phys_addr);
|
||||
cec_transmit_msg(adap, &reply, false);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
|
||||
cec_msg_set_reply_to(&reply, msg);
|
||||
cec_msg_report_power_status(&reply, splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_STANDBY :
|
||||
CEC_OP_POWER_STATUS_ON);
|
||||
cec_transmit_msg(adap, &reply, false);
|
||||
return 0;
|
||||
|
||||
case CEC_MSG_SET_STREAM_PATH:
|
||||
cec_ops_set_stream_path(msg, &pa);
|
||||
if (pa == adap->phys_addr) {
|
||||
cec_msg_set_reply_to(&reply, msg);
|
||||
cec_msg_active_source(&reply, pa);
|
||||
cec_transmit_msg(adap, &reply, false);
|
||||
}
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -ENOMSG;
|
||||
}
|
||||
return -ENOMSG;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called every second to check for timed out messages and whether there
|
||||
* still is a video sink connected or not.
|
||||
*
|
||||
* Returns true if sinks were lost.
|
||||
*/
|
||||
bool cec_splitter_poll(struct cec_splitter *splitter,
|
||||
struct cec_adapter *input_adap, bool debug)
|
||||
{
|
||||
ktime_t now = ktime_get();
|
||||
u8 pwr = splitter->is_standby ?
|
||||
CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON;
|
||||
unsigned int max_delay_ms = input_adap->xfer_timeout_ms + 2000;
|
||||
unsigned int i;
|
||||
bool res = false;
|
||||
|
||||
for (i = 0; i < splitter->num_out_ports; i++) {
|
||||
struct cec_splitter_port *p = splitter->ports[i];
|
||||
s64 pwr_delta, lat_delta;
|
||||
bool pwr_timeout, lat_timeout;
|
||||
|
||||
if (!p)
|
||||
continue;
|
||||
|
||||
pwr_delta = ktime_ms_delta(now, p->out_give_device_power_status_ts);
|
||||
pwr_timeout = p->out_give_device_power_status_seq &&
|
||||
pwr_delta >= max_delay_ms;
|
||||
lat_delta = ktime_ms_delta(now, p->out_request_current_latency_ts);
|
||||
lat_timeout = p->out_request_current_latency_seq &&
|
||||
lat_delta >= max_delay_ms;
|
||||
|
||||
/*
|
||||
* If the HPD is low for more than 5 seconds, then assume no display
|
||||
* is connected.
|
||||
*/
|
||||
if (p->found_sink && ktime_to_ns(p->lost_sink_ts) &&
|
||||
ktime_ms_delta(now, p->lost_sink_ts) > 5000) {
|
||||
if (debug)
|
||||
dev_info(splitter->dev,
|
||||
"port %u: HPD low for more than 5s, assume no sink is connected.\n",
|
||||
p->port);
|
||||
p->found_sink = false;
|
||||
p->lost_sink_ts = ktime_set(0, 0);
|
||||
res = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the power status request timed out, then set the port's
|
||||
* power status to that of the splitter, ensuring a consistent
|
||||
* power state.
|
||||
*/
|
||||
if (pwr_timeout) {
|
||||
mutex_lock(&p->adap->lock);
|
||||
if (debug)
|
||||
dev_info(splitter->dev,
|
||||
"port %u: give up on power status for seq %u\n",
|
||||
p->port,
|
||||
p->out_give_device_power_status_seq & ~(1 << 31));
|
||||
p->power_status = pwr;
|
||||
p->out_give_device_power_status_seq = 0;
|
||||
p->out_give_device_power_status_ts = ktime_set(0, 0);
|
||||
mutex_unlock(&p->adap->lock);
|
||||
cec_out_report_power_status(splitter, input_adap);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the current latency request timed out, then set the port's
|
||||
* latency to 1.
|
||||
*/
|
||||
if (lat_timeout) {
|
||||
mutex_lock(&p->adap->lock);
|
||||
if (debug)
|
||||
dev_info(splitter->dev,
|
||||
"port %u: give up on latency for seq %u\n",
|
||||
p->port,
|
||||
p->out_request_current_latency_seq & ~(1 << 31));
|
||||
p->video_latency = 1;
|
||||
p->out_request_current_latency_seq = 0;
|
||||
p->out_request_current_latency_ts = ktime_set(0, 0);
|
||||
mutex_unlock(&p->adap->lock);
|
||||
cec_out_report_current_latency(splitter, input_adap);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
51
drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.h
Normal file
51
drivers/media/cec/usb/extron-da-hd-4k-plus/cec-splitter.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
/*
|
||||
* Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CEC_SPLITTER_H_
|
||||
#define _CEC_SPLITTER_H_
|
||||
|
||||
struct cec_splitter;
|
||||
|
||||
#define STATE_CHANGE_MAX_REPEATS 2
|
||||
|
||||
struct cec_splitter_port {
|
||||
struct cec_splitter *splitter;
|
||||
struct cec_adapter *adap;
|
||||
unsigned int port;
|
||||
bool is_active_source;
|
||||
bool found_sink;
|
||||
ktime_t lost_sink_ts;
|
||||
u32 out_request_current_latency_seq;
|
||||
ktime_t out_request_current_latency_ts;
|
||||
u8 video_latency;
|
||||
u32 out_give_device_power_status_seq;
|
||||
ktime_t out_give_device_power_status_ts;
|
||||
u8 power_status;
|
||||
};
|
||||
|
||||
struct cec_splitter {
|
||||
struct device *dev;
|
||||
unsigned int num_out_ports;
|
||||
struct cec_splitter_port **ports;
|
||||
|
||||
/* High-level splitter state */
|
||||
u8 request_current_latency_dest;
|
||||
u8 give_device_power_status_dest;
|
||||
bool is_standby;
|
||||
};
|
||||
|
||||
void cec_splitter_unconfigured_output(struct cec_splitter_port *port);
|
||||
void cec_splitter_configured_output(struct cec_splitter_port *port);
|
||||
int cec_splitter_received_input(struct cec_splitter_port *port, struct cec_msg *msg);
|
||||
int cec_splitter_received_output(struct cec_splitter_port *port, struct cec_msg *msg,
|
||||
struct cec_adapter *input_adap);
|
||||
void cec_splitter_nb_transmit_canceled_output(struct cec_splitter_port *port,
|
||||
const struct cec_msg *msg,
|
||||
struct cec_adapter *input_adap);
|
||||
bool cec_splitter_poll(struct cec_splitter *splitter,
|
||||
struct cec_adapter *input_adap, bool debug);
|
||||
|
||||
#endif
|
1836
drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c
Normal file
1836
drivers/media/cec/usb/extron-da-hd-4k-plus/extron-da-hd-4k-plus.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,118 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
/*
|
||||
* Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _EXTRON_DA_HD_4K_PLUS_H_
|
||||
#define _EXTRON_DA_HD_4K_PLUS_H_
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <media/cec.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-dev.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-dv-timings.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/v4l2-fh.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
|
||||
#include "cec-splitter.h"
|
||||
|
||||
#define DATA_SIZE 256
|
||||
|
||||
#define PING_PERIOD (15 * HZ)
|
||||
|
||||
#define NUM_MSGS CEC_MAX_MSG_RX_QUEUE_SZ
|
||||
|
||||
#define MAX_PORTS (1 + 6)
|
||||
|
||||
#define MAX_EDID_BLOCKS 2
|
||||
|
||||
struct extron;
|
||||
|
||||
struct extron_port {
|
||||
struct cec_splitter_port port;
|
||||
struct device *dev;
|
||||
struct cec_adapter *adap;
|
||||
struct video_device vdev;
|
||||
struct v4l2_ctrl_handler hdl;
|
||||
struct v4l2_ctrl *ctrl_rx_power_present;
|
||||
struct v4l2_ctrl *ctrl_tx_hotplug;
|
||||
struct v4l2_ctrl *ctrl_tx_edid_present;
|
||||
bool is_input;
|
||||
char direction;
|
||||
char name[26];
|
||||
unsigned char edid[MAX_EDID_BLOCKS * 128];
|
||||
unsigned char edid_tmp[MAX_EDID_BLOCKS * 128];
|
||||
unsigned int edid_blocks;
|
||||
bool read_edid;
|
||||
struct extron *extron;
|
||||
struct work_struct irq_work;
|
||||
struct completion cmd_done;
|
||||
const char *response;
|
||||
unsigned int cmd_error;
|
||||
struct cec_msg rx_msg[NUM_MSGS];
|
||||
unsigned int rx_msg_cur_idx, rx_msg_num;
|
||||
/* protect rx_msg_cur_idx and rx_msg_num */
|
||||
spinlock_t msg_lock;
|
||||
u32 tx_done_status;
|
||||
bool update_phys_addr;
|
||||
u16 phys_addr;
|
||||
bool cec_was_registered;
|
||||
bool disconnected;
|
||||
bool update_has_signal;
|
||||
bool has_signal;
|
||||
bool update_has_edid;
|
||||
bool has_edid;
|
||||
bool has_4kp30;
|
||||
bool has_4kp60;
|
||||
bool has_qy;
|
||||
bool has_qs;
|
||||
u8 est_i, est_ii;
|
||||
|
||||
/* locks access to the video_device */
|
||||
struct mutex video_lock;
|
||||
};
|
||||
|
||||
struct extron {
|
||||
struct cec_splitter splitter;
|
||||
struct device *dev;
|
||||
struct serio *serio;
|
||||
/* locks access to serio */
|
||||
struct mutex serio_lock;
|
||||
unsigned int num_ports;
|
||||
unsigned int num_in_ports;
|
||||
unsigned int num_out_ports;
|
||||
char unit_name[32];
|
||||
char unit_type[64];
|
||||
char unit_fw_version[32];
|
||||
char unit_cec_engine_version[32];
|
||||
struct extron_port *ports[MAX_PORTS];
|
||||
struct cec_splitter_port *splitter_ports[MAX_PORTS];
|
||||
struct v4l2_device v4l2_dev;
|
||||
bool hpd_never_low;
|
||||
struct task_struct *kthread_setup;
|
||||
struct delayed_work work_update_edid;
|
||||
|
||||
/* serializes EDID reading */
|
||||
struct mutex edid_lock;
|
||||
unsigned int edid_bytes_read;
|
||||
struct extron_port *edid_port;
|
||||
struct completion edid_completion;
|
||||
bool edid_reading;
|
||||
bool is_ready;
|
||||
|
||||
struct completion cmd_done;
|
||||
const char *response;
|
||||
unsigned int cmd_error;
|
||||
char data[DATA_SIZE];
|
||||
unsigned int len;
|
||||
char reply[DATA_SIZE];
|
||||
char buf[DATA_SIZE];
|
||||
unsigned int idx;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user