diff options
author | Linux Build Service Account <lnxbuild@quicinc.com> | 2017-06-05 04:34:38 -0700 |
---|---|---|
committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2017-06-05 04:34:38 -0700 |
commit | 53a8c2aba36b922153b0346647902573eba3adfc (patch) | |
tree | 97d0dc84feefde5a16bb8264ca787c3edfb0d1e2 | |
parent | 21852febc31e52b6fa3a2e5da2c24d213b343ea3 (diff) | |
parent | 1188a3afd2548bc423d6bceccd5226aa4a5d0d55 (diff) |
Merge "msm: cec: Add CEC adapter driver for SDE hardware"
-rw-r--r-- | Documentation/devicetree/bindings/display/msm/cec.txt | 73 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/Kconfig | 11 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/cec/Makefile | 3 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c | 399 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.c | 743 | ||||
-rw-r--r-- | drivers/media/platform/msm/sde/cec/sde_hdmi_cec_util.h | 93 |
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", + ®_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__ */ + |