drm/vc4: hdmi: switch to generic CEC helpers

Switch VC4 driver to using CEC helpers code, simplifying hotplug and
registration / cleanup. The existing vc4_hdmi_cec_release() is kept for
now.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Tested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Link: https://lore.kernel.org/r/20250705-drm-hdmi-connector-cec-v7-1-d14fa0c31b74@oss.qualcomm.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
This commit is contained in:
Dmitry Baryshkov
2025-07-05 13:05:13 +03:00
parent 6ca1701cec
commit cf207ea2c3
3 changed files with 55 additions and 84 deletions

View File

@@ -35,6 +35,7 @@ config DRM_VC4_HDMI_CEC
bool "Broadcom VC4 HDMI CEC Support"
depends on DRM_VC4
select CEC_CORE
select DRM_DISPLAY_HDMI_CEC_HELPER
help
Choose this option if you have a Broadcom VC4 GPU
and want to use CEC.

View File

@@ -32,6 +32,7 @@
*/
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include <drm/display/drm_scdc_helper.h>
@@ -375,14 +376,6 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
drm_atomic_helper_connector_hdmi_hotplug(connector, status);
if (status == connector_status_disconnected) {
cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
return;
}
cec_s_phys_addr(vc4_hdmi->cec_adap,
connector->display_info.source_physical_address, false);
if (status != connector_status_connected)
return;
@@ -2384,8 +2377,8 @@ static irqreturn_t vc4_cec_irq_handler_rx_thread(int irq, void *priv)
struct vc4_hdmi *vc4_hdmi = priv;
if (vc4_hdmi->cec_rx_msg.len)
cec_received_msg(vc4_hdmi->cec_adap,
&vc4_hdmi->cec_rx_msg);
drm_connector_hdmi_cec_received_msg(&vc4_hdmi->connector,
&vc4_hdmi->cec_rx_msg);
return IRQ_HANDLED;
}
@@ -2395,15 +2388,17 @@ static irqreturn_t vc4_cec_irq_handler_tx_thread(int irq, void *priv)
struct vc4_hdmi *vc4_hdmi = priv;
if (vc4_hdmi->cec_tx_ok) {
cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_OK,
0, 0, 0, 0);
drm_connector_hdmi_cec_transmit_done(&vc4_hdmi->connector,
CEC_TX_STATUS_OK,
0, 0, 0, 0);
} else {
/*
* This CEC implementation makes 1 retry, so if we
* get a NACK, then that means it made 2 attempts.
*/
cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_NACK,
0, 2, 0, 0);
drm_connector_hdmi_cec_transmit_done(&vc4_hdmi->connector,
CEC_TX_STATUS_NACK,
0, 2, 0, 0);
}
return IRQ_HANDLED;
}
@@ -2560,9 +2555,9 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv)
return ret;
}
static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
static int vc4_hdmi_cec_enable(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
/* clock period in microseconds */
const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
@@ -2627,9 +2622,9 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
return 0;
}
static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
static int vc4_hdmi_cec_disable(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
unsigned long flags;
int idx;
@@ -2663,17 +2658,17 @@ static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
return 0;
}
static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
static int vc4_hdmi_cec_adap_enable(struct drm_connector *connector, bool enable)
{
if (enable)
return vc4_hdmi_cec_enable(adap);
return vc4_hdmi_cec_enable(connector);
else
return vc4_hdmi_cec_disable(adap);
return vc4_hdmi_cec_disable(connector);
}
static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
static int vc4_hdmi_cec_adap_log_addr(struct drm_connector *connector, u8 log_addr)
{
struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
unsigned long flags;
int idx;
@@ -2699,10 +2694,10 @@ static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
return 0;
}
static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
static int vc4_hdmi_cec_adap_transmit(struct drm_connector *connector, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *dev = vc4_hdmi->connector.dev;
unsigned long flags;
u32 val;
@@ -2745,84 +2740,65 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
return 0;
}
static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = {
.adap_enable = vc4_hdmi_cec_adap_enable,
.adap_log_addr = vc4_hdmi_cec_adap_log_addr,
.adap_transmit = vc4_hdmi_cec_adap_transmit,
};
static void vc4_hdmi_cec_release(void *ptr)
static int vc4_hdmi_cec_init(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = ptr;
cec_unregister_adapter(vc4_hdmi->cec_adap);
vc4_hdmi->cec_adap = NULL;
}
static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
{
struct cec_connector_info conn_info;
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct platform_device *pdev = vc4_hdmi->pdev;
struct device *dev = &pdev->dev;
int ret;
if (!of_property_present(dev->of_node, "interrupts")) {
dev_warn(dev, "'interrupts' DT property is missing, no CEC\n");
return 0;
}
vc4_hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops,
vc4_hdmi,
vc4_hdmi->variant->card_name,
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO, 1);
ret = PTR_ERR_OR_ZERO(vc4_hdmi->cec_adap);
if (ret < 0)
return ret;
cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector);
cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info);
if (vc4_hdmi->variant->external_irq_controller) {
ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-rx"),
vc4_cec_irq_handler_rx_bare,
vc4_cec_irq_handler_rx_thread, 0,
"vc4 hdmi cec rx", vc4_hdmi);
if (ret)
goto err_delete_cec_adap;
return ret;
ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-tx"),
vc4_cec_irq_handler_tx_bare,
vc4_cec_irq_handler_tx_thread, 0,
"vc4 hdmi cec tx", vc4_hdmi);
if (ret)
goto err_delete_cec_adap;
return ret;
} else {
ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
vc4_cec_irq_handler,
vc4_cec_irq_handler_thread, 0,
"vc4 hdmi cec", vc4_hdmi);
if (ret)
goto err_delete_cec_adap;
return ret;
}
ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev);
if (ret < 0)
goto err_delete_cec_adap;
return 0;
}
static const struct drm_connector_hdmi_cec_funcs vc4_hdmi_cec_funcs = {
.init = vc4_hdmi_cec_init,
.enable = vc4_hdmi_cec_adap_enable,
.log_addr = vc4_hdmi_cec_adap_log_addr,
.transmit = vc4_hdmi_cec_adap_transmit,
};
static int vc4_hdmi_cec_register(struct vc4_hdmi *vc4_hdmi)
{
struct platform_device *pdev = vc4_hdmi->pdev;
struct device *dev = &pdev->dev;
if (!of_property_present(dev->of_node, "interrupts")) {
dev_warn(dev, "'interrupts' DT property is missing, no CEC\n");
return 0;
}
/*
* NOTE: Strictly speaking, we should probably use a DRM-managed
* registration there to avoid removing the CEC adapter by the
* time the DRM driver doesn't have any user anymore.
* 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 already 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.
*
* By the time the device-managed hook is executed, we will give
* up our reference to the CEC adapter and therefore don't
* really care when it's actually freed.
*
* There's still a problematic sequence: if we unregister our
* CEC adapter, but the userspace keeps a handle on the CEC
* adapter but not the DRM device for some reason. In such a
@@ -2833,19 +2809,14 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
* the CEC framework already handles this too, by calling
* cec_is_registered() in cec_ioctl() and cec_poll().
*/
ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi);
if (ret)
return ret;
return 0;
err_delete_cec_adap:
cec_delete_adapter(vc4_hdmi->cec_adap);
return ret;
return drmm_connector_hdmi_cec_register(&vc4_hdmi->connector,
&vc4_hdmi_cec_funcs,
vc4_hdmi->variant->card_name,
1,
&pdev->dev);
}
#else
static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
static int vc4_hdmi_cec_register(struct vc4_hdmi *vc4_hdmi)
{
return 0;
}
@@ -3250,7 +3221,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
if (ret)
goto err_put_runtime_pm;
ret = vc4_hdmi_cec_init(vc4_hdmi);
ret = vc4_hdmi_cec_register(vc4_hdmi);
if (ret)
goto err_put_runtime_pm;

View File

@@ -147,7 +147,6 @@ struct vc4_hdmi {
*/
bool disable_wifi_frequencies;
struct cec_adapter *cec_adap;
struct cec_msg cec_rx_msg;
bool cec_tx_ok;
bool cec_irq_was_rx;