// 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(…);