summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@quicinc.com>2017-06-05 04:34:38 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2017-06-05 04:34:38 -0700
commit53a8c2aba36b922153b0346647902573eba3adfc (patch)
tree97d0dc84feefde5a16bb8264ca787c3edfb0d1e2
parent21852febc31e52b6fa3a2e5da2c24d213b343ea3 (diff)
parent1188a3afd2548bc423d6bceccd5226aa4a5d0d55 (diff)
Merge "msm: cec: Add CEC adapter driver for SDE hardware"
-rw-r--r--Documentation/devicetree/bindings/display/msm/cec.txt73
-rw-r--r--drivers/media/platform/msm/sde/Kconfig11
-rw-r--r--drivers/media/platform/msm/sde/Makefile1
-rw-r--r--drivers/media/platform/msm/sde/cec/Makefile3
-rw-r--r--drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c399
-rw-r--r--drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.c743
-rw-r--r--drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.h93
7 files changed, 1323 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/display/msm/cec.txt b/Documentation/devicetree/bindings/display/msm/cec.txt
new file mode 100644
index 000000000000..ba51b0d1dd18
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/cec.txt
@@ -0,0 +1,73 @@
+Qualcomm Technologies, Inc. CEC device
+
+CEC is a protocol that provides high-level control functions between all of the
+various audiovisual products in a user environment.
+
+Required properties:
+- compatible: Must be "qcom,hdmi-cec".
+- interrupt-parent: Must be the hdmi interrupt controller.
+- interrupts: Interrupt associated with cec.
+- reg: Physical base address and length of the controller's registers.
+- reg-names: "hdmi_cec".
+- clocks: List of Phandles for clock device nodes needed by the device.
+- clock-names: List of clock names needed by the device.
+- pinctrl-names: Should contain only two values: "cec_active" and "cec_sleep" which stands for the
+ active and sleep state of pinctrl used in this CEC driver.
+- pinctrl-0: The active pinctrl state which is a list of phandles pointing to a pin configuration node.
+- pinctrl-1: The sleep pinctrl state which is a list of phandles pointing to a pin configuration node.
+- cec-gdsc-supply: Phandle for cec gdsc supply regulator device node.
+- qcom,platform-supply-entries: A sub node that lists the elements of the supply. There can be more
+ than one instance of this binding, in which case the entry would be
+ appended with the supply entry index. e.g. qcom,platform-supply-entry@0.
+ -- reg: offset and length of the register set for the device.
+ -- qcom,supply-name: name of the supply (vdd/vdda/vddio).
+ -- qcom,supply-min-voltage: minimum voltage level (uV).
+ -- qcom,supply-max-voltage: maximum voltage level (uV).
+ -- qcom,supply-enable-load: load drawn (uA) from enabled supply.
+ -- qcom,supply-disable-load: load drawn (uA) from disabled supply.
+
+
+Optional properties:
+- qcom,platform-supply-entries: A sub node that lists the elements of the supply. There can be more
+ than one instance of this binding, in which case the entry would be
+ appended with the supply entry index. e.g. qcom,platform-supply-entry@0.
+ -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on.
+ -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on.
+ -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off.
+ -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off.
+
+Example:
+
+sde_hdmi_cec: qcom,hdmi-cec@c9a0000 {
+ compatible = "qcom,hdmi-cec";
+ label = "sde_hdmi_cec";
+ interrupt-parent = <&sde_hdmi_tx>;
+ interrupts = <1 0>;
+
+ reg = <0xc9a0000 0x50c>;
+ reg-names = "hdmi_cec";
+
+ clocks = <&clock_mmss clk_mmss_mnoc_ahb_clk>,
+ <&clock_mmss clk_mmss_mdss_ahb_clk>,
+ <&clock_mmss clk_mmss_mdss_hdmi_clk>;
+ clock-names = "cec_mnoc_clk", "cec_iface_clk", "cec_core_clk";
+
+ pinctrl-names = "cec_active", "cec_sleep";
+ pinctrl-0 = <&mdss_hdmi_cec_active>;
+ pinctrl-1 = <&mdss_hdmi_cec_suspend>;
+
+ cec-gdsc-supply = <&gdsc_mdss>;
+ qcom,platform-supply-entries {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ qcom,platform-supply-entry@0 {
+ reg = <0>;
+ qcom,supply-name = "cec-gdsc";
+ qcom,supply-min-voltage = <0>;
+ qcom,supply-max-voltage = <0>;
+ qcom,supply-enable-load = <0>;
+ qcom,supply-disable-load = <0>;
+ };
+ };
+};
diff --git a/drivers/media/platform/msm/sde/Kconfig b/drivers/media/platform/msm/sde/Kconfig
index 85f5f4257ddb..c0e73813ab06 100644
--- a/drivers/media/platform/msm/sde/Kconfig
+++ b/drivers/media/platform/msm/sde/Kconfig
@@ -15,3 +15,14 @@ config MSM_SDE_ROTATOR_EVTLOG_DEBUG
features to: Dump rotator registers during driver errors, panic
driver during fatal errors and enable some rotator driver logging
into an internal buffer (this avoids logging overhead).
+
+config MSM_SDE_HDMI_CEC
+ bool "QTI SDE HDMI CEC Driver"
+ depends on DRM_SDE_HDMI
+ select MEDIA_CEC_SUPPORT
+ select MEDIA_CEC_EDID
+ ---help---
+ The HDMI CEC driver provides support to enable HDMI CEC features
+ which allows various audiovisual products to communicate using HDMI
+ CEC links. CEC is a protocol defined in HDMI spec which consists of
+ both low-level and high-level protocol definition.
diff --git a/drivers/media/platform/msm/sde/Makefile b/drivers/media/platform/msm/sde/Makefile
index fe55d5044c3b..f39000a15993 100644
--- a/drivers/media/platform/msm/sde/Makefile
+++ b/drivers/media/platform/msm/sde/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_MSM_SDE_ROTATOR) += rotator/
+obj-$(CONFIG_MSM_SDE_HDMI_CEC) += cec/
diff --git a/drivers/media/platform/msm/sde/cec/Makefile b/drivers/media/platform/msm/sde/cec/Makefile
new file mode 100644
index 000000000000..2a9c30980512
--- /dev/null
+++ b/drivers/media/platform/msm/sde/cec/Makefile
@@ -0,0 +1,3 @@
+obj-y := \
+ sde_hdmi_cec.o \
+ sde_hdmi_cec_util.o
diff --git a/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c
new file mode 100644
index 000000000000..0fed19a01d5b
--- /dev/null
+++ b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c
@@ -0,0 +1,399 @@
+/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/cec.h>
+#include <media/cec.h>
+
+#include "sde_hdmi_cec_util.h"
+
+#define CEC_NAME "sde-hdmi-cec"
+
+/* CEC Register Definition */
+#define CEC_INTR_MASK (BIT(1) | BIT(3) | BIT(7))
+#define CEC_SUPPORTED_HW_VERSION 0x30000001
+
+#define HDMI_CEC_WR_RANGE (0x000002DC)
+#define HDMI_CEC_RD_RANGE (0x000002E0)
+#define HDMI_VERSION (0x000002E4)
+#define HDMI_CEC_CTRL (0x0000028C)
+#define HDMI_CEC_WR_DATA (0x00000290)
+#define HDMI_CEC_RETRANSMIT (0x00000294)
+#define HDMI_CEC_STATUS (0x00000298)
+#define HDMI_CEC_INT (0x0000029C)
+#define HDMI_CEC_ADDR (0x000002A0)
+#define HDMI_CEC_TIME (0x000002A4)
+#define HDMI_CEC_REFTIMER (0x000002A8)
+#define HDMI_CEC_RD_DATA (0x000002AC)
+#define HDMI_CEC_RD_FILTER (0x000002B0)
+#define HDMI_CEC_COMPL_CTL (0x00000360)
+#define HDMI_CEC_RD_START_RANGE (0x00000364)
+#define HDMI_CEC_RD_TOTAL_RANGE (0x00000368)
+#define HDMI_CEC_RD_ERR_RESP_LO (0x0000036C)
+#define HDMI_CEC_WR_CHECK_CONFIG (0x00000370)
+
+enum cec_irq_status {
+ CEC_IRQ_FRAME_WR_DONE = 1 << 0,
+ CEC_IRQ_FRAME_RD_DONE = 1 << 1,
+ CEC_IRQ_FRAME_ERROR = 1 << 2,
+};
+
+struct sde_hdmi_cec {
+ struct cec_adapter *adap;
+ struct device *dev;
+ struct cec_hw_resource hw_res;
+ int irq;
+ enum cec_irq_status irq_status;
+};
+
+static int sde_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+ struct sde_hdmi_cec *cec = adap->priv;
+ struct cec_hw_resource *hw = &cec->hw_res;
+ u32 hdmi_hw_version, reg_val;
+
+ pr_debug("adap enable %d\n", enable);
+
+ if (enable) {
+ pm_runtime_get_sync(cec->dev);
+
+ /* 19.2Mhz * 0.00005 us = 950 = 0x3B6 */
+ CEC_REG_WRITE(hw, HDMI_CEC_REFTIMER, (0x3B6 & 0xFFF) | BIT(16));
+
+ hdmi_hw_version = CEC_REG_READ(hw, HDMI_VERSION);
+ if (hdmi_hw_version >= CEC_SUPPORTED_HW_VERSION) {
+ CEC_REG_WRITE(hw, HDMI_CEC_RD_RANGE, 0x30AB9888);
+ CEC_REG_WRITE(hw, HDMI_CEC_WR_RANGE, 0x888AA888);
+
+ CEC_REG_WRITE(hw, HDMI_CEC_RD_START_RANGE, 0x88888888);
+ CEC_REG_WRITE(hw, HDMI_CEC_RD_TOTAL_RANGE, 0x99);
+ CEC_REG_WRITE(hw, HDMI_CEC_COMPL_CTL, 0xF);
+ CEC_REG_WRITE(hw, HDMI_CEC_WR_CHECK_CONFIG, 0x4);
+ } else {
+ pr_err("CEC version %d is not supported.\n",
+ hdmi_hw_version);
+ return -EPERM;
+ }
+
+ CEC_REG_WRITE(hw, HDMI_CEC_RD_FILTER, BIT(0) | (0x7FF << 4));
+ CEC_REG_WRITE(hw, HDMI_CEC_TIME, BIT(0) | ((7 * 0x30) << 7));
+
+ /* Enable CEC interrupts */
+ CEC_REG_WRITE(hw, HDMI_CEC_INT, CEC_INTR_MASK);
+
+ /* Enable Engine */
+ CEC_REG_WRITE(hw, HDMI_CEC_CTRL, BIT(0));
+ } else {
+ /* Disable Engine */
+ CEC_REG_WRITE(hw, HDMI_CEC_CTRL, 0);
+
+ /* Disable CEC interrupts */
+ reg_val = CEC_REG_READ(hw, HDMI_CEC_INT);
+ CEC_REG_WRITE(hw, HDMI_CEC_INT, reg_val & ~CEC_INTR_MASK);
+
+ pm_runtime_put(cec->dev);
+ }
+
+ return 0;
+}
+
+static int sde_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
+{
+ struct sde_hdmi_cec *cec = adap->priv;
+ struct cec_hw_resource *hw = &cec->hw_res;
+
+ pr_debug("set log addr %d\n", logical_addr);
+
+ CEC_REG_WRITE(hw, HDMI_CEC_ADDR, logical_addr & 0xF);
+
+ return 0;
+}
+
+static int sde_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+{
+ struct sde_hdmi_cec *cec = adap->priv;
+ struct cec_hw_resource *hw = &cec->hw_res;
+ u32 frame_type;
+ int i;
+ u32 line_check_retry = 10;
+
+ pr_debug("transmit msg [%d]->[%d]: len = %d, attampts=%d, signal_free_time=%d\n",
+ cec_msg_initiator(msg), cec_msg_destination(msg), msg->len,
+ attempts, signal_free_time);
+
+ /* toggle cec in order to flush out bad hw state, if any */
+ CEC_REG_WRITE(hw, HDMI_CEC_CTRL, 0);
+ CEC_REG_WRITE(hw, HDMI_CEC_CTRL, BIT(0));
+
+ /* make sure state is cleared */
+ wmb();
+
+ CEC_REG_WRITE(hw, HDMI_CEC_RETRANSMIT,
+ ((attempts & 0xF) << 4) | BIT(0));
+
+ frame_type = cec_msg_is_broadcast(msg) ? BIT(0) : 0;
+
+ /* header block */
+ CEC_REG_WRITE(hw, HDMI_CEC_WR_DATA,
+ (((cec_msg_initiator(msg) << 4) |
+ cec_msg_destination(msg)) << 8) | frame_type);
+
+ /* data block 0 : opcode */
+ CEC_REG_WRITE(hw, HDMI_CEC_WR_DATA,
+ ((msg->len < 2 ? 0 : cec_msg_opcode(msg)) << 8) | frame_type);
+
+ /* data block 1-14 : operand 0-13 */
+ for (i = 2; i < msg->len; i++)
+ CEC_REG_WRITE(hw, HDMI_CEC_WR_DATA,
+ (msg->msg[i] << 8) | frame_type);
+
+ /* check line status */
+ while ((CEC_REG_READ(hw, HDMI_CEC_STATUS) & BIT(0)) &&
+ line_check_retry) {
+ line_check_retry--;
+ pr_debug("CEC line is busy(%d)\n", line_check_retry);
+ schedule();
+ }
+
+ if (!line_check_retry && (CEC_REG_READ(hw, HDMI_CEC_STATUS) & BIT(0))) {
+ pr_err("CEC line is busy. Retry failed\n");
+ return -EBUSY;
+ }
+
+ /* start transmission */
+ CEC_REG_WRITE(hw, HDMI_CEC_CTRL, BIT(0) | BIT(1) |
+ ((msg->len & 0x1F) << 4) | BIT(9));
+
+ return 0;
+}
+
+static void sde_hdmi_cec_handle_rx_done(struct sde_hdmi_cec *cec)
+{
+ struct cec_hw_resource *hw = &cec->hw_res;
+ struct cec_msg msg = {};
+ u32 data;
+ int i;
+
+ pr_debug("rx done\n");
+
+ data = CEC_REG_READ(hw, HDMI_CEC_RD_DATA);
+ msg.len = (data & 0x1F00) >> 8;
+ if (msg.len < 1 || msg.len > CEC_MAX_MSG_SIZE) {
+ pr_err("invalid message size %d", msg.len);
+ return;
+ }
+ msg.msg[0] = data & 0xFF;
+
+ for (i = 1; i < msg.len; i++)
+ msg.msg[i] = CEC_REG_READ(hw, HDMI_CEC_RD_DATA) & 0xFF;
+
+ cec_received_msg(cec->adap, &msg);
+}
+
+static void sde_hdmi_cec_handle_tx_done(struct sde_hdmi_cec *cec)
+{
+ pr_debug("tx done\n");
+ cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
+}
+
+static void sde_hdmi_cec_handle_tx_error(struct sde_hdmi_cec *cec)
+{
+ struct cec_hw_resource *hw = &cec->hw_res;
+ u32 cec_status = CEC_REG_READ(hw, HDMI_CEC_STATUS);
+
+ pr_debug("tx error status %x\n", cec_status);
+
+ if ((cec_status & 0xF0) == 0x10)
+ cec_transmit_done(cec->adap,
+ CEC_TX_STATUS_NACK, 0, 1, 0, 0);
+ else if ((cec_status & 0xF0) == 0x30)
+ cec_transmit_done(cec->adap,
+ CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0);
+ else
+ cec_transmit_done(cec->adap,
+ CEC_TX_STATUS_ERROR | CEC_TX_STATUS_MAX_RETRIES,
+ 0, 0, 0, 1);
+}
+
+static irqreturn_t sde_hdmi_cec_irq_handler_thread(int irq, void *priv)
+{
+ struct sde_hdmi_cec *cec = priv;
+
+ pr_debug("irq thread: status %x\n", cec->irq_status);
+
+ if (cec->irq_status & CEC_IRQ_FRAME_WR_DONE)
+ sde_hdmi_cec_handle_tx_done(cec);
+
+ if (cec->irq_status & CEC_IRQ_FRAME_ERROR)
+ sde_hdmi_cec_handle_tx_error(cec);
+
+ if (cec->irq_status & CEC_IRQ_FRAME_RD_DONE)
+ sde_hdmi_cec_handle_rx_done(cec);
+
+ cec->irq_status = 0;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sde_hdmi_cec_irq_handler(int irq, void *priv)
+{
+ struct sde_hdmi_cec *cec = priv;
+ struct cec_hw_resource *hw = &cec->hw_res;
+ u32 data = CEC_REG_READ(hw, HDMI_CEC_INT);
+
+ CEC_REG_WRITE(hw, HDMI_CEC_INT, data);
+
+ pr_debug("irq handler: %x\n", data);
+
+ if ((data & BIT(0)) && (data & BIT(1)))
+ cec->irq_status |= CEC_IRQ_FRAME_WR_DONE;
+
+ if ((data & BIT(2)) && (data & BIT(3)))
+ cec->irq_status |= CEC_IRQ_FRAME_ERROR;
+
+ if ((data & BIT(6)) && (data & BIT(7)))
+ cec->irq_status |= CEC_IRQ_FRAME_RD_DONE;
+
+ return cec->irq_status ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+static const struct cec_adap_ops sde_hdmi_cec_adap_ops = {
+ .adap_enable = sde_hdmi_cec_adap_enable,
+ .adap_log_addr = sde_hdmi_cec_adap_log_addr,
+ .adap_transmit = sde_hdmi_cec_adap_transmit,
+};
+
+static int sde_hdmi_cec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sde_hdmi_cec *cec;
+ int ret;
+
+ cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
+ if (!cec)
+ return -ENOMEM;
+
+ cec->dev = dev;
+
+ cec->irq = of_irq_get(dev->of_node, 0);
+ if (cec->irq < 0) {
+ pr_err("failed to get irq\n");
+ return cec->irq;
+ }
+
+ ret = devm_request_threaded_irq(dev, cec->irq, sde_hdmi_cec_irq_handler,
+ sde_hdmi_cec_irq_handler_thread, 0,
+ pdev->name, cec);
+ if (ret)
+ return ret;
+
+ ret = sde_hdmi_cec_init_resource(pdev, &cec->hw_res);
+ if (ret)
+ return ret;
+
+ cec->adap = cec_allocate_adapter(&sde_hdmi_cec_adap_ops, cec,
+ CEC_NAME,
+ CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH |
+ CEC_CAP_PHYS_ADDR | CEC_CAP_TRANSMIT, 1);
+ ret = PTR_ERR_OR_ZERO(cec->adap);
+ if (ret)
+ return ret;
+
+ ret = cec_register_adapter(cec->adap, &pdev->dev);
+ if (ret) {
+ cec_delete_adapter(cec->adap);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, cec);
+
+ pm_runtime_enable(dev);
+
+ pr_debug("probe done\n");
+
+ return 0;
+}
+
+static int sde_hdmi_cec_remove(struct platform_device *pdev)
+{
+ struct sde_hdmi_cec *cec = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ cec_unregister_adapter(cec->adap);
+
+ devm_free_irq(&pdev->dev, cec->irq, cec);
+ sde_hdmi_cec_deinit_resource(pdev, &cec->hw_res);
+
+ pr_debug("remove done\n");
+
+ return 0;
+}
+
+static int __maybe_unused sde_hdmi_cec_runtime_suspend(struct device *dev)
+{
+ struct sde_hdmi_cec *cec = dev_get_drvdata(dev);
+ struct cec_hw_resource *hw = &cec->hw_res;
+
+ pr_debug("runtime suspend\n");
+
+ return sde_hdmi_cec_enable_power(hw, false);
+}
+
+static int __maybe_unused sde_hdmi_cec_runtime_resume(struct device *dev)
+{
+ struct sde_hdmi_cec *cec = dev_get_drvdata(dev);
+ struct cec_hw_resource *hw = &cec->hw_res;
+
+ pr_debug("runtime resume\n");
+
+ return sde_hdmi_cec_enable_power(hw, true);
+}
+
+static const struct dev_pm_ops sde_hdmi_cec_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(sde_hdmi_cec_runtime_suspend,
+ sde_hdmi_cec_runtime_resume, NULL)
+};
+
+static const struct of_device_id sde_hdmi_cec_match[] = {
+ {
+ .compatible = "qcom,hdmi-cec",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sde_hdmi_cec_match);
+
+static struct platform_driver sde_hdmi_cec_pdrv = {
+ .probe = sde_hdmi_cec_probe,
+ .remove = sde_hdmi_cec_remove,
+ .driver = {
+ .name = CEC_NAME,
+ .of_match_table = sde_hdmi_cec_match,
+ .pm = &sde_hdmi_cec_pm_ops,
+ },
+};
+
+module_platform_driver(sde_hdmi_cec_pdrv);
+MODULE_DESCRIPTION("MSM SDE HDMI CEC driver");
diff --git a/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.c b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.c
new file mode 100644
index 000000000000..323e0805c886
--- /dev/null
+++ b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.c
@@ -0,0 +1,743 @@
+/* Copyright (c) 2012, 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+#include "sde_hdmi_cec_util.h"
+
+void sde_hdmi_cec_reg_w(struct cec_io_data *io,
+ u32 offset, u32 value, bool debug)
+{
+ u32 in_val;
+
+ if (!io || !io->base) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ if (offset > io->len) {
+ pr_err("offset out of range\n");
+ return;
+ }
+
+ writel_relaxed(value, io->base + offset);
+ if (debug) {
+ in_val = readl_relaxed(io->base + offset);
+ pr_debug("[%08x] => %08x [%08x]\n",
+ (u32)(unsigned long)(io->base + offset),
+ value, in_val);
+ }
+}
+
+u32 sde_hdmi_cec_reg_r(struct cec_io_data *io, u32 offset, bool debug)
+{
+ u32 value;
+
+ if (!io || !io->base) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ if (offset > io->len) {
+ pr_err("offset out of range\n");
+ return -EINVAL;
+ }
+
+ value = readl_relaxed(io->base + offset);
+ if (debug)
+ pr_debug("[%08x] <= %08x\n",
+ (u32)(unsigned long)(io->base + offset), value);
+
+ return value;
+}
+
+void sde_hdmi_cec_reg_dump(void __iomem *base, u32 length, const char *prefix,
+ bool debug)
+{
+ if (debug)
+ print_hex_dump(KERN_INFO, prefix, DUMP_PREFIX_OFFSET, 32, 4,
+ __io_virt(base), length, false);
+}
+
+static int sde_hdmi_cec_config_vreg(struct device *dev,
+ struct cec_vreg *in_vreg, int num_vreg, bool config)
+{
+ int i = 0, rc = 0;
+ struct cec_vreg *curr_vreg = NULL;
+ enum cec_vreg_type type;
+
+ if (!in_vreg || !num_vreg)
+ return rc;
+
+ if (config) {
+ for (i = 0; i < num_vreg; i++) {
+ curr_vreg = &in_vreg[i];
+ curr_vreg->vreg = regulator_get(dev,
+ curr_vreg->vreg_name);
+ rc = PTR_RET(curr_vreg->vreg);
+ if (rc) {
+ pr_err("%s get failed. rc=%d\n",
+ curr_vreg->vreg_name, rc);
+ curr_vreg->vreg = NULL;
+ goto vreg_get_fail;
+ }
+ type = (regulator_count_voltages(curr_vreg->vreg) > 0)
+ ? CEC_REG_LDO : CEC_REG_VS;
+ if (type == CEC_REG_LDO) {
+ rc = regulator_set_voltage(
+ curr_vreg->vreg,
+ curr_vreg->min_voltage,
+ curr_vreg->max_voltage);
+ if (rc < 0) {
+ pr_err("%s set vltg fail\n",
+ curr_vreg->vreg_name);
+ goto vreg_set_voltage_fail;
+ }
+ }
+ }
+ } else {
+ for (i = num_vreg-1; i >= 0; i--) {
+ curr_vreg = &in_vreg[i];
+ if (curr_vreg->vreg) {
+ type = (regulator_count_voltages(
+ curr_vreg->vreg) > 0)
+ ? CEC_REG_LDO : CEC_REG_VS;
+ if (type == CEC_REG_LDO) {
+ regulator_set_voltage(curr_vreg->vreg,
+ 0, curr_vreg->max_voltage);
+ }
+ regulator_put(curr_vreg->vreg);
+ curr_vreg->vreg = NULL;
+ }
+ }
+ }
+ return 0;
+
+vreg_unconfig:
+if (type == CEC_REG_LDO)
+ regulator_set_load(curr_vreg->vreg, 0);
+
+vreg_set_voltage_fail:
+ regulator_put(curr_vreg->vreg);
+ curr_vreg->vreg = NULL;
+
+vreg_get_fail:
+ for (i--; i >= 0; i--) {
+ curr_vreg = &in_vreg[i];
+ type = (regulator_count_voltages(curr_vreg->vreg) > 0)
+ ? CEC_REG_LDO : CEC_REG_VS;
+ goto vreg_unconfig;
+ }
+ return rc;
+}
+
+static int sde_hdmi_cec_enable_vreg(struct cec_hw_resource *hw, int enable)
+{
+ int i = 0, rc = 0;
+ bool need_sleep;
+ struct cec_vreg *in_vreg = hw->vreg_config;
+ int num_vreg = hw->num_vreg;
+
+ if (enable) {
+ for (i = 0; i < num_vreg; i++) {
+ rc = PTR_RET(in_vreg[i].vreg);
+ if (rc) {
+ pr_err("%s regulator error. rc=%d\n",
+ in_vreg[i].vreg_name, rc);
+ goto vreg_set_opt_mode_fail;
+ }
+ need_sleep = !regulator_is_enabled(in_vreg[i].vreg);
+ if (in_vreg[i].pre_on_sleep && need_sleep)
+ usleep_range(in_vreg[i].pre_on_sleep * 1000,
+ in_vreg[i].pre_on_sleep * 1000);
+ rc = regulator_set_load(in_vreg[i].vreg,
+ in_vreg[i].enable_load);
+ if (rc < 0) {
+ pr_err("%s set opt m fail\n",
+ in_vreg[i].vreg_name);
+ goto vreg_set_opt_mode_fail;
+ }
+ rc = regulator_enable(in_vreg[i].vreg);
+ if (in_vreg[i].post_on_sleep && need_sleep)
+ usleep_range(in_vreg[i].post_on_sleep * 1000,
+ in_vreg[i].post_on_sleep * 1000);
+ if (rc < 0) {
+ pr_err("%s enable failed\n",
+ in_vreg[i].vreg_name);
+ goto disable_vreg;
+ }
+ }
+ } else {
+ for (i = num_vreg-1; i >= 0; i--) {
+ if (in_vreg[i].pre_off_sleep)
+ usleep_range(in_vreg[i].pre_off_sleep * 1000,
+ in_vreg[i].pre_off_sleep * 1000);
+ regulator_set_load(in_vreg[i].vreg,
+ in_vreg[i].disable_load);
+ regulator_disable(in_vreg[i].vreg);
+ if (in_vreg[i].post_off_sleep)
+ usleep_range(in_vreg[i].post_off_sleep * 1000,
+ in_vreg[i].post_off_sleep * 1000);
+ }
+ }
+ return rc;
+
+disable_vreg:
+ regulator_set_load(in_vreg[i].vreg, in_vreg[i].disable_load);
+
+vreg_set_opt_mode_fail:
+ for (i--; i >= 0; i--) {
+ if (in_vreg[i].pre_off_sleep)
+ usleep_range(in_vreg[i].pre_off_sleep * 1000,
+ in_vreg[i].pre_off_sleep * 1000);
+ regulator_set_load(in_vreg[i].vreg,
+ in_vreg[i].disable_load);
+ regulator_disable(in_vreg[i].vreg);
+ if (in_vreg[i].post_off_sleep)
+ usleep_range(in_vreg[i].post_off_sleep * 1000,
+ in_vreg[i].post_off_sleep * 1000);
+ }
+
+ return rc;
+}
+
+static void sde_hdmi_cec_put_clk(struct cec_clk *clk_arry, int num_clk)
+{
+ int i;
+
+ for (i = num_clk - 1; i >= 0; i--) {
+ if (clk_arry[i].clk)
+ clk_put(clk_arry[i].clk);
+ clk_arry[i].clk = NULL;
+ }
+}
+
+static int sde_hdmi_cec_get_clk(struct device *dev,
+ struct cec_clk *clk_arry, int num_clk)
+{
+ int i, rc = 0;
+
+ for (i = 0; i < num_clk; i++) {
+ clk_arry[i].clk = clk_get(dev, clk_arry[i].clk_name);
+ rc = PTR_RET(clk_arry[i].clk);
+ if (rc) {
+ pr_err("'%s' get failed. rc=%d\n",
+ clk_arry[i].clk_name, rc);
+ goto error;
+ }
+ }
+
+ return rc;
+
+error:
+ sde_hdmi_cec_put_clk(clk_arry, num_clk);
+
+ return rc;
+}
+
+static int sde_hdmi_cec_enable_clk(struct cec_hw_resource *hw, int enable)
+{
+ int i, rc = 0;
+ struct cec_clk *clk_arry = hw->clk_config;
+ int num_clk = hw->num_clk;
+
+ if (enable) {
+ for (i = 0; i < num_clk; i++) {
+ pr_debug("enable %s\n", clk_arry[i].clk_name);
+ if (clk_arry[i].clk) {
+ rc = clk_prepare_enable(clk_arry[i].clk);
+ if (rc)
+ pr_err("%s enable fail. rc=%d\n",
+ clk_arry[i].clk_name, rc);
+ } else {
+ pr_err("%s is not available\n",
+ clk_arry[i].clk_name);
+ rc = -EPERM;
+ }
+ }
+ } else {
+ for (i = num_clk - 1; i >= 0; i--) {
+ pr_debug("disable %s\n", clk_arry[i].clk_name);
+
+ if (clk_arry[i].clk)
+ clk_disable_unprepare(clk_arry[i].clk);
+ else
+ pr_err("%s is not available\n",
+ clk_arry[i].clk_name);
+ }
+ }
+
+ return rc;
+}
+
+static int sde_hdmi_cec_pinctrl_enable(struct cec_hw_resource *hw,
+ bool enable)
+{
+ struct pinctrl_state *pin_state = NULL;
+ int rc = 0;
+
+ if (!hw) {
+ pr_err("invalid input param hw:%pK\n", hw);
+ return -EINVAL;
+ }
+
+ pr_debug("set cec pinctrl state %d\n", enable);
+
+ pin_state = enable ? hw->pin_res.state_active : hw->pin_res.state_sleep;
+
+ if (!IS_ERR_OR_NULL(hw->pin_res.pinctrl))
+ rc = pinctrl_select_state(hw->pin_res.pinctrl,
+ pin_state);
+ else
+ pr_err("pinstate not found\n");
+
+ return rc;
+}
+
+static void sde_hdmi_cec_put_dt_clock(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return;
+ }
+
+ if (hw->clk_config) {
+ sde_hdmi_cec_put_clk(hw->clk_config, hw->num_clk);
+ devm_kfree(&pdev->dev, hw->clk_config);
+ hw->clk_config = NULL;
+ }
+ hw->num_clk = 0;
+
+ pr_debug("put dt clock\n");
+}
+
+static int sde_hdmi_cec_get_dt_clock(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ int i = 0;
+ int num_clk = 0;
+ const char *clock_name;
+ int rc = 0;
+
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return -EINVAL;
+ }
+
+ hw->num_clk = 0;
+ num_clk = of_property_count_strings(pdev->dev.of_node, "clock-names");
+ if (num_clk <= 0) {
+ pr_debug("clocks are not defined\n");
+ return 0;
+ }
+
+ hw->num_clk = num_clk;
+ hw->clk_config = devm_kzalloc(&pdev->dev,
+ sizeof(struct cec_clk) * num_clk, GFP_KERNEL);
+ if (!hw->clk_config) {
+ hw->num_clk = 0;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num_clk; i++) {
+ of_property_read_string_index(pdev->dev.of_node, "clock-names",
+ i, &clock_name);
+ strlcpy(hw->clk_config[i].clk_name, clock_name,
+ sizeof(hw->clk_config[i].clk_name));
+ }
+
+ rc = sde_hdmi_cec_get_clk(&pdev->dev, hw->clk_config, hw->num_clk);
+ if (rc) {
+ sde_hdmi_cec_put_dt_clock(pdev, hw);
+ return rc;
+ }
+
+ pr_debug("get dt clock\n");
+
+ return 0;
+}
+
+static int sde_hdmi_cec_get_dt_supply(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ int i = 0, rc = 0;
+ u32 tmp = 0;
+ struct device_node *of_node = NULL, *supply_root_node = NULL;
+ struct device_node *supply_node = NULL;
+
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return -EINVAL;
+ }
+
+ of_node = pdev->dev.of_node;
+
+ hw->num_vreg = 0;
+ supply_root_node = of_get_child_by_name(of_node,
+ "qcom,platform-supply-entries");
+ if (!supply_root_node) {
+ pr_debug("no supply entry present\n");
+ return rc;
+ }
+
+ hw->num_vreg = of_get_available_child_count(supply_root_node);
+ if (hw->num_vreg == 0) {
+ pr_debug("no vreg present\n");
+ return rc;
+ }
+
+ pr_debug("vreg found. count=%d\n", hw->num_vreg);
+ hw->vreg_config = devm_kzalloc(&pdev->dev, sizeof(struct cec_vreg) *
+ hw->num_vreg, GFP_KERNEL);
+ if (!hw->vreg_config) {
+ rc = -ENOMEM;
+ return rc;
+ }
+
+ for_each_available_child_of_node(supply_root_node, supply_node) {
+ const char *st = NULL;
+
+ rc = of_property_read_string(supply_node,
+ "qcom,supply-name", &st);
+ if (rc) {
+ pr_err("error reading name. rc=%d\n", rc);
+ goto error;
+ }
+
+ strlcpy(hw->vreg_config[i].vreg_name, st,
+ sizeof(hw->vreg_config[i].vreg_name));
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-min-voltage", &tmp);
+ if (rc) {
+ pr_err("error reading min volt. rc=%d\n", rc);
+ goto error;
+ }
+ hw->vreg_config[i].min_voltage = tmp;
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-max-voltage", &tmp);
+ if (rc) {
+ pr_err("error reading max volt. rc=%d\n", rc);
+ goto error;
+ }
+ hw->vreg_config[i].max_voltage = tmp;
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-enable-load", &tmp);
+ if (rc) {
+ pr_err("error reading enable load. rc=%d\n", rc);
+ goto error;
+ }
+ hw->vreg_config[i].enable_load = tmp;
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-disable-load", &tmp);
+ if (rc) {
+ pr_err("error reading disable load. rc=%d\n", rc);
+ goto error;
+ }
+ hw->vreg_config[i].disable_load = tmp;
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-pre-on-sleep", &tmp);
+ if (rc)
+ pr_debug("no supply pre sleep value. rc=%d\n", rc);
+
+ hw->vreg_config[i].pre_on_sleep = (!rc ? tmp : 0);
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-pre-off-sleep", &tmp);
+ if (rc)
+ pr_debug("no supply pre sleep value. rc=%d\n", rc);
+
+ hw->vreg_config[i].pre_off_sleep = (!rc ? tmp : 0);
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-post-on-sleep", &tmp);
+ if (rc)
+ pr_debug("no supply post sleep value. rc=%d\n", rc);
+
+ hw->vreg_config[i].post_on_sleep = (!rc ? tmp : 0);
+
+ rc = of_property_read_u32(supply_node,
+ "qcom,supply-post-off-sleep", &tmp);
+ if (rc)
+ pr_debug("no supply post sleep value. rc=%d\n", rc);
+
+ hw->vreg_config[i].post_off_sleep = (!rc ? tmp : 0);
+
+ pr_debug("%s min=%d, max=%d, enable=%d, disable=%d, preonsleep=%d, postonsleep=%d, preoffsleep=%d, postoffsleep=%d\n",
+ hw->vreg_config[i].vreg_name,
+ hw->vreg_config[i].min_voltage,
+ hw->vreg_config[i].max_voltage,
+ hw->vreg_config[i].enable_load,
+ hw->vreg_config[i].disable_load,
+ hw->vreg_config[i].pre_on_sleep,
+ hw->vreg_config[i].post_on_sleep,
+ hw->vreg_config[i].pre_off_sleep,
+ hw->vreg_config[i].post_off_sleep);
+ ++i;
+
+ rc = 0;
+ }
+
+ rc = sde_hdmi_cec_config_vreg(&pdev->dev,
+ hw->vreg_config, hw->num_vreg, true);
+ if (rc)
+ goto error;
+
+ pr_debug("get dt supply\n");
+
+ return rc;
+
+error:
+ if (hw->vreg_config) {
+ devm_kfree(&pdev->dev, hw->vreg_config);
+ hw->vreg_config = NULL;
+ hw->num_vreg = 0;
+ }
+
+ return rc;
+}
+
+static void sde_hdmi_cec_put_dt_supply(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return;
+ }
+
+ sde_hdmi_cec_config_vreg(&pdev->dev,
+ hw->vreg_config, hw->num_vreg, false);
+
+ if (hw->vreg_config) {
+ devm_kfree(&pdev->dev, hw->vreg_config);
+ hw->vreg_config = NULL;
+ }
+ hw->num_vreg = 0;
+
+ pr_debug("put dt supply\n");
+}
+
+static int sde_hdmi_cec_get_dt_pinres(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return -EINVAL;
+ }
+
+ hw->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR_OR_NULL(hw->pin_res.pinctrl)) {
+ pr_err("failed to get pinctrl\n");
+ return PTR_ERR(hw->pin_res.pinctrl);
+ }
+
+ hw->pin_res.state_active =
+ pinctrl_lookup_state(hw->pin_res.pinctrl, "cec_active");
+ if (IS_ERR_OR_NULL(hw->pin_res.state_active))
+ pr_debug("cannot get active pinstate\n");
+
+ hw->pin_res.state_sleep =
+ pinctrl_lookup_state(hw->pin_res.pinctrl, "cec_sleep");
+ if (IS_ERR_OR_NULL(hw->pin_res.state_sleep))
+ pr_debug("cannot get sleep pinstate\n");
+
+ pr_debug("get dt pinres data\n");
+
+ return 0;
+}
+
+static void sde_hdmi_cec_put_dt_pinres(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return;
+ }
+
+ if (!IS_ERR_OR_NULL(hw->pin_res.pinctrl))
+ devm_pinctrl_put(hw->pin_res.pinctrl);
+}
+
+static void sde_hdmi_cec_deinit_power(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return;
+ }
+
+ sde_hdmi_cec_put_dt_supply(pdev, hw);
+ sde_hdmi_cec_put_dt_clock(pdev, hw);
+ sde_hdmi_cec_put_dt_pinres(pdev, hw);
+
+ pr_debug("put dt power data\n");
+}
+
+static int sde_hdmi_cec_init_power(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ int rc = 0;
+
+ if (!pdev || !hw) {
+ pr_err("invalid input param pdev:%pK hw:%pK\n", pdev, hw);
+ return -EINVAL;
+ }
+
+ /* VREG */
+ rc = sde_hdmi_cec_get_dt_supply(pdev, hw);
+ if (rc) {
+ pr_err("get_dt_supply failed. rc=%d\n", rc);
+ goto error;
+ }
+
+ /* Clock */
+ rc = sde_hdmi_cec_get_dt_clock(pdev, hw);
+ if (rc) {
+ pr_err("get_dt_clock failed. rc=%d\n", rc);
+ goto error;
+ }
+
+ /* Pinctrl */
+ rc = sde_hdmi_cec_get_dt_pinres(pdev, hw);
+ if (rc) {
+ pr_err("get_dt_pinres failed. rc=%d\n", rc);
+ goto error;
+ }
+
+ pr_debug("get dt power data\n");
+
+ return rc;
+
+error:
+ sde_hdmi_cec_deinit_power(pdev, hw);
+ return rc;
+}
+
+static int sde_hdmi_cec_init_io(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ struct resource *res = NULL;
+ struct cec_io_data *io_data = NULL;
+ const char *reg_name;
+
+ if (!pdev || !hw) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_string(pdev->dev.of_node, "reg-names",
+ &reg_name)) {
+ pr_err("cec reg not defined\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, reg_name);
+ if (!res) {
+ pr_err("%s get_res_byname failed\n", reg_name);
+ return -ENODEV;
+ }
+
+ io_data = &hw->io_res;
+ io_data->len = (u32)resource_size(res);
+ io_data->base = ioremap(res->start, io_data->len);
+ if (!io_data->base) {
+ pr_err("%s ioremap failed\n", reg_name);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void sde_hdmi_cec_deinit_io(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ struct cec_io_data *io_data = NULL;
+
+ if (!pdev || !hw) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ io_data = &hw->io_res;
+
+ if (io_data->base) {
+ iounmap(io_data->base);
+ io_data->base = NULL;
+ }
+ io_data->len = 0;
+}
+
+int sde_hdmi_cec_init_resource(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ int rc = 0;
+
+ /* power */
+ rc = sde_hdmi_cec_init_power(pdev, hw);
+ if (rc)
+ return rc;
+
+ /* io */
+ rc = sde_hdmi_cec_init_io(pdev, hw);
+ if (rc)
+ goto io_error;
+
+ pr_debug("cec init resource\n");
+
+ return rc;
+
+io_error:
+ sde_hdmi_cec_deinit_power(pdev, hw);
+ return rc;
+}
+
+void sde_hdmi_cec_deinit_resource(struct platform_device *pdev,
+ struct cec_hw_resource *hw)
+{
+ sde_hdmi_cec_deinit_power(pdev, hw);
+ sde_hdmi_cec_deinit_io(pdev, hw);
+
+ pr_debug("cec deinit resource\n");
+}
+
+int sde_hdmi_cec_enable_power(struct cec_hw_resource *hw, bool enable)
+{
+ int rc = 0;
+
+ rc = sde_hdmi_cec_enable_vreg(hw, enable);
+ if (rc)
+ return rc;
+
+ rc = sde_hdmi_cec_pinctrl_enable(hw, enable);
+ if (rc)
+ return rc;
+
+ rc = sde_hdmi_cec_enable_clk(hw, enable);
+ if (rc)
+ return rc;
+
+ pr_debug("cec power enable = %d\n", enable);
+
+ return rc;
+}
diff --git a/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.h b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.h
new file mode 100644
index 000000000000..f92c43ea3288
--- /dev/null
+++ b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.h
@@ -0,0 +1,93 @@
+/* Copyright (c) 2012, 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SDE_HDMI_CEC_UTIL_H__
+#define __SDE_HDMI_CEC_UTIL_H__
+
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/types.h>
+
+#ifdef DEBUG
+#define CEC_REG_WRITE(hw, off, val) \
+ sde_hdmi_cec_reg_w(&(hw)->io_res, (off), (val), true)
+#define CEC_REG_READ(hw, off) \
+ sde_hdmi_cec_reg_r(&(hw)->io_res, (off), true)
+#else
+#define CEC_REG_WRITE(hw, off, val) \
+ sde_hdmi_cec_reg_w(&(hw)->io_res, (off), (val), false)
+#define CEC_REG_READ(hw, off) \
+ sde_hdmi_cec_reg_r(&(hw)->io_res, (off), false)
+#endif
+
+struct cec_io_data {
+ u32 len;
+ void __iomem *base;
+};
+
+enum cec_vreg_type {
+ CEC_REG_LDO,
+ CEC_REG_VS,
+};
+
+struct cec_vreg {
+ struct regulator *vreg; /* vreg handle */
+ char vreg_name[32];
+ int min_voltage;
+ int max_voltage;
+ int enable_load;
+ int disable_load;
+ int pre_on_sleep;
+ int post_on_sleep;
+ int pre_off_sleep;
+ int post_off_sleep;
+};
+
+struct cec_clk {
+ struct clk *clk; /* clk handle */
+ char clk_name[32];
+};
+
+struct cec_pin_res {
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *state_active;
+ struct pinctrl_state *state_sleep;
+};
+
+struct cec_hw_resource {
+ /* power */
+ unsigned num_vreg;
+ struct cec_vreg *vreg_config;
+ unsigned num_clk;
+ struct cec_clk *clk_config;
+ struct cec_pin_res pin_res;
+
+ /* io */
+ struct cec_io_data io_res;
+};
+
+void sde_hdmi_cec_reg_w(struct cec_io_data *io,
+ u32 offset, u32 value, bool debug);
+u32 sde_hdmi_cec_reg_r(struct cec_io_data *io, u32 offset, bool debug);
+void sde_hdmi_cec_reg_dump(void __iomem *base, u32 length, const char *prefix,
+ bool debug);
+
+int sde_hdmi_cec_init_resource(struct platform_device *pdev,
+ struct cec_hw_resource *hw);
+void sde_hdmi_cec_deinit_resource(struct platform_device *pdev,
+ struct cec_hw_resource *hw);
+int sde_hdmi_cec_enable_power(struct cec_hw_resource *hw, bool enable);
+
+#endif /* __SDE_HDMI_CEC_UTIL_H__ */
+