summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTatenda Chipeperekwa <tatendac@codeaurora.org>2014-07-29 16:41:57 -0400
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:20:57 -0700
commit7833d2e68db90a275475c7f417d07313d3dedd0e (patch)
treeac2172fcc1d489d731d00c043b5c736c3888234f
parent87f34e2e551b28400eb6f337bc485bd9b34700e6 (diff)
msm: mdss: hdmi: add support for CEC suspend and resume events
Add support for CEC commands to suspend and resume the device. The HDMI core must be kept on when the CEC wakeup feature is enabled and the device is going into suspend state. Furthermore, interrupts must be enabled in this state to capture CEC commands. This allows the device to be resumed later on via CEC wakeup commands. Change-Id: Ie6fcbc666e4f40335ab8faaa969d4b03aa83e17c Signed-off-by: Tatenda Chipeperekwa <tatendac@codeaurora.org>
-rw-r--r--drivers/video/fbdev/msm/mdss.h4
-rw-r--r--drivers/video/fbdev/msm/mdss_cec_core.c7
-rw-r--r--drivers/video/fbdev/msm/mdss_cec_core.h10
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_cec.c119
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_cec.h23
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_tx.c41
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_tx.h1
-rw-r--r--drivers/video/fbdev/msm/mdss_util.c62
8 files changed, 253 insertions, 14 deletions
diff --git a/drivers/video/fbdev/msm/mdss.h b/drivers/video/fbdev/msm/mdss.h
index 470647d13f55..2e8d09b738a2 100644
--- a/drivers/video/fbdev/msm/mdss.h
+++ b/drivers/video/fbdev/msm/mdss.h
@@ -460,7 +460,9 @@ extern struct mdss_data_type *mdss_res;
struct irq_info {
u32 irq;
u32 irq_mask;
+ u32 irq_wake_mask;
u32 irq_ena;
+ u32 irq_wake_ena;
u32 irq_buzy;
};
@@ -484,6 +486,8 @@ struct mdss_util_intf {
int (*register_irq)(struct mdss_hw *hw);
void (*enable_irq)(struct mdss_hw *hw);
void (*disable_irq)(struct mdss_hw *hw);
+ void (*enable_wake_irq)(struct mdss_hw *hw);
+ void (*disable_wake_irq)(struct mdss_hw *hw);
void (*disable_irq_nosync)(struct mdss_hw *hw);
int (*irq_dispatch)(u32 hw_ndx, int irq, void *ptr);
int (*get_iommu_domain)(u32 type);
diff --git a/drivers/video/fbdev/msm/mdss_cec_core.c b/drivers/video/fbdev/msm/mdss_cec_core.c
index b8f0bd4755e8..4b53b01be709 100644
--- a/drivers/video/fbdev/msm/mdss_cec_core.c
+++ b/drivers/video/fbdev/msm/mdss_cec_core.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2016, 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
@@ -445,10 +445,7 @@ static ssize_t cec_wta_enable(struct device *dev,
ctl->cec_wakeup_en = false;
if (ops && ops->wakeup_en)
- ret = ops->wakeup_en(ops->data, ctl->cec_wakeup_en);
-
- if (ret)
- goto end;
+ ops->wakeup_en(ops->data, ctl->cec_wakeup_en);
if (ctl->enabled == cec_en) {
pr_debug("cec is already %s\n",
diff --git a/drivers/video/fbdev/msm/mdss_cec_core.h b/drivers/video/fbdev/msm/mdss_cec_core.h
index ba9f256a696d..12b7677c5dee 100644
--- a/drivers/video/fbdev/msm/mdss_cec_core.h
+++ b/drivers/video/fbdev/msm/mdss_cec_core.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2016, 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
@@ -54,7 +54,9 @@ struct cec_msg {
* @enable: function pointer to enable CEC
* @send_msg: function pointer to send CEC message
* @wt_logical_addr: function pointer to write logical address
- * @wakeup_en: function pointer to enable wakup feature
+ * @wakeup_en: function pointer to enable wakeup feature
+ * @is_wakeup_en: function pointer to query wakeup feature state
+ * @device_suspend: function pointer to update device suspend state
* @data: pointer to the data needed to send with operation functions
*
* Defines all the operations that abstract module can call
@@ -65,7 +67,9 @@ struct cec_ops {
int (*send_msg)(void *data,
struct cec_msg *msg);
void (*wt_logical_addr)(void *data, u8 addr);
- int (*wakeup_en)(void *data, bool en);
+ void (*wakeup_en)(void *data, bool en);
+ bool (*is_wakeup_en)(void *data);
+ void (*device_suspend)(void *data, bool suspend);
void *data;
};
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_cec.c b/drivers/video/fbdev/msm/mdss_hdmi_cec.c
index 3799216aa5ad..c2509d1240e8 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_cec.c
+++ b/drivers/video/fbdev/msm/mdss_hdmi_cec.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2016, 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
@@ -16,6 +16,7 @@
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/device.h>
+#include <linux/input.h>
#include "mdss_hdmi_cec.h"
#include "mdss_panel.h"
@@ -28,14 +29,21 @@
/* Reference: HDMI 1.4a Specification section 7.1 */
+#define CEC_OP_SET_STREAM_PATH 0x86
+#define CEC_OP_KEY_PRESS 0x44
+#define CEC_OP_STANDBY 0x36
+
struct hdmi_cec_ctrl {
bool cec_enabled;
+ bool cec_wakeup_en;
+ bool cec_device_suspend;
u32 cec_msg_wr_status;
spinlock_t lock;
struct work_struct cec_read_work;
struct completion cec_msg_wr_done;
struct hdmi_cec_init_data init_data;
+ struct input_dev *input;
};
static int hdmi_cec_msg_send(void *data, struct cec_msg *msg)
@@ -116,6 +124,46 @@ static int hdmi_cec_msg_send(void *data, struct cec_msg *msg)
return rc;
} /* hdmi_cec_msg_send */
+static void hdmi_cec_init_input_event(struct hdmi_cec_ctrl *cec_ctrl)
+{
+ int rc = 0;
+
+ if (!cec_ctrl) {
+ DEV_ERR("%s: Invalid input\n", __func__);
+ return;
+ }
+
+ /* Initialize CEC input events */
+ if (!cec_ctrl->input)
+ cec_ctrl->input = input_allocate_device();
+ if (!cec_ctrl->input) {
+ DEV_ERR("%s: hdmi input device allocation failed\n", __func__);
+ return;
+ }
+
+ cec_ctrl->input->name = "HDMI CEC User or Deck Control";
+ cec_ctrl->input->phys = "hdmi/input0";
+ cec_ctrl->input->id.bustype = BUS_VIRTUAL;
+
+ input_set_capability(cec_ctrl->input, EV_KEY, KEY_POWER);
+
+ rc = input_register_device(cec_ctrl->input);
+ if (rc) {
+ DEV_ERR("%s: cec input device registeration failed\n",
+ __func__);
+ input_free_device(cec_ctrl->input);
+ cec_ctrl->input = NULL;
+ return;
+ }
+}
+
+static void hdmi_cec_deinit_input_event(struct hdmi_cec_ctrl *cec_ctrl)
+{
+ if (cec_ctrl->input)
+ input_unregister_device(cec_ctrl->input);
+ cec_ctrl->input = NULL;
+}
+
static void hdmi_cec_msg_recv(struct work_struct *work)
{
int i;
@@ -171,6 +219,31 @@ static void hdmi_cec_msg_recv(struct work_struct *work)
for (; i < 14; i++)
msg.operand[i] = 0;
+ DEV_DBG("%s: opcode 0x%x, wakup_en %d, device_suspend %d\n", __func__,
+ msg.opcode, cec_ctrl->cec_wakeup_en,
+ cec_ctrl->cec_device_suspend);
+
+ if ((msg.opcode == CEC_OP_SET_STREAM_PATH ||
+ msg.opcode == CEC_OP_KEY_PRESS) &&
+ cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
+ cec_ctrl->cec_device_suspend) {
+ DEV_DBG("%s: Sending power on at wakeup\n", __func__);
+ input_report_key(cec_ctrl->input, KEY_POWER, 1);
+ input_sync(cec_ctrl->input);
+ input_report_key(cec_ctrl->input, KEY_POWER, 0);
+ input_sync(cec_ctrl->input);
+ }
+
+ if ((msg.opcode == CEC_OP_STANDBY) &&
+ cec_ctrl->input && cec_ctrl->cec_wakeup_en &&
+ !cec_ctrl->cec_device_suspend) {
+ DEV_DBG("%s: Sending power off on standby\n", __func__);
+ input_report_key(cec_ctrl->input, KEY_POWER, 1);
+ input_sync(cec_ctrl->input);
+ input_report_key(cec_ctrl->input, KEY_POWER, 0);
+ input_sync(cec_ctrl->input);
+ }
+
if (cbs && cbs->msg_recv_notify)
cbs->msg_recv_notify(cbs->data, &msg);
}
@@ -242,6 +315,42 @@ int hdmi_cec_isr(void *input)
return rc;
}
+void hdmi_cec_device_suspend(void *input, bool suspend)
+{
+ struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
+
+ if (!cec_ctrl) {
+ DEV_WARN("%s: HDMI CEC HW module not initialized.\n", __func__);
+ return;
+ }
+
+ cec_ctrl->cec_device_suspend = suspend;
+}
+
+bool hdmi_cec_is_wakeup_en(void *input)
+{
+ struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
+
+ if (!cec_ctrl) {
+ DEV_WARN("%s: HDMI CEC HW module not initialized.\n", __func__);
+ return 0;
+ }
+
+ return cec_ctrl->cec_wakeup_en;
+}
+
+static void hdmi_cec_wakeup_en(void *input, bool enable)
+{
+ struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
+
+ if (!cec_ctrl) {
+ DEV_ERR("%s: Invalid input\n", __func__);
+ return;
+ }
+
+ cec_ctrl->cec_wakeup_en = enable;
+}
+
static void hdmi_cec_write_logical_addr(void *input, u8 addr)
{
struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)input;
@@ -367,6 +476,11 @@ void *hdmi_cec_init(struct hdmi_cec_init_data *init_data)
ops->wt_logical_addr = hdmi_cec_write_logical_addr;
ops->enable = hdmi_cec_enable;
ops->data = cec_ctrl;
+ ops->wakeup_en = hdmi_cec_wakeup_en;
+ ops->is_wakeup_en = hdmi_cec_is_wakeup_en;
+ ops->device_suspend = hdmi_cec_device_suspend;
+
+ hdmi_cec_init_input_event(cec_ctrl);
return cec_ctrl;
error:
@@ -383,5 +497,8 @@ void hdmi_cec_deinit(void *data)
{
struct hdmi_cec_ctrl *cec_ctrl = (struct hdmi_cec_ctrl *)data;
+ if (cec_ctrl)
+ hdmi_cec_deinit_input_event(cec_ctrl);
+
kfree(cec_ctrl);
}
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_cec.h b/drivers/video/fbdev/msm/mdss_hdmi_cec.h
index a197ce2a605f..0ee696675d7e 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_cec.h
+++ b/drivers/video/fbdev/msm/mdss_hdmi_cec.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2013, 2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2013, 2015-2016, 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
@@ -66,4 +66,25 @@ void *hdmi_cec_init(struct hdmi_cec_init_data *init_data);
* This API release all resources allocated.
*/
void hdmi_cec_deinit(void *data);
+
+/**
+ * hdmi_cec_is_wakeup_en() - checks cec wakeup state
+ * @cec_ctrl: pointer to cec hw module's data
+ *
+ * Return: cec wakeup state
+ *
+ * This API is used to query whether the cec wakeup functionality is
+ * enabled or not.
+ */
+bool hdmi_cec_is_wakeup_en(void *cec_ctrl);
+
+/**
+ * hdmi_cec_device_suspend() - updates cec with device suspend state
+ * @cec_ctrl: pointer to cec hw module's data
+ * @suspend: device suspend state
+ *
+ * This API is used to update the CEC HW module of the device's suspend
+ * state.
+ */
+void hdmi_cec_device_suspend(void *cec_ctrl, bool suspend);
#endif /* __MDSS_HDMI_CEC_H__ */
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_tx.c b/drivers/video/fbdev/msm/mdss_hdmi_tx.c
index c2dc735137ca..fc7ed49f8536 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_tx.c
+++ b/drivers/video/fbdev/msm/mdss_hdmi_tx.c
@@ -501,6 +501,26 @@ static inline bool hdmi_tx_is_panel_on(struct hdmi_tx_ctrl *hdmi_ctrl)
return hdmi_ctrl->hpd_state && hdmi_ctrl->panel_power_on;
}
+static inline bool hdmi_tx_is_cec_wakeup_en(struct hdmi_tx_ctrl *hdmi_ctrl)
+{
+ if (!hdmi_ctrl || !hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW])
+ return false;
+
+ return hdmi_cec_is_wakeup_en(
+ hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]);
+}
+
+static inline void hdmi_tx_cec_device_suspend(struct hdmi_tx_ctrl *hdmi_ctrl,
+ bool suspend)
+{
+ if (!hdmi_ctrl || !hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW])
+ return;
+
+ hdmi_cec_device_suspend(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW],
+ suspend);
+}
+
+
static inline void hdmi_tx_send_cable_notification(
struct hdmi_tx_ctrl *hdmi_ctrl, int val)
{
@@ -2867,7 +2887,7 @@ static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
goto error;
}
- if (enable) {
+ if (enable && !hdmi_ctrl->power_data_enable[module]) {
if (hdmi_ctrl->panel_data.panel_info.cont_splash_enabled) {
DEV_DBG("%s: %s already eanbled by splash\n",
__func__, hdmi_pm_name(module));
@@ -2914,7 +2934,10 @@ static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
__func__, hdmi_tx_pm_name(module), rc);
goto disable_gpio;
}
- } else {
+ hdmi_ctrl->power_data_enable[module] = true;
+ } else if (!enable && hdmi_ctrl->power_data_enable[module] &&
+ (!hdmi_tx_is_cec_wakeup_en(hdmi_ctrl) ||
+ ((module != HDMI_TX_HPD_PM) && (module != HDMI_TX_CEC_PM)))) {
msm_dss_enable_clk(power_data->clk_config,
power_data->num_clk, 0);
mdss_update_reg_bus_vote(hdmi_ctrl->pdata.reg_bus_clt[module],
@@ -2924,6 +2947,7 @@ static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
hdmi_tx_pinctrl_set_state(hdmi_ctrl, module, 0);
msm_dss_enable_vreg(power_data->vreg_config,
power_data->num_vreg, 0);
+ hdmi_ctrl->power_data_enable[module] = false;
}
return rc;
@@ -3976,9 +4000,13 @@ static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl)
/* Turn off HPD interrupts */
DSS_REG_W(io, HDMI_HPD_INT_CTRL, 0);
- hdmi_ctrl->mdss_util->disable_irq(&hdmi_tx_hw);
- hdmi_tx_set_mode(hdmi_ctrl, false);
+ if (hdmi_tx_is_cec_wakeup_en(hdmi_ctrl)) {
+ hdmi_ctrl->mdss_util->enable_wake_irq(&hdmi_tx_hw);
+ } else {
+ hdmi_ctrl->mdss_util->disable_irq(&hdmi_tx_hw);
+ hdmi_tx_set_mode(hdmi_ctrl, false);
+ }
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, 0);
if (rc)
@@ -4032,6 +4060,9 @@ static int hdmi_tx_hpd_on(struct hdmi_tx_ctrl *hdmi_ctrl)
DSS_REG_W(io, HDMI_USEC_REFTIMER, 0x0001001B);
+ if (hdmi_tx_is_cec_wakeup_en(hdmi_ctrl))
+ hdmi_ctrl->mdss_util->disable_wake_irq(&hdmi_tx_hw);
+
hdmi_ctrl->mdss_util->enable_irq(&hdmi_tx_hw);
hdmi_ctrl->hpd_initialized = true;
@@ -4461,6 +4492,7 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
case MDSS_EVENT_RESUME:
hdmi_ctrl->panel_suspend = false;
+ hdmi_tx_cec_device_suspend(hdmi_ctrl, hdmi_ctrl->panel_suspend);
if (!hdmi_ctrl->hpd_feature_on)
goto end;
@@ -4538,6 +4570,7 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
hdmi_tx_hpd_off(hdmi_ctrl);
hdmi_ctrl->panel_suspend = true;
+ hdmi_tx_cec_device_suspend(hdmi_ctrl, hdmi_ctrl->panel_suspend);
break;
case MDSS_EVENT_BLANK:
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_tx.h b/drivers/video/fbdev/msm/mdss_hdmi_tx.h
index 4507cac30463..34474ecf0ff0 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_tx.h
+++ b/drivers/video/fbdev/msm/mdss_hdmi_tx.h
@@ -196,6 +196,7 @@ struct hdmi_tx_ctrl {
struct cec_cbs hdmi_cec_cbs;
char disp_switch_name[MAX_SWITCH_NAME_SIZE];
+ bool power_data_enable[HDMI_TX_MAX_PM];
};
#endif /* __MDSS_HDMI_TX_H__ */
diff --git a/drivers/video/fbdev/msm/mdss_util.c b/drivers/video/fbdev/msm/mdss_util.c
index 3a9ff9b6adb3..db318de6fc6d 100644
--- a/drivers/video/fbdev/msm/mdss_util.c
+++ b/drivers/video/fbdev/msm/mdss_util.c
@@ -139,10 +139,72 @@ int mdss_irq_dispatch(u32 hw_ndx, int irq, void *ptr)
return rc;
}
+void mdss_enable_irq_wake(struct mdss_hw *hw)
+{
+ unsigned long irq_flags;
+ u32 ndx_bit;
+
+ if (hw->hw_ndx >= MDSS_MAX_HW_BLK)
+ return;
+
+ if (!mdss_irq_handlers[hw->hw_ndx]) {
+ pr_err("failed. First register the irq then enable it.\n");
+ return;
+ }
+
+ ndx_bit = BIT(hw->hw_ndx);
+
+ pr_debug("Enable HW=%d irq ena=%d mask=%x\n", hw->hw_ndx,
+ hw->irq_info->irq_wake_ena,
+ hw->irq_info->irq_wake_mask);
+
+ spin_lock_irqsave(&mdss_lock, irq_flags);
+ if (hw->irq_info->irq_wake_mask & ndx_bit) {
+ pr_debug("MDSS HW ndx=%d is already set, mask=%x\n",
+ hw->hw_ndx, hw->irq_info->irq_wake_mask);
+ } else {
+ hw->irq_info->irq_wake_mask |= ndx_bit;
+ if (!hw->irq_info->irq_wake_ena) {
+ hw->irq_info->irq_wake_ena = true;
+ enable_irq_wake(hw->irq_info->irq);
+ }
+ }
+ spin_unlock_irqrestore(&mdss_lock, irq_flags);
+}
+
+void mdss_disable_irq_wake(struct mdss_hw *hw)
+{
+ unsigned long irq_flags;
+ u32 ndx_bit;
+
+ if (hw->hw_ndx >= MDSS_MAX_HW_BLK)
+ return;
+
+ ndx_bit = BIT(hw->hw_ndx);
+
+ pr_debug("Disable HW=%d irq ena=%d mask=%x\n", hw->hw_ndx,
+ hw->irq_info->irq_wake_ena,
+ hw->irq_info->irq_wake_mask);
+
+ spin_lock_irqsave(&mdss_lock, irq_flags);
+ if (!(hw->irq_info->irq_wake_mask & ndx_bit)) {
+ pr_warn("MDSS HW ndx=%d is NOT set\n", hw->hw_ndx);
+ } else {
+ hw->irq_info->irq_wake_mask &= ~ndx_bit;
+ if (hw->irq_info->irq_wake_ena) {
+ hw->irq_info->irq_wake_ena = false;
+ disable_irq_wake(hw->irq_info->irq);
+ }
+ }
+ spin_unlock_irqrestore(&mdss_lock, irq_flags);
+}
+
struct mdss_util_intf mdss_util = {
.register_irq = mdss_register_irq,
.enable_irq = mdss_enable_irq,
.disable_irq = mdss_disable_irq,
+ .enable_wake_irq = mdss_enable_irq_wake,
+ .disable_wake_irq = mdss_disable_irq_wake,
.disable_irq_nosync = mdss_disable_irq_nosync,
.irq_dispatch = mdss_irq_dispatch,
.get_iommu_domain = NULL,