diff mbox series

[4/5] drm/omap: hdmi5: add CEC support

Message ID 20210211103703.444625-5-hverkuil-cisco@xs4all.nl
State Superseded
Headers show
Series None | expand

Commit Message

Hans Verkuil Feb. 11, 2021, 10:37 a.m. UTC
Add HDMI CEC support for OMAP5.

Many thanks to Tomi for helping out how to enable CEC for omap5.

Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Thanks-to: Tomi Valkeinen <tomi.valkeinen@iki.fi>
---
 drivers/gpu/drm/omapdrm/dss/Kconfig      |   8 +
 drivers/gpu/drm/omapdrm/dss/Makefile     |   1 +
 drivers/gpu/drm/omapdrm/dss/hdmi.h       |   1 +
 drivers/gpu/drm/omapdrm/dss/hdmi5.c      |  63 +++++--
 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c  | 201 +++++++++++++++++++++++
 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h  |  42 +++++
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.c |  28 +++-
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.h |  33 +++-
 8 files changed, 358 insertions(+), 19 deletions(-)
 create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
 create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h

Comments

Hans Verkuil March 1, 2021, noon UTC | #1
On 19/02/2021 12:09, Tomi Valkeinen wrote:
> Hi Hans,
> 
> On 11/02/2021 12:37, Hans Verkuil wrote:
>> Add HDMI CEC support for OMAP5.
>>
>> Many thanks to Tomi for helping out how to enable CEC for omap5.
>>
>> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
>> Thanks-to: Tomi Valkeinen <tomi.valkeinen@iki.fi>
>> ---
>>  drivers/gpu/drm/omapdrm/dss/Kconfig      |   8 +
>>  drivers/gpu/drm/omapdrm/dss/Makefile     |   1 +
>>  drivers/gpu/drm/omapdrm/dss/hdmi.h       |   1 +
>>  drivers/gpu/drm/omapdrm/dss/hdmi5.c      |  63 +++++--
>>  drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c  | 201 +++++++++++++++++++++++
>>  drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h  |  42 +++++
>>  drivers/gpu/drm/omapdrm/dss/hdmi5_core.c |  28 +++-
>>  drivers/gpu/drm/omapdrm/dss/hdmi5_core.h |  33 +++-
>>  8 files changed, 358 insertions(+), 19 deletions(-)
>>  create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
>>  create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h
>>
>> diff --git a/drivers/gpu/drm/omapdrm/dss/Kconfig b/drivers/gpu/drm/omapdrm/dss/Kconfig
>> index e11b258a2294..67a1ba14703b 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/Kconfig
>> +++ b/drivers/gpu/drm/omapdrm/dss/Kconfig
>> @@ -83,6 +83,14 @@ config OMAP5_DSS_HDMI
>>  	  Definition Multimedia Interface. See https://www.hdmi.org/ for HDMI
>>  	  specification.
>>  
>> +config OMAP5_DSS_HDMI_CEC
>> +	bool "Enable HDMI CEC support for OMAP5"
>> +	depends on OMAP5_DSS_HDMI
>> +	select CEC_CORE
>> +	default y
>> +	help
>> +	  When selected the HDMI transmitter will support the CEC feature.
>> +
>>  config OMAP2_DSS_SDI
>>  	bool "SDI support"
>>  	default n
>> diff --git a/drivers/gpu/drm/omapdrm/dss/Makefile b/drivers/gpu/drm/omapdrm/dss/Makefile
>> index f967e6948f2e..94fe0fa3b3c2 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/Makefile
>> +++ b/drivers/gpu/drm/omapdrm/dss/Makefile
>> @@ -17,4 +17,5 @@ omapdss-$(CONFIG_OMAP2_DSS_HDMI_COMMON) += hdmi_common.o hdmi_wp.o hdmi_pll.o \
>>  omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi4.o hdmi4_core.o
>>  omapdss-$(CONFIG_OMAP4_DSS_HDMI_CEC) += hdmi4_cec.o
>>  omapdss-$(CONFIG_OMAP5_DSS_HDMI) += hdmi5.o hdmi5_core.o
>> +omapdss-$(CONFIG_OMAP5_DSS_HDMI_CEC) += hdmi5_cec.o
>>  ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h
>> index c4a4e07f0b99..72d8ae441da6 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/hdmi.h
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h
>> @@ -261,6 +261,7 @@ struct hdmi_core_data {
>>  	struct hdmi_wp_data *wp;
>>  	unsigned int core_pwr_cnt;
>>  	struct cec_adapter *adap;
>> +	struct clk *cec_clk;
>>  };
>>  
>>  static inline void hdmi_write_reg(void __iomem *base_addr, const u32 idx,
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
>> index 54e5cb5aa52d..b674d8ba173f 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
>> @@ -29,12 +29,14 @@
>>  #include <linux/of.h>
>>  #include <linux/of_graph.h>
>>  #include <sound/omap-hdmi-audio.h>
>> +#include <media/cec.h>
>>  
>>  #include <drm/drm_atomic.h>
>>  #include <drm/drm_atomic_state_helper.h>
>>  
>>  #include "omapdss.h"
>>  #include "hdmi5_core.h"
>> +#include "hdmi5_cec.h"
>>  #include "dss.h"
>>  
>>  static int hdmi_runtime_get(struct omap_hdmi *hdmi)
>> @@ -104,6 +106,10 @@ static irqreturn_t hdmi_irq_handler(int irq, void *data)
>>  	} else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
>>  		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON);
>>  	}
> 
> Empty line here, please.
> 
>> +	if (irqstatus & HDMI_IRQ_CORE) {
>> +		hdmi5_cec_irq(&hdmi->core);
>> +		hdmi5_core_handle_irqs(&hdmi->core);
>> +	}
> 
> It's a bit odd to call two functions here. Would it work if
> hdmi5_core_handle_irqs() would read and clear HDMI_CORE_IH_CEC_STAT0,
> and call hdmi5_cec_irq() if the stat != 0 ?

Makes sense, I'll do that.

> 
> And it would be nice if hdmi5_core.c would enable and disable core
> interrupt, but maybe that can be left for later if the need ever comes
> to handle other interrupts than cec.

I prefer to leave it as-is.

> 
>>  
>>  	return IRQ_HANDLED;
>>  }
>> @@ -112,9 +118,12 @@ static int hdmi_power_on_core(struct omap_hdmi *hdmi)
>>  {
>>  	int r;
>>  
>> +	if (hdmi->core.core_pwr_cnt++)
>> +		return 0;
>> +
>>  	r = regulator_enable(hdmi->vdda_reg);
>>  	if (r)
>> -		return r;
>> +		goto err_reg_enable;
>>  
>>  	r = hdmi_runtime_get(hdmi);
>>  	if (r)
>> @@ -129,12 +138,17 @@ static int hdmi_power_on_core(struct omap_hdmi *hdmi)
>>  
>>  err_runtime_get:
>>  	regulator_disable(hdmi->vdda_reg);
>> +err_reg_enable:
>> +	hdmi->core.core_pwr_cnt--;
>>  
>>  	return r;
>>  }
>>  
>>  static void hdmi_power_off_core(struct omap_hdmi *hdmi)
>>  {
>> +	if (--hdmi->core.core_pwr_cnt)
>> +		return;
>> +
>>  	hdmi->core_enabled = false;
>>  
>>  	hdmi_runtime_put(hdmi);
>> @@ -168,7 +182,7 @@ static int hdmi_power_on_full(struct omap_hdmi *hdmi)
>>  		pc, &hdmi_cinfo);
>>  
>>  	/* disable and clear irqs */
>> -	hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff);
>> +	hdmi_wp_clear_irqenable(&hdmi->wp, ~HDMI_IRQ_CORE);
> 
> I guess the point here is to not touch CORE interrupt, as hdmi5_cec.c
> handles that? The line below will still clear the CORE interrupt status.
> 
>>  	hdmi_wp_set_irqstatus(&hdmi->wp,
>>  			hdmi_wp_get_irqstatus(&hdmi->wp));
>>  
>> @@ -225,7 +239,7 @@ static int hdmi_power_on_full(struct omap_hdmi *hdmi)
>>  
>>  static void hdmi_power_off_full(struct omap_hdmi *hdmi)
>>  {
>> -	hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff);
>> +	hdmi_wp_clear_irqenable(&hdmi->wp, ~HDMI_IRQ_CORE);
>>  
>>  	hdmi_wp_video_stop(&hdmi->wp);
>>  
>> @@ -273,11 +287,11 @@ static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
>>  	REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2);
>>  }
>>  
>> -static int hdmi_core_enable(struct omap_hdmi *hdmi)
>> +int hdmi5_core_enable(struct omap_hdmi *hdmi)
>>  {
>>  	int r = 0;
>>  
>> -	DSSDBG("ENTER omapdss_hdmi_core_enable\n");
>> +	DSSDBG("ENTER %s\n", __func__);
>>  
>>  	mutex_lock(&hdmi->lock);
>>  
>> @@ -295,9 +309,9 @@ static int hdmi_core_enable(struct omap_hdmi *hdmi)
>>  	return r;
>>  }
>>  
>> -static void hdmi_core_disable(struct omap_hdmi *hdmi)
>> +void hdmi5_core_disable(struct omap_hdmi *hdmi)
>>  {
>> -	DSSDBG("Enter omapdss_hdmi_core_disable\n");
>> +	DSSDBG("ENTER %s\n", __func__);
>>  
>>  	mutex_lock(&hdmi->lock);
>>  
>> @@ -424,6 +438,15 @@ static void hdmi5_bridge_disable(struct drm_bridge *bridge,
>>  	mutex_unlock(&hdmi->lock);
>>  }
>>  
>> +static void hdmi5_bridge_hpd_notify(struct drm_bridge *bridge,
>> +				    enum drm_connector_status status)
>> +{
>> +	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
>> +
>> +	if (status == connector_status_disconnected)
>> +		hdmi5_cec_set_phys_addr(&hdmi->core, NULL);
>> +}
>> +
>>  static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
>>  					  struct drm_connector *connector)
>>  {
>> @@ -436,7 +459,7 @@ static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
>>  	need_enable = hdmi->core_enabled == false;
>>  
>>  	if (need_enable) {
>> -		r = hdmi_core_enable(hdmi);
>> +		r = hdmi5_core_enable(hdmi);
>>  		if (r)
>>  			return NULL;
>>  	}
>> @@ -460,12 +483,29 @@ static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
>>  	hdmi_runtime_put(hdmi);
>>  	mutex_unlock(&hdmi->lock);
>>  
>> +	hdmi5_cec_set_phys_addr(&hdmi->core, edid);
>> +
>>  	if (need_enable)
>> -		hdmi_core_disable(hdmi);
>> +		hdmi5_core_disable(hdmi);
>>  
>>  	return (struct edid *)edid;
>>  }
>>  
>> +static int hdmi5_bridge_cec_init(struct drm_bridge *bridge,
>> +				 struct drm_connector *conn)
>> +{
>> +	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
>> +
>> +	return hdmi5_cec_init(hdmi->pdev, &hdmi->core, &hdmi->wp, conn);
>> +}
>> +
>> +static void hdmi5_bridge_cec_exit(struct drm_bridge *bridge)
>> +{
>> +	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
>> +
>> +	hdmi5_cec_uninit(&hdmi->core);
>> +}
>> +
>>  static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
>>  	.attach = hdmi5_bridge_attach,
>>  	.mode_set = hdmi5_bridge_mode_set,
>> @@ -474,14 +514,17 @@ static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
>>  	.atomic_reset = drm_atomic_helper_bridge_reset,
>>  	.atomic_enable = hdmi5_bridge_enable,
>>  	.atomic_disable = hdmi5_bridge_disable,
>> +	.hpd_notify = hdmi5_bridge_hpd_notify,
>>  	.get_edid = hdmi5_bridge_get_edid,
>> +	.cec_init = hdmi5_bridge_cec_init,
>> +	.cec_exit = hdmi5_bridge_cec_exit,
>>  };
>>  
>>  static void hdmi5_bridge_init(struct omap_hdmi *hdmi)
>>  {
>>  	hdmi->bridge.funcs = &hdmi5_bridge_funcs;
>>  	hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
>> -	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
>> +	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_CEC;
>>  	hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
>>  
>>  	drm_bridge_add(&hdmi->bridge);
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
>> new file mode 100644
>> index 000000000000..26ef8f585b8d
>> --- /dev/null
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
>> @@ -0,0 +1,201 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * HDMI CEC
>> + *
>> + * Copyright 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
>> + */
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/sched.h>
>> +#include <linux/slab.h>
>> +#include <linux/clk.h>
>> +
>> +#include "dss.h"
>> +#include "hdmi.h"
>> +#include "hdmi5_core.h"
>> +#include "hdmi5_cec.h"
>> +
>> +static int hdmi5_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
>> +{
>> +	struct hdmi_core_data *core = cec_get_drvdata(adap);
>> +	u8 v;
>> +
>> +	if (logical_addr == CEC_LOG_ADDR_INVALID) {
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_L, 0);
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, 0);
> 
> Empty line here
> 
>> +		return 0;
>> +	}
>> +
>> +	if (logical_addr <= 7) {
>> +		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_L);
>> +		v |= 1 << logical_addr;
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_L, v);
>> +		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_H);
>> +		v |= 1 << 7;
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, v);
>> +	} else {
>> +		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_H);
>> +		v |= 1 << (logical_addr - 8);
>> +		v |= 1 << 7;
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, v);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int hdmi5_cec_transmit(struct cec_adapter *adap, u8 attempts,
>> +			      u32 signal_free_time, struct cec_msg *msg)
>> +{
>> +	struct hdmi_core_data *core = cec_get_drvdata(adap);
>> +	unsigned int i, ctrl;
>> +
>> +	switch (signal_free_time) {
>> +	case CEC_SIGNAL_FREE_TIME_RETRY:
>> +		ctrl = CEC_CTRL_RETRY;
>> +		break;
>> +	case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
>> +	default:
>> +		ctrl = CEC_CTRL_NORMAL;
>> +		break;
>> +	case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
>> +		ctrl = CEC_CTRL_IMMED;
>> +		break;
>> +	}
>> +
>> +	for (i = 0; i < msg->len; i++)
>> +		hdmi_write_reg(core->base,
>> +			       HDMI_CORE_CEC_TX_DATA0 + i * 4, msg->msg[i]);
>> +
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_TX_CNT, msg->len);
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_CTRL,
>> +		       ctrl | CEC_CTRL_START);
>> +
>> +	return 0;
>> +}
>> +
>> +void hdmi5_cec_irq(struct hdmi_core_data *core)
>> +{
>> +	struct cec_adapter *adap = core->adap;
>> +	unsigned int stat = hdmi_read_reg(core->base, HDMI_CORE_IH_CEC_STAT0);
>> +
>> +	if (stat == 0)
>> +		return;
>> +
>> +	hdmi_write_reg(core->base, HDMI_CORE_IH_CEC_STAT0, stat);
>> +
>> +	if (stat & CEC_STAT_ERROR_INIT)
>> +		cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR);
>> +	else if (stat & CEC_STAT_DONE)
>> +		cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK);
>> +	else if (stat & CEC_STAT_NACK)
>> +		cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK);
>> +
>> +	if (stat & CEC_STAT_EOM) {
>> +		struct cec_msg msg = {};
>> +		unsigned int len, i;
>> +
>> +		len = hdmi_read_reg(core->base, HDMI_CORE_CEC_RX_CNT);
>> +		if (len > sizeof(msg.msg))
>> +			len = sizeof(msg.msg);
>> +
>> +		for (i = 0; i < len; i++)
>> +			msg.msg[i] =
>> +				hdmi_read_reg(core->base,
>> +					      HDMI_CORE_CEC_RX_DATA0 + i * 4);
>> +
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_LOCK, 0);
>> +
>> +		msg.len = len;
>> +		cec_received_msg(adap, &msg);
>> +	}
>> +}
>> +
>> +static int hdmi5_cec_enable(struct cec_adapter *adap, bool enable)
>> +{
>> +	struct hdmi_core_data *core = cec_get_drvdata(adap);
>> +	struct omap_hdmi *hdmi = container_of(core, struct omap_hdmi, core);
>> +	unsigned int irqs;
>> +	int err;
>> +
>> +	if (!enable) {
>> +		hdmi_write_reg(core->base, HDMI_CORE_CEC_MASK, ~0);
>> +		hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_CEC_STAT0, ~0);
>> +		hdmi_wp_clear_irqenable(core->wp, HDMI_IRQ_CORE);
>> +		hdmi_wp_set_irqstatus(core->wp, HDMI_IRQ_CORE);
>> +		REG_FLD_MOD(core->base, HDMI_CORE_MC_CLKDIS, 0x01, 5, 5);
>> +		hdmi5_core_disable(hdmi);
> 
> Empty line.
> 
>> +		return 0;
>> +	}
> 
> And here.
> 
>> +	err = hdmi5_core_enable(hdmi);
>> +	if (err)
>> +		return err;
>> +
>> +	REG_FLD_MOD(core->base, HDMI_CORE_MC_CLKDIS, 0x00, 5, 5);
>> +	hdmi_write_reg(core->base, HDMI_CORE_IH_I2CM_STAT0, ~0);
>> +	hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_I2CM_STAT0, ~0);
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_CTRL, 0);
>> +	hdmi_write_reg(core->base, HDMI_CORE_IH_CEC_STAT0, ~0);
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_LOCK, 0);
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_TX_CNT, 0);
>> +
>> +	hdmi5_cec_log_addr(adap, CEC_LOG_ADDR_INVALID);
>> +
>> +	/* Enable HDMI core interrupts */
>> +	hdmi_wp_set_irqenable(core->wp, HDMI_IRQ_CORE);
>> +
>> +	irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM |
>> +	       CEC_STAT_DONE;
>> +	hdmi_write_reg(core->base, HDMI_CORE_CEC_MASK, ~irqs);
>> +	hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_CEC_STAT0, ~irqs);
> 
> Empty line.
> 
>> +	return 0;
>> +}
>> +
>> +static const struct cec_adap_ops hdmi5_cec_ops = {
>> +	.adap_enable = hdmi5_cec_enable,
>> +	.adap_log_addr = hdmi5_cec_log_addr,
>> +	.adap_transmit = hdmi5_cec_transmit,
>> +};
> 
> There's a chance of race with these and the drm originating hdmi code.
> hdmi5_core_enable/disable is protected with a mutex, but a few of the
> registers are touched without any common lock held. Did you go through
> these and ensure there's no race? With some studying, I can't see
> anything that might cause issues, so maybe it's fine.
> 
>> +void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core, struct edid *edid)
>> +{
>> +	cec_s_phys_addr_from_edid(core->adap, edid);
>> +}
>> +
>> +int hdmi5_cec_init(struct platform_device *pdev, struct hdmi_core_data *core,
>> +		   struct hdmi_wp_data *wp, struct drm_connector *conn)
>> +{
>> +	const u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO;
>> +	struct cec_connector_info conn_info;
>> +	unsigned int ret;
>> +
>> +	core->cec_clk = devm_clk_get(&pdev->dev, "cec");
>> +	if (IS_ERR(core->cec_clk))
>> +		return PTR_ERR(core->cec_clk);
>> +	ret = clk_prepare_enable(core->cec_clk);
>> +	if (ret)
>> +		return ret;
>> +
>> +	core->adap = cec_allocate_adapter(&hdmi5_cec_ops, core,
>> +					  "omap5", caps, CEC_MAX_LOG_ADDRS);
>> +	ret = PTR_ERR_OR_ZERO(core->adap);
>> +	if (ret < 0)
>> +		return ret;
> 
> Empty line.
> 
>> +	cec_fill_conn_info_from_drm(&conn_info, conn);
>> +	cec_s_conn_info(core->adap, &conn_info);
>> +	core->wp = wp;
>> +
>> +	ret = cec_register_adapter(core->adap, &pdev->dev);
>> +	if (ret < 0) {
>> +		cec_delete_adapter(core->adap);
>> +		return ret;
>> +	}
> 
> Empty line.

Added all these empty lines.

Regards,

	Hans

> 
>> +	return 0;
>> +}
>> +
>> +void hdmi5_cec_uninit(struct hdmi_core_data *core)
>> +{
>> +	clk_disable_unprepare(core->cec_clk);
>> +	cec_unregister_adapter(core->adap);
>> +}
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h
>> new file mode 100644
>> index 000000000000..904541da46da
>> --- /dev/null
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h
>> @@ -0,0 +1,42 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * HDMI header definition for OMAP5 HDMI CEC IP
>> + *
>> + * Copyright 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
>> + */
>> +
>> +#ifndef _HDMI5_CEC_H_
>> +#define _HDMI5_CEC_H_
>> +
>> +/* HDMI CEC funcs */
>> +#ifdef CONFIG_OMAP5_DSS_HDMI_CEC
>> +void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core,
>> +			     struct edid *edid);
>> +void hdmi5_cec_irq(struct hdmi_core_data *core);
>> +int hdmi5_cec_init(struct platform_device *pdev, struct hdmi_core_data *core,
>> +		   struct hdmi_wp_data *wp, struct drm_connector *conn);
>> +void hdmi5_cec_uninit(struct hdmi_core_data *core);
>> +#else
>> +static inline void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core,
>> +					   struct edid *edid)
>> +{
>> +}
>> +
>> +static inline void hdmi5_cec_irq(struct hdmi_core_data *core)
>> +{
>> +}
>> +
>> +static inline int hdmi5_cec_init(struct platform_device *pdev,
>> +				 struct hdmi_core_data *core,
>> +				 struct hdmi_wp_data *wp,
>> +				 struct drm_connector *conn)
>> +{
>> +	return 0;
>> +}
>> +
>> +static inline void hdmi5_cec_uninit(struct hdmi_core_data *core)
>> +{
>> +}
>> +#endif
>> +
>> +#endif
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
>> index 6cc2ad7a420c..13bc0f3d850b 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
>> @@ -229,6 +229,19 @@ void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s)
>>  	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_1_ADDR);
>>  	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR);
>>  	DUMPCORE(HDMI_CORE_I2CM_SDA_HOLD_ADDR);
>> +
>> +	DUMPCORE(HDMI_CORE_IH_CEC_STAT0);
>> +	DUMPCORE(HDMI_CORE_IH_MUTE_CEC_STAT0);
>> +	DUMPCORE(HDMI_CORE_CEC_CTRL);
>> +	DUMPCORE(HDMI_CORE_CEC_MASK);
>> +	DUMPCORE(HDMI_CORE_CEC_ADDR_L);
>> +	DUMPCORE(HDMI_CORE_CEC_ADDR_H);
>> +	DUMPCORE(HDMI_CORE_CEC_TX_CNT);
>> +	DUMPCORE(HDMI_CORE_CEC_RX_CNT);
>> +	DUMPCORE(HDMI_CORE_CEC_TX_DATA0);
>> +	DUMPCORE(HDMI_CORE_CEC_RX_DATA0);
>> +	DUMPCORE(HDMI_CORE_CEC_LOCK);
>> +	DUMPCORE(HDMI_CORE_CEC_WKUPCTRL);
>>  }
>>  
>>  static void hdmi_core_init(struct hdmi_core_vid_config *video_cfg,
>> @@ -513,8 +526,6 @@ static void hdmi_core_mask_interrupts(struct hdmi_core_data *core)
>>  	REG_FLD_MOD(base, HDMI_CORE_AUD_INT, 0x3, 3, 2);
>>  	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_MASK, 0x3, 1, 0);
>>  
>> -	REG_FLD_MOD(base, HDMI_CORE_CEC_MASK, 0x7f, 6, 0);
>> -
>>  	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 6, 6);
>>  	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 2, 2);
>>  	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2);
>> @@ -532,8 +543,6 @@ static void hdmi_core_mask_interrupts(struct hdmi_core_data *core)
>>  
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0x7, 2, 0);
>>  
>> -	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0x7f, 6, 0);
>> -
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0x3, 1, 0);
>>  
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0);
>> @@ -549,13 +558,17 @@ int hdmi5_core_handle_irqs(struct hdmi_core_data *core)
>>  {
>>  	void __iomem *base = core->base;
>>  
>> +	/*
>> +	 * Clear all possible IRQ_CORE interrupts except for
>> +	 * HDMI_CORE_IH_I2CM_STAT0 (that interrupt is muted and
>> +	 * is handled by polling elsewhere) and HDMI_CORE_IH_CEC_STAT0
>> +	 * which is handled by the CEC code.
>> +	 */
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT0, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT1, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT2, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0);
>> -	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0xff, 7, 0);
>> -	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_VP_STAT0, 0xff, 7, 0);
>>  	REG_FLD_MOD(base, HDMI_CORE_IH_I2CMPHY_STAT0, 0xff, 7, 0);
>>  
>> @@ -879,5 +892,8 @@ int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core)
>>  	if (IS_ERR(core->base))
>>  		return PTR_ERR(core->base);
>>  
>> +	REG_FLD_MOD(core->base, HDMI_CORE_CEC_MASK, 0x7f, 6, 0);
>> +	REG_FLD_MOD(core->base, HDMI_CORE_IH_CEC_STAT0, 0x7f, 6, 0);
>> +
>>  	return 0;
>>  }
>> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
>> index 070cbf5fb57d..a83b634f6011 100644
>> --- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
>> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
>> @@ -30,8 +30,18 @@
>>  #define HDMI_CORE_IH_PHY_STAT0			0x00410
>>  #define HDMI_CORE_IH_I2CM_STAT0			0x00414
>>  #define HDMI_CORE_IH_CEC_STAT0			0x00418
>> +#define CEC_STAT_DONE				BIT(0)
>> +#define CEC_STAT_EOM				BIT(1)
>> +#define CEC_STAT_NACK				BIT(2)
>> +#define CEC_STAT_ARBLOST			BIT(3)
>> +#define CEC_STAT_ERROR_INIT			BIT(4)
>> +#define CEC_STAT_ERROR_FOLL			BIT(5)
>> +#define CEC_STAT_WAKEUP				BIT(6)
>> +
>>  #define HDMI_CORE_IH_VP_STAT0			0x0041C
>>  #define HDMI_CORE_IH_I2CMPHY_STAT0		0x00420
>> +#define HDMI_CORE_IH_MUTE_I2CM_STAT0            0x00614
>> +#define HDMI_CORE_IH_MUTE_CEC_STAT0		0x00618
>>  #define HDMI_CORE_IH_MUTE			0x007FC
>>  
>>  /* HDMI Video Sampler */
>> @@ -233,9 +243,6 @@
>>  /* HDMI HDCP */
>>  #define HDMI_CORE_HDCP_MASK			0x14020
>>  
>> -/* HDMI CEC */
>> -#define HDMI_CORE_CEC_MASK			0x17408
>> -
>>  /* HDMI I2C Master */
>>  #define HDMI_CORE_I2CM_SLAVE			0x157C8
>>  #define HDMI_CORE_I2CM_ADDRESS			0x157CC
>> @@ -258,6 +265,24 @@
>>  #define HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR	0x15810
>>  #define HDMI_CORE_I2CM_SDA_HOLD_ADDR		0x15814
>>  
>> +/* HDMI CEC */
>> +#define HDMI_CORE_CEC_CTRL			0x153C8
>> +#define CEC_CTRL_START				BIT(0)
>> +#define CEC_CTRL_FRAME_TYP			(3 << 1)
>> +#define CEC_CTRL_RETRY				(0 << 1)
>> +#define CEC_CTRL_NORMAL				(1 << 1)
>> +#define CEC_CTRL_IMMED				(2 << 1)
>> +
>> +#define HDMI_CORE_CEC_MASK			0x153D0
>> +#define HDMI_CORE_CEC_ADDR_L			0x153DC
>> +#define HDMI_CORE_CEC_ADDR_H			0x153E0
>> +#define HDMI_CORE_CEC_TX_CNT			0x153E4
>> +#define HDMI_CORE_CEC_RX_CNT			0x153E8
>> +#define HDMI_CORE_CEC_TX_DATA0			0x15408
>> +#define HDMI_CORE_CEC_RX_DATA0			0x15448
>> +#define HDMI_CORE_CEC_LOCK			0x15488
>> +#define HDMI_CORE_CEC_WKUPCTRL			0x1548C
>> +
>>  enum hdmi_core_packet_mode {
>>  	HDMI_PACKETMODERESERVEDVALUE = 0,
>>  	HDMI_PACKETMODE24BITPERPIXEL = 4,
>> @@ -290,6 +315,8 @@ int hdmi5_core_handle_irqs(struct hdmi_core_data *core);
>>  void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
>>  			struct hdmi_config *cfg);
>>  int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core);
>> +int hdmi5_core_enable(struct omap_hdmi *hdmi);
>> +void hdmi5_core_disable(struct omap_hdmi *hdmi);
>>  
>>  int hdmi5_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
>>  			struct omap_dss_audio *audio, u32 pclk);
>>
diff mbox series

Patch

diff --git a/drivers/gpu/drm/omapdrm/dss/Kconfig b/drivers/gpu/drm/omapdrm/dss/Kconfig
index e11b258a2294..67a1ba14703b 100644
--- a/drivers/gpu/drm/omapdrm/dss/Kconfig
+++ b/drivers/gpu/drm/omapdrm/dss/Kconfig
@@ -83,6 +83,14 @@  config OMAP5_DSS_HDMI
 	  Definition Multimedia Interface. See https://www.hdmi.org/ for HDMI
 	  specification.
 
+config OMAP5_DSS_HDMI_CEC
+	bool "Enable HDMI CEC support for OMAP5"
+	depends on OMAP5_DSS_HDMI
+	select CEC_CORE
+	default y
+	help
+	  When selected the HDMI transmitter will support the CEC feature.
+
 config OMAP2_DSS_SDI
 	bool "SDI support"
 	default n
diff --git a/drivers/gpu/drm/omapdrm/dss/Makefile b/drivers/gpu/drm/omapdrm/dss/Makefile
index f967e6948f2e..94fe0fa3b3c2 100644
--- a/drivers/gpu/drm/omapdrm/dss/Makefile
+++ b/drivers/gpu/drm/omapdrm/dss/Makefile
@@ -17,4 +17,5 @@  omapdss-$(CONFIG_OMAP2_DSS_HDMI_COMMON) += hdmi_common.o hdmi_wp.o hdmi_pll.o \
 omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi4.o hdmi4_core.o
 omapdss-$(CONFIG_OMAP4_DSS_HDMI_CEC) += hdmi4_cec.o
 omapdss-$(CONFIG_OMAP5_DSS_HDMI) += hdmi5.o hdmi5_core.o
+omapdss-$(CONFIG_OMAP5_DSS_HDMI_CEC) += hdmi5_cec.o
 ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h
index c4a4e07f0b99..72d8ae441da6 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h
@@ -261,6 +261,7 @@  struct hdmi_core_data {
 	struct hdmi_wp_data *wp;
 	unsigned int core_pwr_cnt;
 	struct cec_adapter *adap;
+	struct clk *cec_clk;
 };
 
 static inline void hdmi_write_reg(void __iomem *base_addr, const u32 idx,
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 54e5cb5aa52d..b674d8ba173f 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -29,12 +29,14 @@ 
 #include <linux/of.h>
 #include <linux/of_graph.h>
 #include <sound/omap-hdmi-audio.h>
+#include <media/cec.h>
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_state_helper.h>
 
 #include "omapdss.h"
 #include "hdmi5_core.h"
+#include "hdmi5_cec.h"
 #include "dss.h"
 
 static int hdmi_runtime_get(struct omap_hdmi *hdmi)
@@ -104,6 +106,10 @@  static irqreturn_t hdmi_irq_handler(int irq, void *data)
 	} else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
 		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON);
 	}
+	if (irqstatus & HDMI_IRQ_CORE) {
+		hdmi5_cec_irq(&hdmi->core);
+		hdmi5_core_handle_irqs(&hdmi->core);
+	}
 
 	return IRQ_HANDLED;
 }
@@ -112,9 +118,12 @@  static int hdmi_power_on_core(struct omap_hdmi *hdmi)
 {
 	int r;
 
+	if (hdmi->core.core_pwr_cnt++)
+		return 0;
+
 	r = regulator_enable(hdmi->vdda_reg);
 	if (r)
-		return r;
+		goto err_reg_enable;
 
 	r = hdmi_runtime_get(hdmi);
 	if (r)
@@ -129,12 +138,17 @@  static int hdmi_power_on_core(struct omap_hdmi *hdmi)
 
 err_runtime_get:
 	regulator_disable(hdmi->vdda_reg);
+err_reg_enable:
+	hdmi->core.core_pwr_cnt--;
 
 	return r;
 }
 
 static void hdmi_power_off_core(struct omap_hdmi *hdmi)
 {
+	if (--hdmi->core.core_pwr_cnt)
+		return;
+
 	hdmi->core_enabled = false;
 
 	hdmi_runtime_put(hdmi);
@@ -168,7 +182,7 @@  static int hdmi_power_on_full(struct omap_hdmi *hdmi)
 		pc, &hdmi_cinfo);
 
 	/* disable and clear irqs */
-	hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff);
+	hdmi_wp_clear_irqenable(&hdmi->wp, ~HDMI_IRQ_CORE);
 	hdmi_wp_set_irqstatus(&hdmi->wp,
 			hdmi_wp_get_irqstatus(&hdmi->wp));
 
@@ -225,7 +239,7 @@  static int hdmi_power_on_full(struct omap_hdmi *hdmi)
 
 static void hdmi_power_off_full(struct omap_hdmi *hdmi)
 {
-	hdmi_wp_clear_irqenable(&hdmi->wp, 0xffffffff);
+	hdmi_wp_clear_irqenable(&hdmi->wp, ~HDMI_IRQ_CORE);
 
 	hdmi_wp_video_stop(&hdmi->wp);
 
@@ -273,11 +287,11 @@  static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
 	REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2);
 }
 
-static int hdmi_core_enable(struct omap_hdmi *hdmi)
+int hdmi5_core_enable(struct omap_hdmi *hdmi)
 {
 	int r = 0;
 
-	DSSDBG("ENTER omapdss_hdmi_core_enable\n");
+	DSSDBG("ENTER %s\n", __func__);
 
 	mutex_lock(&hdmi->lock);
 
@@ -295,9 +309,9 @@  static int hdmi_core_enable(struct omap_hdmi *hdmi)
 	return r;
 }
 
-static void hdmi_core_disable(struct omap_hdmi *hdmi)
+void hdmi5_core_disable(struct omap_hdmi *hdmi)
 {
-	DSSDBG("Enter omapdss_hdmi_core_disable\n");
+	DSSDBG("ENTER %s\n", __func__);
 
 	mutex_lock(&hdmi->lock);
 
@@ -424,6 +438,15 @@  static void hdmi5_bridge_disable(struct drm_bridge *bridge,
 	mutex_unlock(&hdmi->lock);
 }
 
+static void hdmi5_bridge_hpd_notify(struct drm_bridge *bridge,
+				    enum drm_connector_status status)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	if (status == connector_status_disconnected)
+		hdmi5_cec_set_phys_addr(&hdmi->core, NULL);
+}
+
 static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
 					  struct drm_connector *connector)
 {
@@ -436,7 +459,7 @@  static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
 	need_enable = hdmi->core_enabled == false;
 
 	if (need_enable) {
-		r = hdmi_core_enable(hdmi);
+		r = hdmi5_core_enable(hdmi);
 		if (r)
 			return NULL;
 	}
@@ -460,12 +483,29 @@  static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
 	hdmi_runtime_put(hdmi);
 	mutex_unlock(&hdmi->lock);
 
+	hdmi5_cec_set_phys_addr(&hdmi->core, edid);
+
 	if (need_enable)
-		hdmi_core_disable(hdmi);
+		hdmi5_core_disable(hdmi);
 
 	return (struct edid *)edid;
 }
 
+static int hdmi5_bridge_cec_init(struct drm_bridge *bridge,
+				 struct drm_connector *conn)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	return hdmi5_cec_init(hdmi->pdev, &hdmi->core, &hdmi->wp, conn);
+}
+
+static void hdmi5_bridge_cec_exit(struct drm_bridge *bridge)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	hdmi5_cec_uninit(&hdmi->core);
+}
+
 static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
 	.attach = hdmi5_bridge_attach,
 	.mode_set = hdmi5_bridge_mode_set,
@@ -474,14 +514,17 @@  static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
 	.atomic_reset = drm_atomic_helper_bridge_reset,
 	.atomic_enable = hdmi5_bridge_enable,
 	.atomic_disable = hdmi5_bridge_disable,
+	.hpd_notify = hdmi5_bridge_hpd_notify,
 	.get_edid = hdmi5_bridge_get_edid,
+	.cec_init = hdmi5_bridge_cec_init,
+	.cec_exit = hdmi5_bridge_cec_exit,
 };
 
 static void hdmi5_bridge_init(struct omap_hdmi *hdmi)
 {
 	hdmi->bridge.funcs = &hdmi5_bridge_funcs;
 	hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
-	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
+	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_CEC;
 	hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
 
 	drm_bridge_add(&hdmi->bridge);
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
new file mode 100644
index 000000000000..26ef8f585b8d
--- /dev/null
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.c
@@ -0,0 +1,201 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HDMI CEC
+ *
+ * Copyright 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ */
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include "dss.h"
+#include "hdmi.h"
+#include "hdmi5_core.h"
+#include "hdmi5_cec.h"
+
+static int hdmi5_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
+{
+	struct hdmi_core_data *core = cec_get_drvdata(adap);
+	u8 v;
+
+	if (logical_addr == CEC_LOG_ADDR_INVALID) {
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_L, 0);
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, 0);
+		return 0;
+	}
+
+	if (logical_addr <= 7) {
+		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_L);
+		v |= 1 << logical_addr;
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_L, v);
+		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_H);
+		v |= 1 << 7;
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, v);
+	} else {
+		v = hdmi_read_reg(core->base, HDMI_CORE_CEC_ADDR_H);
+		v |= 1 << (logical_addr - 8);
+		v |= 1 << 7;
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_ADDR_H, v);
+	}
+
+	return 0;
+}
+
+static int hdmi5_cec_transmit(struct cec_adapter *adap, u8 attempts,
+			      u32 signal_free_time, struct cec_msg *msg)
+{
+	struct hdmi_core_data *core = cec_get_drvdata(adap);
+	unsigned int i, ctrl;
+
+	switch (signal_free_time) {
+	case CEC_SIGNAL_FREE_TIME_RETRY:
+		ctrl = CEC_CTRL_RETRY;
+		break;
+	case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
+	default:
+		ctrl = CEC_CTRL_NORMAL;
+		break;
+	case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
+		ctrl = CEC_CTRL_IMMED;
+		break;
+	}
+
+	for (i = 0; i < msg->len; i++)
+		hdmi_write_reg(core->base,
+			       HDMI_CORE_CEC_TX_DATA0 + i * 4, msg->msg[i]);
+
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_TX_CNT, msg->len);
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_CTRL,
+		       ctrl | CEC_CTRL_START);
+
+	return 0;
+}
+
+void hdmi5_cec_irq(struct hdmi_core_data *core)
+{
+	struct cec_adapter *adap = core->adap;
+	unsigned int stat = hdmi_read_reg(core->base, HDMI_CORE_IH_CEC_STAT0);
+
+	if (stat == 0)
+		return;
+
+	hdmi_write_reg(core->base, HDMI_CORE_IH_CEC_STAT0, stat);
+
+	if (stat & CEC_STAT_ERROR_INIT)
+		cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR);
+	else if (stat & CEC_STAT_DONE)
+		cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK);
+	else if (stat & CEC_STAT_NACK)
+		cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK);
+
+	if (stat & CEC_STAT_EOM) {
+		struct cec_msg msg = {};
+		unsigned int len, i;
+
+		len = hdmi_read_reg(core->base, HDMI_CORE_CEC_RX_CNT);
+		if (len > sizeof(msg.msg))
+			len = sizeof(msg.msg);
+
+		for (i = 0; i < len; i++)
+			msg.msg[i] =
+				hdmi_read_reg(core->base,
+					      HDMI_CORE_CEC_RX_DATA0 + i * 4);
+
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_LOCK, 0);
+
+		msg.len = len;
+		cec_received_msg(adap, &msg);
+	}
+}
+
+static int hdmi5_cec_enable(struct cec_adapter *adap, bool enable)
+{
+	struct hdmi_core_data *core = cec_get_drvdata(adap);
+	struct omap_hdmi *hdmi = container_of(core, struct omap_hdmi, core);
+	unsigned int irqs;
+	int err;
+
+	if (!enable) {
+		hdmi_write_reg(core->base, HDMI_CORE_CEC_MASK, ~0);
+		hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_CEC_STAT0, ~0);
+		hdmi_wp_clear_irqenable(core->wp, HDMI_IRQ_CORE);
+		hdmi_wp_set_irqstatus(core->wp, HDMI_IRQ_CORE);
+		REG_FLD_MOD(core->base, HDMI_CORE_MC_CLKDIS, 0x01, 5, 5);
+		hdmi5_core_disable(hdmi);
+		return 0;
+	}
+	err = hdmi5_core_enable(hdmi);
+	if (err)
+		return err;
+
+	REG_FLD_MOD(core->base, HDMI_CORE_MC_CLKDIS, 0x00, 5, 5);
+	hdmi_write_reg(core->base, HDMI_CORE_IH_I2CM_STAT0, ~0);
+	hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_I2CM_STAT0, ~0);
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_CTRL, 0);
+	hdmi_write_reg(core->base, HDMI_CORE_IH_CEC_STAT0, ~0);
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_LOCK, 0);
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_TX_CNT, 0);
+
+	hdmi5_cec_log_addr(adap, CEC_LOG_ADDR_INVALID);
+
+	/* Enable HDMI core interrupts */
+	hdmi_wp_set_irqenable(core->wp, HDMI_IRQ_CORE);
+
+	irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM |
+	       CEC_STAT_DONE;
+	hdmi_write_reg(core->base, HDMI_CORE_CEC_MASK, ~irqs);
+	hdmi_write_reg(core->base, HDMI_CORE_IH_MUTE_CEC_STAT0, ~irqs);
+	return 0;
+}
+
+static const struct cec_adap_ops hdmi5_cec_ops = {
+	.adap_enable = hdmi5_cec_enable,
+	.adap_log_addr = hdmi5_cec_log_addr,
+	.adap_transmit = hdmi5_cec_transmit,
+};
+
+void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core, struct edid *edid)
+{
+	cec_s_phys_addr_from_edid(core->adap, edid);
+}
+
+int hdmi5_cec_init(struct platform_device *pdev, struct hdmi_core_data *core,
+		   struct hdmi_wp_data *wp, struct drm_connector *conn)
+{
+	const u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO;
+	struct cec_connector_info conn_info;
+	unsigned int ret;
+
+	core->cec_clk = devm_clk_get(&pdev->dev, "cec");
+	if (IS_ERR(core->cec_clk))
+		return PTR_ERR(core->cec_clk);
+	ret = clk_prepare_enable(core->cec_clk);
+	if (ret)
+		return ret;
+
+	core->adap = cec_allocate_adapter(&hdmi5_cec_ops, core,
+					  "omap5", caps, CEC_MAX_LOG_ADDRS);
+	ret = PTR_ERR_OR_ZERO(core->adap);
+	if (ret < 0)
+		return ret;
+	cec_fill_conn_info_from_drm(&conn_info, conn);
+	cec_s_conn_info(core->adap, &conn_info);
+	core->wp = wp;
+
+	ret = cec_register_adapter(core->adap, &pdev->dev);
+	if (ret < 0) {
+		cec_delete_adapter(core->adap);
+		return ret;
+	}
+	return 0;
+}
+
+void hdmi5_cec_uninit(struct hdmi_core_data *core)
+{
+	clk_disable_unprepare(core->cec_clk);
+	cec_unregister_adapter(core->adap);
+}
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h
new file mode 100644
index 000000000000..904541da46da
--- /dev/null
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_cec.h
@@ -0,0 +1,42 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * HDMI header definition for OMAP5 HDMI CEC IP
+ *
+ * Copyright 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ */
+
+#ifndef _HDMI5_CEC_H_
+#define _HDMI5_CEC_H_
+
+/* HDMI CEC funcs */
+#ifdef CONFIG_OMAP5_DSS_HDMI_CEC
+void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core,
+			     struct edid *edid);
+void hdmi5_cec_irq(struct hdmi_core_data *core);
+int hdmi5_cec_init(struct platform_device *pdev, struct hdmi_core_data *core,
+		   struct hdmi_wp_data *wp, struct drm_connector *conn);
+void hdmi5_cec_uninit(struct hdmi_core_data *core);
+#else
+static inline void hdmi5_cec_set_phys_addr(struct hdmi_core_data *core,
+					   struct edid *edid)
+{
+}
+
+static inline void hdmi5_cec_irq(struct hdmi_core_data *core)
+{
+}
+
+static inline int hdmi5_cec_init(struct platform_device *pdev,
+				 struct hdmi_core_data *core,
+				 struct hdmi_wp_data *wp,
+				 struct drm_connector *conn)
+{
+	return 0;
+}
+
+static inline void hdmi5_cec_uninit(struct hdmi_core_data *core)
+{
+}
+#endif
+
+#endif
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
index 6cc2ad7a420c..13bc0f3d850b 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
@@ -229,6 +229,19 @@  void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s)
 	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_1_ADDR);
 	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR);
 	DUMPCORE(HDMI_CORE_I2CM_SDA_HOLD_ADDR);
+
+	DUMPCORE(HDMI_CORE_IH_CEC_STAT0);
+	DUMPCORE(HDMI_CORE_IH_MUTE_CEC_STAT0);
+	DUMPCORE(HDMI_CORE_CEC_CTRL);
+	DUMPCORE(HDMI_CORE_CEC_MASK);
+	DUMPCORE(HDMI_CORE_CEC_ADDR_L);
+	DUMPCORE(HDMI_CORE_CEC_ADDR_H);
+	DUMPCORE(HDMI_CORE_CEC_TX_CNT);
+	DUMPCORE(HDMI_CORE_CEC_RX_CNT);
+	DUMPCORE(HDMI_CORE_CEC_TX_DATA0);
+	DUMPCORE(HDMI_CORE_CEC_RX_DATA0);
+	DUMPCORE(HDMI_CORE_CEC_LOCK);
+	DUMPCORE(HDMI_CORE_CEC_WKUPCTRL);
 }
 
 static void hdmi_core_init(struct hdmi_core_vid_config *video_cfg,
@@ -513,8 +526,6 @@  static void hdmi_core_mask_interrupts(struct hdmi_core_data *core)
 	REG_FLD_MOD(base, HDMI_CORE_AUD_INT, 0x3, 3, 2);
 	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_MASK, 0x3, 1, 0);
 
-	REG_FLD_MOD(base, HDMI_CORE_CEC_MASK, 0x7f, 6, 0);
-
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 6, 6);
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 2, 2);
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2);
@@ -532,8 +543,6 @@  static void hdmi_core_mask_interrupts(struct hdmi_core_data *core)
 
 	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0x7, 2, 0);
 
-	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0x7f, 6, 0);
-
 	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0x3, 1, 0);
 
 	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0);
@@ -549,13 +558,17 @@  int hdmi5_core_handle_irqs(struct hdmi_core_data *core)
 {
 	void __iomem *base = core->base;
 
+	/*
+	 * Clear all possible IRQ_CORE interrupts except for
+	 * HDMI_CORE_IH_I2CM_STAT0 (that interrupt is muted and
+	 * is handled by polling elsewhere) and HDMI_CORE_IH_CEC_STAT0
+	 * which is handled by the CEC code.
+	 */
 	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT0, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT1, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT2, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0);
-	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0xff, 7, 0);
-	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_VP_STAT0, 0xff, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_IH_I2CMPHY_STAT0, 0xff, 7, 0);
 
@@ -879,5 +892,8 @@  int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core)
 	if (IS_ERR(core->base))
 		return PTR_ERR(core->base);
 
+	REG_FLD_MOD(core->base, HDMI_CORE_CEC_MASK, 0x7f, 6, 0);
+	REG_FLD_MOD(core->base, HDMI_CORE_IH_CEC_STAT0, 0x7f, 6, 0);
+
 	return 0;
 }
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
index 070cbf5fb57d..a83b634f6011 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
@@ -30,8 +30,18 @@ 
 #define HDMI_CORE_IH_PHY_STAT0			0x00410
 #define HDMI_CORE_IH_I2CM_STAT0			0x00414
 #define HDMI_CORE_IH_CEC_STAT0			0x00418
+#define CEC_STAT_DONE				BIT(0)
+#define CEC_STAT_EOM				BIT(1)
+#define CEC_STAT_NACK				BIT(2)
+#define CEC_STAT_ARBLOST			BIT(3)
+#define CEC_STAT_ERROR_INIT			BIT(4)
+#define CEC_STAT_ERROR_FOLL			BIT(5)
+#define CEC_STAT_WAKEUP				BIT(6)
+
 #define HDMI_CORE_IH_VP_STAT0			0x0041C
 #define HDMI_CORE_IH_I2CMPHY_STAT0		0x00420
+#define HDMI_CORE_IH_MUTE_I2CM_STAT0            0x00614
+#define HDMI_CORE_IH_MUTE_CEC_STAT0		0x00618
 #define HDMI_CORE_IH_MUTE			0x007FC
 
 /* HDMI Video Sampler */
@@ -233,9 +243,6 @@ 
 /* HDMI HDCP */
 #define HDMI_CORE_HDCP_MASK			0x14020
 
-/* HDMI CEC */
-#define HDMI_CORE_CEC_MASK			0x17408
-
 /* HDMI I2C Master */
 #define HDMI_CORE_I2CM_SLAVE			0x157C8
 #define HDMI_CORE_I2CM_ADDRESS			0x157CC
@@ -258,6 +265,24 @@ 
 #define HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR	0x15810
 #define HDMI_CORE_I2CM_SDA_HOLD_ADDR		0x15814
 
+/* HDMI CEC */
+#define HDMI_CORE_CEC_CTRL			0x153C8
+#define CEC_CTRL_START				BIT(0)
+#define CEC_CTRL_FRAME_TYP			(3 << 1)
+#define CEC_CTRL_RETRY				(0 << 1)
+#define CEC_CTRL_NORMAL				(1 << 1)
+#define CEC_CTRL_IMMED				(2 << 1)
+
+#define HDMI_CORE_CEC_MASK			0x153D0
+#define HDMI_CORE_CEC_ADDR_L			0x153DC
+#define HDMI_CORE_CEC_ADDR_H			0x153E0
+#define HDMI_CORE_CEC_TX_CNT			0x153E4
+#define HDMI_CORE_CEC_RX_CNT			0x153E8
+#define HDMI_CORE_CEC_TX_DATA0			0x15408
+#define HDMI_CORE_CEC_RX_DATA0			0x15448
+#define HDMI_CORE_CEC_LOCK			0x15488
+#define HDMI_CORE_CEC_WKUPCTRL			0x1548C
+
 enum hdmi_core_packet_mode {
 	HDMI_PACKETMODERESERVEDVALUE = 0,
 	HDMI_PACKETMODE24BITPERPIXEL = 4,
@@ -290,6 +315,8 @@  int hdmi5_core_handle_irqs(struct hdmi_core_data *core);
 void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
 			struct hdmi_config *cfg);
 int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core);
+int hdmi5_core_enable(struct omap_hdmi *hdmi);
+void hdmi5_core_disable(struct omap_hdmi *hdmi);
 
 int hdmi5_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
 			struct omap_dss_audio *audio, u32 pclk);