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 #include #include +#include #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,