Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/display: add CEC helpers code

Add generic CEC helpers to be used by HDMI drivers. Both notifier and
and adapter are supported for registration. Once registered, the driver
can call common set of functions to update physical address, to
invalidate it or to unregister CEC data. Unlike drm_connector_cec_funcs
(which provides interface common to all implementations, including, but
not limited to the CEC adapter, CEC notifier, CEC pin-based adapter,
etc) the struct drm_connector_hdmi_cec_adapter_ops provides callbacks
specific to the CEC adapter implementations.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Link: https://lore.kernel.org/r/20250517-drm-hdmi-connector-cec-v6-5-35651db6f19b@oss.qualcomm.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>

+343 -1
+11 -1
drivers/gpu/drm/display/Kconfig
··· 8 8 config DRM_DISPLAY_HELPER 9 9 tristate 10 10 depends on DRM 11 - select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC 11 + select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC || DRM_DISPLAY_HDMI_CEC_HELPER || CEC_NOTIFIER 12 12 help 13 13 DRM helpers for display adapters. 14 14 ··· 81 81 help 82 82 DRM display helpers for HDMI Audio functionality (generic HDMI Codec 83 83 implementation). 84 + 85 + config DRM_DISPLAY_HDMI_CEC_HELPER 86 + bool 87 + help 88 + DRM display helpers for HDMI CEC implementation. 89 + 90 + config DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER 91 + def_bool CEC_NOTIFIER 92 + help 93 + DRM display helpers for HDMI CEC notifiers implementation. 84 94 85 95 config DRM_DISPLAY_HDMI_HELPER 86 96 bool
+4
drivers/gpu/drm/display/Makefile
··· 16 16 drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o 17 17 drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER) += \ 18 18 drm_hdmi_audio_helper.o 19 + drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER) += \ 20 + drm_hdmi_cec_helper.o 21 + drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER) += \ 22 + drm_hdmi_cec_notifier_helper.o 19 23 drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \ 20 24 drm_hdmi_helper.o \ 21 25 drm_scdc_helper.o
+192
drivers/gpu/drm/display/drm_hdmi_cec_helper.c
··· 1 + // SPDX-License-Identifier: MIT 2 + /* 3 + * Copyright (c) 2024 Linaro Ltd 4 + */ 5 + 6 + #include <drm/drm_bridge.h> 7 + #include <drm/drm_connector.h> 8 + #include <drm/drm_managed.h> 9 + #include <drm/display/drm_hdmi_cec_helper.h> 10 + 11 + #include <linux/mutex.h> 12 + 13 + #include <media/cec.h> 14 + 15 + struct drm_connector_hdmi_cec_data { 16 + struct cec_adapter *adapter; 17 + const struct drm_connector_hdmi_cec_funcs *funcs; 18 + }; 19 + 20 + static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) 21 + { 22 + struct drm_connector *connector = cec_get_drvdata(adap); 23 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 24 + 25 + return data->funcs->enable(connector, enable); 26 + } 27 + 28 + static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) 29 + { 30 + struct drm_connector *connector = cec_get_drvdata(adap); 31 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 32 + 33 + return data->funcs->log_addr(connector, logical_addr); 34 + } 35 + 36 + static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, 37 + u32 signal_free_time, struct cec_msg *msg) 38 + { 39 + struct drm_connector *connector = cec_get_drvdata(adap); 40 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 41 + 42 + return data->funcs->transmit(connector, attempts, signal_free_time, msg); 43 + } 44 + 45 + static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = { 46 + .adap_enable = drm_connector_hdmi_cec_adap_enable, 47 + .adap_log_addr = drm_connector_hdmi_cec_adap_log_addr, 48 + .adap_transmit = drm_connector_hdmi_cec_adap_transmit, 49 + }; 50 + 51 + static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector) 52 + { 53 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 54 + 55 + cec_phys_addr_invalidate(data->adapter); 56 + } 57 + 58 + static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector, 59 + u16 addr) 60 + { 61 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 62 + 63 + cec_s_phys_addr(data->adapter, addr, false); 64 + } 65 + 66 + static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res) 67 + { 68 + struct drm_connector *connector = res; 69 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 70 + 71 + cec_delete_adapter(data->adapter); 72 + 73 + if (data->funcs->uninit) 74 + data->funcs->uninit(connector); 75 + 76 + kfree(data); 77 + connector->cec.data = NULL; 78 + } 79 + 80 + static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = { 81 + .phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate, 82 + .phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set, 83 + }; 84 + 85 + int drmm_connector_hdmi_cec_register(struct drm_connector *connector, 86 + const struct drm_connector_hdmi_cec_funcs *funcs, 87 + const char *name, 88 + u8 available_las, 89 + struct device *dev) 90 + { 91 + struct drm_connector_hdmi_cec_data *data; 92 + struct cec_connector_info conn_info; 93 + struct cec_adapter *cec_adap; 94 + int ret; 95 + 96 + if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit) 97 + return -EINVAL; 98 + 99 + data = kzalloc(sizeof(*data), GFP_KERNEL); 100 + if (!data) 101 + return -ENOMEM; 102 + 103 + data->funcs = funcs; 104 + 105 + cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name, 106 + CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO, 107 + available_las ? : CEC_MAX_LOG_ADDRS); 108 + ret = PTR_ERR_OR_ZERO(cec_adap); 109 + if (ret < 0) 110 + goto err_free; 111 + 112 + cec_fill_conn_info_from_drm(&conn_info, connector); 113 + cec_s_conn_info(cec_adap, &conn_info); 114 + 115 + data->adapter = cec_adap; 116 + 117 + mutex_lock(&connector->cec.mutex); 118 + 119 + connector->cec.data = data; 120 + connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs; 121 + 122 + ret = funcs->init(connector); 123 + if (ret < 0) 124 + goto err_delete_adapter; 125 + 126 + /* 127 + * NOTE: the CEC adapter will be unregistered by drmm cleanup from 128 + * drm_managed_release(), which is called from drm_dev_release() 129 + * during device unbind. 130 + * 131 + * However, the CEC framework cleans up the CEC adapter only when the 132 + * last user has closed its file descriptor, so we don't need to handle 133 + * it in DRM. 134 + * 135 + * Before that CEC framework makes sure that even if the userspace 136 + * still holds CEC device open, all calls will be shortcut via 137 + * cec_is_registered(), making sure that there is no access to the 138 + * freed memory. 139 + */ 140 + ret = cec_register_adapter(cec_adap, dev); 141 + if (ret < 0) 142 + goto err_delete_adapter; 143 + 144 + mutex_unlock(&connector->cec.mutex); 145 + 146 + return drmm_add_action_or_reset(connector->dev, 147 + drm_connector_hdmi_cec_adapter_unregister, 148 + connector); 149 + 150 + err_delete_adapter: 151 + cec_delete_adapter(cec_adap); 152 + 153 + connector->cec.data = NULL; 154 + 155 + mutex_unlock(&connector->cec.mutex); 156 + 157 + err_free: 158 + kfree(data); 159 + 160 + return ret; 161 + } 162 + EXPORT_SYMBOL(drmm_connector_hdmi_cec_register); 163 + 164 + void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector, 165 + struct cec_msg *msg) 166 + { 167 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 168 + 169 + cec_received_msg(data->adapter, msg); 170 + } 171 + EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg); 172 + 173 + void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector, 174 + u8 status) 175 + { 176 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 177 + 178 + cec_transmit_attempt_done(data->adapter, status); 179 + } 180 + EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done); 181 + 182 + void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector, 183 + u8 status, 184 + u8 arb_lost_cnt, u8 nack_cnt, 185 + u8 low_drive_cnt, u8 error_cnt) 186 + { 187 + struct drm_connector_hdmi_cec_data *data = connector->cec.data; 188 + 189 + cec_transmit_done(data->adapter, status, 190 + arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt); 191 + } 192 + EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);
+64
drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c
··· 1 + // SPDX-License-Identifier: MIT 2 + /* 3 + * Copyright (c) 2024 Linaro Ltd 4 + */ 5 + 6 + #include <drm/drm_bridge.h> 7 + #include <drm/drm_connector.h> 8 + #include <drm/drm_managed.h> 9 + #include <drm/display/drm_hdmi_cec_helper.h> 10 + 11 + #include <linux/mutex.h> 12 + 13 + #include <media/cec.h> 14 + #include <media/cec-notifier.h> 15 + 16 + static void drm_connector_hdmi_cec_notifier_phys_addr_invalidate(struct drm_connector *connector) 17 + { 18 + cec_notifier_phys_addr_invalidate(connector->cec.data); 19 + } 20 + 21 + static void drm_connector_hdmi_cec_notifier_phys_addr_set(struct drm_connector *connector, 22 + u16 addr) 23 + { 24 + cec_notifier_set_phys_addr(connector->cec.data, addr); 25 + } 26 + 27 + static void drm_connector_hdmi_cec_notifier_unregister(struct drm_device *dev, void *res) 28 + { 29 + struct drm_connector *connector = res; 30 + 31 + cec_notifier_conn_unregister(connector->cec.data); 32 + connector->cec.data = NULL; 33 + } 34 + 35 + static const struct drm_connector_cec_funcs drm_connector_cec_notifier_funcs = { 36 + .phys_addr_invalidate = drm_connector_hdmi_cec_notifier_phys_addr_invalidate, 37 + .phys_addr_set = drm_connector_hdmi_cec_notifier_phys_addr_set, 38 + }; 39 + 40 + int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector, 41 + const char *port_name, 42 + struct device *dev) 43 + { 44 + struct cec_connector_info conn_info; 45 + struct cec_notifier *notifier; 46 + 47 + cec_fill_conn_info_from_drm(&conn_info, connector); 48 + 49 + notifier = cec_notifier_conn_register(dev, port_name, &conn_info); 50 + if (!notifier) 51 + return -ENOMEM; 52 + 53 + mutex_lock(&connector->cec.mutex); 54 + 55 + connector->cec.data = notifier; 56 + connector->cec.funcs = &drm_connector_cec_notifier_funcs; 57 + 58 + mutex_unlock(&connector->cec.mutex); 59 + 60 + return drmm_add_action_or_reset(connector->dev, 61 + drm_connector_hdmi_cec_notifier_unregister, 62 + connector); 63 + } 64 + EXPORT_SYMBOL(drmm_connector_hdmi_cec_notifier_register);
+72
include/drm/display/drm_hdmi_cec_helper.h
··· 1 + /* SPDX-License-Identifier: MIT */ 2 + 3 + #ifndef DRM_DISPLAY_HDMI_CEC_HELPER 4 + #define DRM_DISPLAY_HDMI_CEC_HELPER 5 + 6 + #include <linux/types.h> 7 + 8 + struct drm_connector; 9 + 10 + struct cec_msg; 11 + struct device; 12 + 13 + struct drm_connector_hdmi_cec_funcs { 14 + /** 15 + * @init: perform hardware-specific initialization before registering the CEC adapter 16 + */ 17 + int (*init)(struct drm_connector *connector); 18 + 19 + /** 20 + * @uninit: perform hardware-specific teardown for the CEC adapter 21 + */ 22 + void (*uninit)(struct drm_connector *connector); 23 + 24 + /** 25 + * @enable: enable or disable CEC adapter 26 + */ 27 + int (*enable)(struct drm_connector *connector, bool enable); 28 + 29 + /** 30 + * @log_addr: set adapter's logical address, can be called multiple 31 + * times if adapter supports several LAs 32 + */ 33 + int (*log_addr)(struct drm_connector *connector, u8 logical_addr); 34 + 35 + /** 36 + * @transmit: start transmission of the specified CEC message 37 + */ 38 + int (*transmit)(struct drm_connector *connector, u8 attempts, 39 + u32 signal_free_time, struct cec_msg *msg); 40 + }; 41 + 42 + int drmm_connector_hdmi_cec_register(struct drm_connector *connector, 43 + const struct drm_connector_hdmi_cec_funcs *funcs, 44 + const char *name, 45 + u8 available_las, 46 + struct device *dev); 47 + 48 + void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector, 49 + struct cec_msg *msg); 50 + 51 + void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector, 52 + u8 status, 53 + u8 arb_lost_cnt, u8 nack_cnt, 54 + u8 low_drive_cnt, u8 error_cnt); 55 + 56 + void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector, 57 + u8 status); 58 + 59 + #if IS_ENABLED(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER) 60 + int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector, 61 + const char *port_name, 62 + struct device *dev); 63 + #else 64 + static inline int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector, 65 + const char *port_name, 66 + struct device *dev) 67 + { 68 + return 0; 69 + } 70 + #endif 71 + 72 + #endif