From patchwork Tue Apr 18 18:10:44 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnaud Vrac X-Patchwork-Id: 13216054 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 750CCC7EE20 for ; Tue, 18 Apr 2023 18:11:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232594AbjDRSLe (ORCPT ); Tue, 18 Apr 2023 14:11:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:45542 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232576AbjDRSLa (ORCPT ); Tue, 18 Apr 2023 14:11:30 -0400 Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B9CCC118CD for ; Tue, 18 Apr 2023 11:11:19 -0700 (PDT) Received: by mail-wr1-x429.google.com with SMTP id ffacd0b85a97d-2fde2879eabso527222f8f.1 for ; Tue, 18 Apr 2023 11:11:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebox-fr.20221208.gappssmtp.com; s=20221208; t=1681841478; x=1684433478; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=c4bhC/0/CKuJr3jqGcugEl39w3nLiuBCBXEG6Ax9dVM=; b=SavY056WeyOK9jInVRVG3l005ZdEhJMFi0HqknfCrLVD6023xPGVHY7VtnIa/JS7OD k/w9ql44blqbEQItnV+gdrZsfd4zPzEFLRo+yqis+RU1iC+lMEClqNgZDM4IyX+nbk4j W6z7DCqjmW4drJGOwO/pUMXQ4GP/dHPVw1hAG073eMxuKo36KRKU3ZEqQ15z5rSy4H1y xqNEMgrhd7eWFNeH8ehmw0VUwOrM5gAQ8xowkFzizaa70C2R6SdJu5Kinaaq7MtG+6vx UfQCBBtJSKRol/s7sUB/AExMNixCmyJyqG58xv3FNU/UJ9qIq/frIkc/cSnh7Ffx93JH 0A4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681841478; x=1684433478; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=c4bhC/0/CKuJr3jqGcugEl39w3nLiuBCBXEG6Ax9dVM=; b=Dj8AjsRXC1DR6Ku0hsArsTktfXPurpncYbEQjod2f9AHNNOLIRx1Yl1nMIDEmblgM1 HMYPqaZu87nO/n0RZ4bpcgxlbqBz+aIvAK1NNDDsuTEhuB6kgShxn9DSgxzRx2H55fgs arryNiSrT+ATIJuRMrvoVH7Pn32HfGbyyc4q5q2tOGXyxJtlhcmF1wrrlJuEcd7PsoGf XWCb6SPYzzVmRMCObvkyPytZXuXwirwR32O4I5ilvu7Kn+hJ3074TqoSNIsebDUHgCu7 IAesHJG0JHYoRLXmOMmYJuq43A8GUo7BmnEMKdy0hNY7vWDjYPdFESgQeh7JlHV5o9hG JDCw== X-Gm-Message-State: AAQBX9esXDkGbAkBWtsDIScEjcpwzhPPoMfHNX/Mjf36jfVmQiF1Fh8O ch5z2NxUXCSxVZKjkuBv86YO X-Google-Smtp-Source: AKy350YFtEg0jfESweKQpcZGebM0uznJYWS53MmXHamwXCQH0xO6hXXKm7dHZzCXifgRuAcjnEFGtg== X-Received: by 2002:a05:6000:12c1:b0:2f8:8e65:7339 with SMTP id l1-20020a05600012c100b002f88e657339mr2226386wrx.18.1681841477946; Tue, 18 Apr 2023 11:11:17 -0700 (PDT) Received: from [127.0.1.1] (freebox.vlq16.iliad.fr. [213.36.7.13]) by smtp.gmail.com with ESMTPSA id r17-20020a5d4e51000000b002f01e181c4asm13727898wrt.5.2023.04.18.11.11.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 18 Apr 2023 11:11:17 -0700 (PDT) From: Arnaud Vrac Date: Tue, 18 Apr 2023 20:10:44 +0200 Subject: [PATCH 2/4] drm/msm: add hdmi cec support MIME-Version: 1.0 Message-Id: <20230418-msm8998-hdmi-cec-v1-2-176479fb2fce@freebox.fr> References: <20230418-msm8998-hdmi-cec-v1-0-176479fb2fce@freebox.fr> In-Reply-To: <20230418-msm8998-hdmi-cec-v1-0-176479fb2fce@freebox.fr> To: Rob Clark , Hans Verkuil , Abhinav Kumar , Dmitry Baryshkov , Sean Paul , Andy Gross , Bjorn Andersson , Konrad Dybcio , Rob Herring , Krzysztof Kozlowski Cc: David Airlie , Daniel Vetter , linux-media@vger.kernel.org, linux-arm-msm@vger.kernel.org, dri-devel@lists.freedesktop.org, freedreno@lists.freedesktop.org, devicetree@vger.kernel.org, Arnaud Vrac X-Mailer: b4 0.12.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=12091; i=avrac@freebox.fr; h=from:subject:message-id; bh=oUde66qHUtY/Iy3OeVfYl6UO61Y9HAsr6+CW5la4fOA=; b=owEBbQKS/ZANAwAIAXED/9m7NDSrAcsmYgBkPt0+4eICf2235GeN+nmVx5Dd/f5/DEem6xSyZ Pa50jBvKLmJAjMEAAEIAB0WIQSUwb/ndwkXHdZ/QQZxA//ZuzQ0qwUCZD7dPgAKCRBxA//ZuzQ0 q+oOEACI77yKyXBZt7FRVV6trtAwNFceM4Njls+01wXXn6Ik7YgRTlaLF6DGEomowTF8FKQ0b6u PfQqSBBqL+VF6eJlEF+gPv5k22Rl41WyuBVA9Dw36uvCvkRCua94XXG5ATW1qnhGlOB9yDyybuf J8Lv5FU8fypvd+WmSJocznV8qETUu19lujOglUDho+DF8QB9gohFj+2Fe9VM10qB5FGuYdd5HHh wXDpuQFCpYMbCq/kIehIx459IJROgQYxkymowwFTRjYw5hiC5id18MgyAktsMTX/6wVw3fidJD0 ACRk7YM1EzI3+NyAQlEVKP4QYKjseanHCWClNQclnmGITAJg/FO4NyQhB+8aCgGG5ilEHysBAS6 71oPjkD770sQN/rfHeyfkdeqXPKlSaY9+NwRrVa11O0nouMOuNEAwg/+FOgEUe9c5jOLJ+iGQcR Br9453fims+VYbuaFRUit8uZNjSnWInVlN0nyyBK/LQynzNwlRNCMwhIzcbQ0XbhXmQnXDDHGm3 q4OCTkOCbSf9FYkYBDsaqqRDAX7rYNJnoNzJK19HD13OFmFbF+VfIvkVMPposKPjplzOOoJAtFP DhHpJR8y+jiIPQRr8PGcupWQjgD7HJq+yO9sj4YpfU0KchOPw0xURQZKHiV2cSp+gPcMXJVxwy5 4DTLPUwC+h6lL4Q== X-Developer-Key: i=avrac@freebox.fr; a=openpgp; fpr=6225092072BB58E3CEEC091E75392A176D952DB4 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Some Qualcomm SoCs that support HDMI also support CEC, including MSM8996 and MSM8998. The hardware block can handle a single CEC logical address and broadcast messages. Port the CEC driver from downstream msm-4.4 kernel. It has been tested on MSM8998 and passes the cec-compliance tool tests. Signed-off-by: Arnaud Vrac --- drivers/gpu/drm/msm/Kconfig | 8 ++ drivers/gpu/drm/msm/Makefile | 1 + drivers/gpu/drm/msm/hdmi/hdmi.c | 15 ++ drivers/gpu/drm/msm/hdmi/hdmi.h | 18 +++ drivers/gpu/drm/msm/hdmi/hdmi_cec.c | 280 ++++++++++++++++++++++++++++++++++++ 5 files changed, 322 insertions(+) diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 85f5ab1d552c4..2a02c74207935 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -165,3 +165,11 @@ config DRM_MSM_HDMI_HDCP default y help Choose this option to enable HDCP state machine + +config DRM_MSM_HDMI_CEC + bool "Enable HDMI CEC support in MSM DRM driver" + depends on DRM_MSM && DRM_MSM_HDMI + select CEC_CORE + default y + help + Choose this option to enable CEC support diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 7274c41228ed9..0237a2f219ac2 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -131,6 +131,7 @@ msm-$(CONFIG_DRM_MSM_DP)+= dp/dp_aux.o \ msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o +msm-$(CONFIG_DRM_MSM_HDMI_CEC) += hdmi/hdmi_cec.o msm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index 3132105a2a433..1dde3890e25c0 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -11,6 +11,8 @@ #include #include +#include + #include #include "hdmi.h" @@ -53,6 +55,9 @@ static irqreturn_t msm_hdmi_irq(int irq, void *dev_id) if (hdmi->hdcp_ctrl) msm_hdmi_hdcp_irq(hdmi->hdcp_ctrl); + /* Process CEC: */ + msm_hdmi_cec_irq(hdmi); + /* TODO audio.. */ return IRQ_HANDLED; @@ -66,6 +71,8 @@ static void msm_hdmi_destroy(struct hdmi *hdmi) */ if (hdmi->workq) destroy_workqueue(hdmi->workq); + + msm_hdmi_cec_exit(hdmi); msm_hdmi_hdcp_destroy(hdmi); if (hdmi->i2c) @@ -139,6 +146,8 @@ static int msm_hdmi_init(struct hdmi *hdmi) hdmi->hdcp_ctrl = NULL; } + msm_hdmi_cec_init(hdmi); + return 0; fail: @@ -198,6 +207,12 @@ int msm_hdmi_modeset_init(struct hdmi *hdmi, drm_connector_attach_encoder(hdmi->connector, hdmi->encoder); + if (hdmi->cec_adap) { + struct cec_connector_info conn_info; + cec_fill_conn_info_from_drm(&conn_info, hdmi->connector); + cec_s_conn_info(hdmi->cec_adap, &conn_info); + } + ret = devm_request_irq(dev->dev, hdmi->irq, msm_hdmi_irq, IRQF_TRIGGER_HIGH, "hdmi_isr", hdmi); diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index e8dbee50637fa..c639bd87f4b8f 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -29,6 +29,7 @@ struct hdmi_audio { }; struct hdmi_hdcp_ctrl; +struct cec_adapter; struct hdmi { struct drm_device *dev; @@ -73,6 +74,7 @@ struct hdmi { struct workqueue_struct *workq; struct hdmi_hdcp_ctrl *hdcp_ctrl; + struct cec_adapter *cec_adap; /* * spinlock to protect registers shared by different execution @@ -261,4 +263,20 @@ static inline void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) {} static inline void msm_hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl) {} #endif +/* + * cec + */ +#ifdef CONFIG_DRM_MSM_HDMI_CEC +int msm_hdmi_cec_init(struct hdmi *hdmi); +void msm_hdmi_cec_exit(struct hdmi *hdmi); +void msm_hdmi_cec_irq(struct hdmi *hdmi); +#else +static inline int msm_hdmi_cec_init(struct hdmi *hdmi) +{ + return -ENXIO; +} +static inline void msm_hdmi_cec_exit(struct hdmi *hdmi) {} +static inline void msm_hdmi_cec_irq(struct hdmi *hdmi) {} +#endif + #endif /* __HDMI_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_cec.c b/drivers/gpu/drm/msm/hdmi/hdmi_cec.c new file mode 100644 index 0000000000000..51326e493e5da --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_cec.c @@ -0,0 +1,280 @@ +#include +#include + +#include "hdmi.h" + +#define HDMI_CEC_INT_MASK ( \ + HDMI_CEC_INT_TX_DONE_MASK | \ + HDMI_CEC_INT_TX_ERROR_MASK | \ + HDMI_CEC_INT_RX_DONE_MASK) + +struct hdmi_cec_ctrl { + struct hdmi *hdmi; + struct work_struct work; + spinlock_t lock; + u32 irq_status; + u32 tx_status; + u32 tx_retransmits; +}; + +static int msm_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct hdmi_cec_ctrl *cec_ctrl = adap->priv; + struct hdmi *hdmi = cec_ctrl->hdmi; + + if (enable) { + /* timer frequency, 19.2Mhz * 0.05ms / 1000ms = 960 */ + hdmi_write(hdmi, REG_HDMI_CEC_REFTIMER, + HDMI_CEC_REFTIMER_REFTIMER(960) | + HDMI_CEC_REFTIMER_ENABLE); + + /* read and write timings */ + hdmi_write(hdmi, REG_HDMI_CEC_RD_RANGE, 0x30AB9888); + hdmi_write(hdmi, REG_HDMI_CEC_WR_RANGE, 0x888AA888); + hdmi_write(hdmi, REG_HDMI_CEC_RD_START_RANGE, 0x88888888); + hdmi_write(hdmi, REG_HDMI_CEC_RD_TOTAL_RANGE, 0x99); + + /* start bit low pulse duration, 3.7ms */ + hdmi_write(hdmi, REG_HDMI_CEC_RD_ERR_RESP_LO, 74); + + /* signal free time, 7 * 2.4ms */ + hdmi_write(hdmi, REG_HDMI_CEC_TIME, + HDMI_CEC_TIME_SIGNAL_FREE_TIME(7 * 48) | + HDMI_CEC_TIME_ENABLE); + + hdmi_write(hdmi, REG_HDMI_CEC_COMPL_CTL, 0xF); + hdmi_write(hdmi, REG_HDMI_CEC_WR_CHECK_CONFIG, 0x4); + hdmi_write(hdmi, REG_HDMI_CEC_RD_FILTER, BIT(0) | (0x7FF << 4)); + + hdmi_write(hdmi, REG_HDMI_CEC_INT, HDMI_CEC_INT_MASK); + hdmi_write(hdmi, REG_HDMI_CEC_CTRL, HDMI_CEC_CTRL_ENABLE); + } else { + hdmi_write(hdmi, REG_HDMI_CEC_INT, 0); + hdmi_write(hdmi, REG_HDMI_CEC_CTRL, 0); + cancel_work_sync(&cec_ctrl->work); + } + + return 0; +} + +static int msm_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct hdmi_cec_ctrl *cec_ctrl = adap->priv; + struct hdmi *hdmi = cec_ctrl->hdmi; + + hdmi_write(hdmi, REG_HDMI_CEC_ADDR, logical_addr & 0xF); + + return 0; +} + +static int msm_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct hdmi_cec_ctrl *cec_ctrl = adap->priv; + struct hdmi *hdmi = cec_ctrl->hdmi; + u8 retransmits; + u32 broadcast; + u32 status; + int i; + + /* toggle cec in order to flush out bad hw state, if any */ + hdmi_write(hdmi, REG_HDMI_CEC_CTRL, 0); + hdmi_write(hdmi, REG_HDMI_CEC_CTRL, HDMI_CEC_CTRL_ENABLE); + + /* flush register writes */ + wmb(); + + retransmits = attempts ? (attempts - 1) : 0; + hdmi_write(hdmi, REG_HDMI_CEC_RETRANSMIT, + HDMI_CEC_RETRANSMIT_ENABLE | + HDMI_CEC_RETRANSMIT_COUNT(retransmits)); + + broadcast = cec_msg_is_broadcast(msg) ? HDMI_CEC_WR_DATA_BROADCAST : 0; + for (i = 0; i < msg->len; i++) { + hdmi_write(hdmi, REG_HDMI_CEC_WR_DATA, + HDMI_CEC_WR_DATA_DATA(msg->msg[i]) | broadcast); + } + + /* check line status */ + if (read_poll_timeout(hdmi_read, status, !(status & HDMI_CEC_STATUS_BUSY), + 5, 1000, false, hdmi, REG_HDMI_CEC_STATUS)) { + pr_err("CEC line is busy. Retry failed\n"); + return -EBUSY; + } + + cec_ctrl->tx_retransmits = retransmits; + + /* start transmission */ + hdmi_write(hdmi, REG_HDMI_CEC_CTRL, + HDMI_CEC_CTRL_ENABLE | + HDMI_CEC_CTRL_SEND_TRIGGER | + HDMI_CEC_CTRL_FRAME_SIZE(msg->len) | + HDMI_CEC_CTRL_LINE_OE); + + return 0; +} + +static void msm_hdmi_cec_adap_free(struct cec_adapter *adap) +{ + struct hdmi_cec_ctrl *cec_ctrl = adap->priv; + + cec_ctrl->hdmi->cec_adap = NULL; + kfree(cec_ctrl); +} + +static const struct cec_adap_ops msm_hdmi_cec_adap_ops = { + .adap_enable = msm_hdmi_cec_adap_enable, + .adap_log_addr = msm_hdmi_cec_adap_log_addr, + .adap_transmit = msm_hdmi_cec_adap_transmit, + .adap_free = msm_hdmi_cec_adap_free, +}; + +#define CEC_IRQ_FRAME_WR_DONE 0x01 +#define CEC_IRQ_FRAME_RD_DONE 0x02 + +static void msm_hdmi_cec_handle_rx_done(struct hdmi_cec_ctrl *cec_ctrl) +{ + struct hdmi *hdmi = cec_ctrl->hdmi; + struct cec_msg msg = {}; + u32 data; + int i; + + data = hdmi_read(hdmi, REG_HDMI_CEC_RD_DATA); + msg.len = (data & 0x1f00) >> 8; + if (msg.len < 1 || msg.len > CEC_MAX_MSG_SIZE) + return; + + msg.msg[0] = data & 0xff; + for (i = 1; i < msg.len; i++) + msg.msg[i] = hdmi_read(hdmi, REG_HDMI_CEC_RD_DATA) & 0xff; + + cec_received_msg(hdmi->cec_adap, &msg); +} + +static void msm_hdmi_cec_handle_tx_done(struct hdmi_cec_ctrl *cec_ctrl) +{ + struct hdmi *hdmi = cec_ctrl->hdmi; + u32 tx_status; + + tx_status = (cec_ctrl->tx_status & HDMI_CEC_STATUS_TX_STATUS__MASK) >> + HDMI_CEC_STATUS_TX_STATUS__SHIFT; + + switch (tx_status) { + case 0: + cec_transmit_done(hdmi->cec_adap, + CEC_TX_STATUS_OK, 0, 0, 0, 0); + break; + case 1: + cec_transmit_done(hdmi->cec_adap, + CEC_TX_STATUS_NACK, 0, 1, 0, 0); + break; + case 2: + cec_transmit_done(hdmi->cec_adap, + CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0); + break; + case 3: + cec_transmit_done(hdmi->cec_adap, + CEC_TX_STATUS_MAX_RETRIES | + CEC_TX_STATUS_NACK, + 0, cec_ctrl->tx_retransmits + 1, 0, 0); + break; + default: + cec_transmit_done(hdmi->cec_adap, + CEC_TX_STATUS_ERROR, 0, 0, 0, 1); + break; + } +} + +static void msm_hdmi_cec_work(struct work_struct *work) +{ + struct hdmi_cec_ctrl *cec_ctrl = + container_of(work, struct hdmi_cec_ctrl, work); + unsigned long flags; + + spin_lock_irqsave(&cec_ctrl->lock, flags); + + if (cec_ctrl->irq_status & CEC_IRQ_FRAME_WR_DONE) + msm_hdmi_cec_handle_tx_done(cec_ctrl); + + if (cec_ctrl->irq_status & CEC_IRQ_FRAME_RD_DONE) + msm_hdmi_cec_handle_rx_done(cec_ctrl); + + cec_ctrl->irq_status = 0; + cec_ctrl->tx_status = 0; + + spin_unlock_irqrestore(&cec_ctrl->lock, flags); +} + +void msm_hdmi_cec_irq(struct hdmi *hdmi) +{ + struct hdmi_cec_ctrl *cec_ctrl; + unsigned long flags; + u32 int_status; + + if (!hdmi->cec_adap) + return; + + cec_ctrl = hdmi->cec_adap->priv; + + int_status = hdmi_read(hdmi, REG_HDMI_CEC_INT); + if (!(int_status & HDMI_CEC_INT_MASK)) + return; + + spin_lock_irqsave(&cec_ctrl->lock, flags); + + if (int_status & (HDMI_CEC_INT_TX_DONE | HDMI_CEC_INT_TX_ERROR)) { + cec_ctrl->tx_status = hdmi_read(hdmi, REG_HDMI_CEC_STATUS); + cec_ctrl->irq_status |= CEC_IRQ_FRAME_WR_DONE; + } + + if (int_status & HDMI_CEC_INT_RX_DONE) + cec_ctrl->irq_status |= CEC_IRQ_FRAME_RD_DONE; + + spin_unlock_irqrestore(&cec_ctrl->lock, flags); + + hdmi_write(hdmi, REG_HDMI_CEC_INT, int_status); + queue_work(hdmi->workq, &cec_ctrl->work); +} + +int msm_hdmi_cec_init(struct hdmi *hdmi) +{ + struct platform_device *pdev = hdmi->pdev; + struct hdmi_cec_ctrl *cec_ctrl; + struct cec_adapter *cec_adap; + int ret; + + cec_ctrl = kzalloc(sizeof (*cec_ctrl), GFP_KERNEL); + if (!cec_ctrl) + return -ENOMEM; + + cec_ctrl->hdmi = hdmi; + INIT_WORK(&cec_ctrl->work, msm_hdmi_cec_work); + + cec_adap = cec_allocate_adapter(&msm_hdmi_cec_adap_ops, + cec_ctrl, "msm", + CEC_CAP_DEFAULTS | + CEC_CAP_CONNECTOR_INFO, 1); + ret = PTR_ERR_OR_ZERO(cec_adap); + if (ret < 0) { + kfree(cec_ctrl); + return ret; + } + + /* Set the logical address to Unregistered */ + hdmi_write(hdmi, REG_HDMI_CEC_ADDR, 0xf); + + ret = cec_register_adapter(cec_adap, &pdev->dev); + if (ret < 0) { + cec_delete_adapter(cec_adap); + return ret; + } + + hdmi->cec_adap = cec_adap; + + return 0; +} + +void msm_hdmi_cec_exit(struct hdmi *hdmi) +{ + cec_unregister_adapter(hdmi->cec_adap); +}