summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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,