linux/drivers/gpu/drm/display/drm_dp_cec.c

// SPDX-License-Identifier: GPL-2.0
/*
 * DisplayPort CEC-Tunneling-over-AUX support
 *
 * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>

#include <media/cec.h>

#include <drm/display/drm_dp_helper.h>
#include <drm/drm_connector.h>
#include <drm/drm_device.h>
#include <drm/drm_edid.h>

/*
 * Unfortunately it turns out that we have a chicken-and-egg situation
 * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters
 * have a converter chip that supports CEC-Tunneling-over-AUX (usually the
 * Parade PS176), but they do not wire up the CEC pin, thus making CEC
 * useless. Note that MegaChips 2900-based adapters appear to have good
 * support for CEC tunneling. Those adapters that I have tested using
 * this chipset all have the CEC line connected.
 *
 * Sadly there is no way for this driver to know this. What happens is
 * that a /dev/cecX device is created that is isolated and unable to see
 * any of the other CEC devices. Quite literally the CEC wire is cut
 * (or in this case, never connected in the first place).
 *
 * The reason so few adapters support this is that this tunneling protocol
 * was never supported by any OS. So there was no easy way of testing it,
 * and no incentive to correctly wire up the CEC pin.
 *
 * Hopefully by creating this driver it will be easier for vendors to
 * finally fix their adapters and test the CEC functionality.
 *
 * I keep a list of known working adapters here:
 *
 * https://hverkuil.home.xs4all.nl/cec-status.txt
 *
 * Please mail me ([email protected]) if you find an adapter that works
 * and is not yet listed there.
 *
 * Note that the current implementation does not support CEC over an MST hub.
 * As far as I can see there is no mechanism defined in the DisplayPort
 * standard to transport CEC interrupts over an MST device. It might be
 * possible to do this through polling, but I have not been able to get that
 * to work.
 */

/**
 * DOC: dp cec helpers
 *
 * These functions take care of supporting the CEC-Tunneling-over-AUX
 * feature of DisplayPort-to-HDMI adapters.
 */

/*
 * When the EDID is unset because the HPD went low, then the CEC DPCD registers
 * typically can no longer be read (true for a DP-to-HDMI adapter since it is
 * powered by the HPD). However, some displays toggle the HPD off and on for a
 * short period for one reason or another, and that would cause the CEC adapter
 * to be removed and added again, even though nothing else changed.
 *
 * This module parameter sets a delay in seconds before the CEC adapter is
 * actually unregistered. Only if the HPD does not return within that time will
 * the CEC adapter be unregistered.
 *
 * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never
 * be unregistered for as long as the connector remains registered.
 *
 * If it is set to 0, then the CEC adapter will be unregistered immediately as
 * soon as the HPD disappears.
 *
 * The default is one second to prevent short HPD glitches from unregistering
 * the CEC adapter.
 *
 * Note that for integrated HDMI branch devices that support CEC the DPCD
 * registers remain available even if the HPD goes low since it is not powered
 * by the HPD. In that case the CEC adapter will never be unregistered during
 * the life time of the connector. At least, this is the theory since I do not
 * have hardware with an integrated HDMI branch device that supports CEC.
 */
#define NEVER_UNREG_DELAY
static unsigned int drm_dp_cec_unregister_delay =;
module_param(drm_dp_cec_unregister_delay, uint, 0600);
MODULE_PARM_DESC();

static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable)
{}

static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
{}

static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
				    u32 signal_free_time, struct cec_msg *msg)
{}

static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap,
					      bool enable)
{}

static void drm_dp_cec_adap_status(struct cec_adapter *adap,
				   struct seq_file *file)
{}

static const struct cec_adap_ops drm_dp_cec_adap_ops =;

static int drm_dp_cec_received(struct drm_dp_aux *aux)
{}

static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux)
{}

/**
 * drm_dp_cec_irq() - handle CEC interrupt, if any
 * @aux: DisplayPort AUX channel
 *
 * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX
 * is present, then it will check for a CEC_IRQ and handle it accordingly.
 */
void drm_dp_cec_irq(struct drm_dp_aux *aux)
{}
EXPORT_SYMBOL();

static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap)
{}

/*
 * Called if the HPD was low for more than drm_dp_cec_unregister_delay
 * seconds. This unregisters the CEC adapter.
 */
static void drm_dp_cec_unregister_work(struct work_struct *work)
{}

/*
 * A new EDID is set. If there is no CEC adapter, then create one. If
 * there was a CEC adapter, then check if the CEC adapter properties
 * were unchanged and just update the CEC physical address. Otherwise
 * unregister the old CEC adapter and create a new one.
 */
void drm_dp_cec_attach(struct drm_dp_aux *aux, u16 source_physical_address)
{}
EXPORT_SYMBOL();

/*
 * Note: Prefer calling drm_dp_cec_attach() with
 * connector->display_info.source_physical_address if possible.
 */
void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid)
{}
EXPORT_SYMBOL();

/*
 * The EDID disappeared (likely because of the HPD going down).
 */
void drm_dp_cec_unset_edid(struct drm_dp_aux *aux)
{}
EXPORT_SYMBOL();

/**
 * drm_dp_cec_register_connector() - register a new connector
 * @aux: DisplayPort AUX channel
 * @connector: drm connector
 *
 * A new connector was registered with associated CEC adapter name and
 * CEC adapter parent device. After registering the name and parent
 * drm_dp_cec_set_edid() is called to check if the connector supports
 * CEC and to register a CEC adapter if that is the case.
 */
void drm_dp_cec_register_connector(struct drm_dp_aux *aux,
				   struct drm_connector *connector)
{}
EXPORT_SYMBOL();

/**
 * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any
 * @aux: DisplayPort AUX channel
 */
void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux)
{}
EXPORT_SYMBOL();