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>
This commit is contained in:
parent
87f34e2e55
commit
7833d2e68d
8 changed files with 253 additions and 14 deletions
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue