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>
This commit is contained in:
Dmitry Baryshkov 2025-05-17 04:59:41 +03:00
parent bcc8553b62
commit 8b1a8f8b20
5 changed files with 343 additions and 1 deletions

View File

@ -8,7 +8,7 @@ config DRM_DISPLAY_DP_AUX_BUS
config DRM_DISPLAY_HELPER
tristate
depends on DRM
select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC
select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC || DRM_DISPLAY_HDMI_CEC_HELPER || CEC_NOTIFIER
help
DRM helpers for display adapters.
@ -82,6 +82,16 @@ config DRM_DISPLAY_HDMI_AUDIO_HELPER
DRM display helpers for HDMI Audio functionality (generic HDMI Codec
implementation).
config DRM_DISPLAY_HDMI_CEC_HELPER
bool
help
DRM display helpers for HDMI CEC implementation.
config DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER
def_bool CEC_NOTIFIER
help
DRM display helpers for HDMI CEC notifiers implementation.
config DRM_DISPLAY_HDMI_HELPER
bool
help

View File

@ -16,6 +16,10 @@ drm_display_helper-$(CONFIG_DRM_DISPLAY_DSC_HELPER) += \
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER) += \
drm_hdmi_audio_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER) += \
drm_hdmi_cec_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER) += \
drm_hdmi_cec_notifier_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \
drm_hdmi_helper.o \
drm_scdc_helper.o

View File

@ -0,0 +1,192 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_managed.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <linux/mutex.h>
#include <media/cec.h>
struct drm_connector_hdmi_cec_data {
struct cec_adapter *adapter;
const struct drm_connector_hdmi_cec_funcs *funcs;
};
static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->enable(connector, enable);
}
static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->log_addr(connector, logical_addr);
}
static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->transmit(connector, attempts, signal_free_time, msg);
}
static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
.adap_enable = drm_connector_hdmi_cec_adap_enable,
.adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
.adap_transmit = drm_connector_hdmi_cec_adap_transmit,
};
static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_phys_addr_invalidate(data->adapter);
}
static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector,
u16 addr)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_s_phys_addr(data->adapter, addr, false);
}
static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res)
{
struct drm_connector *connector = res;
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_delete_adapter(data->adapter);
if (data->funcs->uninit)
data->funcs->uninit(connector);
kfree(data);
connector->cec.data = NULL;
}
static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = {
.phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
.phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
};
int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
const struct drm_connector_hdmi_cec_funcs *funcs,
const char *name,
u8 available_las,
struct device *dev)
{
struct drm_connector_hdmi_cec_data *data;
struct cec_connector_info conn_info;
struct cec_adapter *cec_adap;
int ret;
if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit)
return -EINVAL;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->funcs = funcs;
cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name,
CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
available_las ? : CEC_MAX_LOG_ADDRS);
ret = PTR_ERR_OR_ZERO(cec_adap);
if (ret < 0)
goto err_free;
cec_fill_conn_info_from_drm(&conn_info, connector);
cec_s_conn_info(cec_adap, &conn_info);
data->adapter = cec_adap;
mutex_lock(&connector->cec.mutex);
connector->cec.data = data;
connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;
ret = funcs->init(connector);
if (ret < 0)
goto err_delete_adapter;
/*
* NOTE: the CEC adapter will be unregistered by drmm cleanup from
* drm_managed_release(), which is called from drm_dev_release()
* during device unbind.
*
* However, the CEC framework cleans up the CEC adapter only when the
* last user has closed its file descriptor, so we don't need to handle
* it in DRM.
*
* Before that CEC framework makes sure that even if the userspace
* still holds CEC device open, all calls will be shortcut via
* cec_is_registered(), making sure that there is no access to the
* freed memory.
*/
ret = cec_register_adapter(cec_adap, dev);
if (ret < 0)
goto err_delete_adapter;
mutex_unlock(&connector->cec.mutex);
return drmm_add_action_or_reset(connector->dev,
drm_connector_hdmi_cec_adapter_unregister,
connector);
err_delete_adapter:
cec_delete_adapter(cec_adap);
connector->cec.data = NULL;
mutex_unlock(&connector->cec.mutex);
err_free:
kfree(data);
return ret;
}
EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);
void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
struct cec_msg *msg)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_received_msg(data->adapter, msg);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
u8 status)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_attempt_done(data->adapter, status);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
u8 status,
u8 arb_lost_cnt, u8 nack_cnt,
u8 low_drive_cnt, u8 error_cnt)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_done(data->adapter, status,
arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_managed.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <linux/mutex.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
static void drm_connector_hdmi_cec_notifier_phys_addr_invalidate(struct drm_connector *connector)
{
cec_notifier_phys_addr_invalidate(connector->cec.data);
}
static void drm_connector_hdmi_cec_notifier_phys_addr_set(struct drm_connector *connector,
u16 addr)
{
cec_notifier_set_phys_addr(connector->cec.data, addr);
}
static void drm_connector_hdmi_cec_notifier_unregister(struct drm_device *dev, void *res)
{
struct drm_connector *connector = res;
cec_notifier_conn_unregister(connector->cec.data);
connector->cec.data = NULL;
}
static const struct drm_connector_cec_funcs drm_connector_cec_notifier_funcs = {
.phys_addr_invalidate = drm_connector_hdmi_cec_notifier_phys_addr_invalidate,
.phys_addr_set = drm_connector_hdmi_cec_notifier_phys_addr_set,
};
int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector,
const char *port_name,
struct device *dev)
{
struct cec_connector_info conn_info;
struct cec_notifier *notifier;
cec_fill_conn_info_from_drm(&conn_info, connector);
notifier = cec_notifier_conn_register(dev, port_name, &conn_info);
if (!notifier)
return -ENOMEM;
mutex_lock(&connector->cec.mutex);
connector->cec.data = notifier;
connector->cec.funcs = &drm_connector_cec_notifier_funcs;
mutex_unlock(&connector->cec.mutex);
return drmm_add_action_or_reset(connector->dev,
drm_connector_hdmi_cec_notifier_unregister,
connector);
}
EXPORT_SYMBOL(drmm_connector_hdmi_cec_notifier_register);

View File

@ -0,0 +1,72 @@
/* SPDX-License-Identifier: MIT */
#ifndef DRM_DISPLAY_HDMI_CEC_HELPER
#define DRM_DISPLAY_HDMI_CEC_HELPER
#include <linux/types.h>
struct drm_connector;
struct cec_msg;
struct device;
struct drm_connector_hdmi_cec_funcs {
/**
* @init: perform hardware-specific initialization before registering the CEC adapter
*/
int (*init)(struct drm_connector *connector);
/**
* @uninit: perform hardware-specific teardown for the CEC adapter
*/
void (*uninit)(struct drm_connector *connector);
/**
* @enable: enable or disable CEC adapter
*/
int (*enable)(struct drm_connector *connector, bool enable);
/**
* @log_addr: set adapter's logical address, can be called multiple
* times if adapter supports several LAs
*/
int (*log_addr)(struct drm_connector *connector, u8 logical_addr);
/**
* @transmit: start transmission of the specified CEC message
*/
int (*transmit)(struct drm_connector *connector, u8 attempts,
u32 signal_free_time, struct cec_msg *msg);
};
int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
const struct drm_connector_hdmi_cec_funcs *funcs,
const char *name,
u8 available_las,
struct device *dev);
void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
struct cec_msg *msg);
void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
u8 status,
u8 arb_lost_cnt, u8 nack_cnt,
u8 low_drive_cnt, u8 error_cnt);
void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
u8 status);
#if IS_ENABLED(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER)
int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector,
const char *port_name,
struct device *dev);
#else
static inline int drmm_connector_hdmi_cec_notifier_register(struct drm_connector *connector,
const char *port_name,
struct device *dev)
{
return 0;
}
#endif
#endif