Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 998140d2 authored by Hans Verkuil's avatar Hans Verkuil Committed by Maxime Ripard
Browse files

sun4i_hdmi: add CEC support



Add HDMI CEC support to the Allwinner A10 SoC.

This SoC uses a poor-man's CEC implementation by polling the CEC pin. It is
using the CEC_PIN core implementation for such devices to do the heavy
lifting. It just provides the callbacks to read/drive the CEC pin.

Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Tested-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
parent 9181b5bb
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -22,6 +22,15 @@ config DRM_SUN4I_HDMI
	  Choose this option if you have an Allwinner SoC with an HDMI
	  controller.

config DRM_SUN4I_HDMI_CEC
       bool "Allwinner A10 HDMI CEC Support"
       depends on DRM_SUN4I_HDMI
       select CEC_CORE
       depends on CEC_PIN
       help
	  Choose this option if you have an Allwinner SoC with an HDMI
	  controller and want to use CEC.

config DRM_SUN4I_BACKEND
	tristate "Support for Allwinner A10 Display Engine Backend"
	default DRM_SUN4I
+8 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>

#include <media/cec.h>

#define SUN4I_HDMI_CTRL_REG		0x004
#define SUN4I_HDMI_CTRL_ENABLE			BIT(31)

@@ -86,6 +88,11 @@
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK	BIT(21)
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT	21

#define SUN4I_HDMI_CEC			0x214
#define SUN4I_HDMI_CEC_ENABLE			BIT(11)
#define SUN4I_HDMI_CEC_TX			BIT(9)
#define SUN4I_HDMI_CEC_RX			BIT(8)

#define SUN4I_HDMI_PKT_CTRL_REG(n)	(0x2f0 + (4 * (n)))
#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)		((t) << (((n) % 4) * 4))

@@ -172,6 +179,7 @@ struct sun4i_hdmi {
	struct sun4i_drv	*drv;

	bool			hdmi_monitor;
	struct cec_adapter	*cec_adap;
};

int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
+54 −1
Original line number Diff line number Diff line
@@ -197,6 +197,7 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector)
			 hdmi->hdmi_monitor ? "an HDMI" : "a DVI");

	drm_mode_connector_update_edid_property(connector, edid);
	cec_s_phys_addr_from_edid(hdmi->cec_adap, edid);
	ret = drm_add_edid_modes(connector, edid);
	kfree(edid);

@@ -215,8 +216,10 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)

	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
			       reg & SUN4I_HDMI_HPD_HIGH,
			       0, 500000))
			       0, 500000)) {
		cec_phys_addr_invalidate(hdmi->cec_adap);
		return connector_status_disconnected;
	}

	return connector_status_connected;
}
@@ -231,6 +234,40 @@ static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
};

#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap)
{
	struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);

	return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX;
}

static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap)
{
	struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);

	/* Start driving the CEC pin low */
	writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC);
}

static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap)
{
	struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);

	/*
	 * Stop driving the CEC pin, the pull up will take over
	 * unless another CEC device is driving the pin low.
	 */
	writel(0, hdmi->base + SUN4I_HDMI_CEC);
}

static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
	.read = sun4i_hdmi_cec_pin_read,
	.low = sun4i_hdmi_cec_pin_low,
	.high = sun4i_hdmi_cec_pin_high,
};
#endif

static int sun4i_hdmi_bind(struct device *dev, struct device *master,
			   void *data)
{
@@ -348,6 +385,17 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
		goto err_del_i2c_adapter;
	}

#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
	hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops,
		hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
		CEC_CAP_PASSTHROUGH | CEC_CAP_RC);
	ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
	if (ret < 0)
		goto err_cleanup_connector;
	writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX,
	       hdmi->base + SUN4I_HDMI_CEC);
#endif

	drm_connector_helper_add(&hdmi->connector,
				 &sun4i_hdmi_connector_helper_funcs);
	ret = drm_connector_init(drm, &hdmi->connector,
@@ -363,11 +411,15 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
	hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
		DRM_CONNECTOR_POLL_DISCONNECT;

	ret = cec_register_adapter(hdmi->cec_adap, dev);
	if (ret < 0)
		goto err_cleanup_connector;
	drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);

	return 0;

err_cleanup_connector:
	cec_delete_adapter(hdmi->cec_adap);
	drm_encoder_cleanup(&hdmi->encoder);
err_del_i2c_adapter:
	i2c_del_adapter(hdmi->i2c);
@@ -379,6 +431,7 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
{
	struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);

	cec_unregister_adapter(hdmi->cec_adap);
	drm_connector_cleanup(&hdmi->connector);
	drm_encoder_cleanup(&hdmi->encoder);
	i2c_del_adapter(hdmi->i2c);