From 998140d26723bcddef5857e39077898b0d1bdb8f Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 11 Jul 2017 08:30:44 +0200 Subject: [PATCH] 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: Hans Verkuil Tested-by: Maxime Ripard Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/Kconfig | 9 +++++ drivers/gpu/drm/sun4i/sun4i_hdmi.h | 8 ++++ drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 55 +++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 5cc116692913..06f05302ee75 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -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 diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index 0957ff2076ac..1457750988da 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -15,6 +15,8 @@ #include #include +#include + #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); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index b74607feb35c..863a51618819 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -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); -- 2.20.1