Commit cf207ea2 authored by Dmitry Baryshkov's avatar Dmitry Baryshkov
Browse files

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: default avatarDmitry Baryshkov <dmitry.baryshkov@linaro.org>
Reviewed-by: default avatarMaxime Ripard <mripard@kernel.org>
Tested-by: default avatarDave 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: default avatarDmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
parent 6ca1701c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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.
+54 −83
Original line number Diff line number Diff line
@@ -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,7 +2377,7 @@ 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,
		drm_connector_hdmi_cec_received_msg(&vc4_hdmi->connector,
						    &vc4_hdmi->cec_rx_msg);

	return IRQ_HANDLED;
@@ -2395,14 +2388,16 @@ 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,
		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,
		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;

+0 −1
Original line number Diff line number Diff line
@@ -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;