| /* Copyright (c) 2013-2018, 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. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/fb.h> |
| #include <linux/notifier.h> |
| #include <linux/workqueue.h> |
| #include <linux/delay.h> |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/iopoll.h> |
| #include <linux/kobject.h> |
| #include <linux/string.h> |
| #include <linux/sysfs.h> |
| #include <linux/interrupt.h> |
| |
| #include "mdss_fb.h" |
| #include "mdss_dsi.h" |
| #include "mdss_panel.h" |
| #include "mdss_mdp.h" |
| |
| #define STATUS_CHECK_INTERVAL_MS 5000 |
| #define STATUS_CHECK_INTERVAL_MIN_MS 50 |
| #define DSI_STATUS_CHECK_INIT -1 |
| #define DSI_STATUS_CHECK_DISABLE 1 |
| |
| static uint32_t interval = STATUS_CHECK_INTERVAL_MS; |
| static int32_t dsi_status_disable = DSI_STATUS_CHECK_INIT; |
| struct dsi_status_data *pstatus_data; |
| |
| /* |
| * check_dsi_ctrl_status() - Reads MFD structure and |
| * calls platform specific DSI ctrl Status function. |
| * @work : dsi controller status data |
| */ |
| static void check_dsi_ctrl_status(struct work_struct *work) |
| { |
| struct dsi_status_data *pdsi_status = NULL; |
| |
| pdsi_status = container_of(to_delayed_work(work), |
| struct dsi_status_data, check_status); |
| |
| if (!pdsi_status) { |
| pr_err("%s: DSI status data not available\n", __func__); |
| return; |
| } |
| |
| if (!pdsi_status->mfd) { |
| pr_err("%s: FB data not available\n", __func__); |
| return; |
| } |
| |
| if (mdss_panel_is_power_off(pdsi_status->mfd->panel_power_state) || |
| pdsi_status->mfd->shutdown_pending) { |
| pr_debug("%s: panel off\n", __func__); |
| return; |
| } |
| |
| pdsi_status->mfd->mdp.check_dsi_status(work, interval); |
| } |
| |
| /* |
| * hw_vsync_handler() - Interrupt handler for HW VSYNC signal. |
| * @irq : irq line number |
| * @data : Pointer to the device structure. |
| * |
| * This function is called whenever a HW vsync signal is received from the |
| * panel. This resets the timer of ESD delayed workqueue back to initial |
| * value. |
| */ |
| irqreturn_t hw_vsync_handler(int irq, void *data) |
| { |
| struct mdss_dsi_ctrl_pdata *ctrl_pdata = |
| (struct mdss_dsi_ctrl_pdata *)data; |
| if (!ctrl_pdata) { |
| pr_err("%s: DSI ctrl not available\n", __func__); |
| return IRQ_HANDLED; |
| } |
| |
| if (pstatus_data) |
| mod_delayed_work(system_wq, &pstatus_data->check_status, |
| msecs_to_jiffies(interval)); |
| else |
| pr_err("Pstatus data is NULL\n"); |
| |
| if (!atomic_read(&ctrl_pdata->te_irq_ready)) { |
| complete_all(&ctrl_pdata->te_irq_comp); |
| atomic_inc(&ctrl_pdata->te_irq_ready); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* |
| * disable_esd_thread() - Cancels work item for the esd check. |
| */ |
| void disable_esd_thread(void) |
| { |
| if (pstatus_data && |
| cancel_delayed_work_sync(&pstatus_data->check_status)) |
| pr_debug("esd thread killed\n"); |
| } |
| |
| /* |
| * fb_event_callback() - Call back function for the fb_register_client() |
| * notifying events |
| * @self : notifier block |
| * @event : The event that was triggered |
| * @data : Of type struct fb_event |
| * |
| * This function listens for FB_BLANK_UNBLANK and FB_BLANK_POWERDOWN events |
| * from frame buffer. DSI status check work is either scheduled again after |
| * PANEL_STATUS_CHECK_INTERVAL or cancelled based on the event. |
| */ |
| static int fb_event_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fb_event *evdata = data; |
| struct dsi_status_data *pdata = container_of(self, |
| struct dsi_status_data, fb_notifier); |
| struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL; |
| struct mdss_panel_info *pinfo; |
| struct msm_fb_data_type *mfd; |
| |
| if (!evdata) { |
| pr_err("%s: event data not available\n", __func__); |
| return NOTIFY_BAD; |
| } |
| |
| /* handle only mdss fb device */ |
| if (strcmp("mdssfb", evdata->info->fix.id)) |
| return NOTIFY_DONE; |
| |
| mfd = evdata->info->par; |
| ctrl_pdata = container_of(dev_get_platdata(&mfd->pdev->dev), |
| struct mdss_dsi_ctrl_pdata, panel_data); |
| if (!ctrl_pdata) { |
| pr_err("%s: DSI ctrl not available\n", __func__); |
| return NOTIFY_BAD; |
| } |
| |
| pinfo = &ctrl_pdata->panel_data.panel_info; |
| |
| if ((!(pinfo->esd_check_enabled) && |
| dsi_status_disable) || |
| (dsi_status_disable == DSI_STATUS_CHECK_DISABLE)) { |
| pr_debug("ESD check is disabled.\n"); |
| cancel_delayed_work(&pdata->check_status); |
| return NOTIFY_DONE; |
| } |
| |
| pdata->mfd = evdata->info->par; |
| if (event == FB_EVENT_BLANK) { |
| int *blank = evdata->data; |
| struct dsi_status_data *pdata = container_of(self, |
| struct dsi_status_data, fb_notifier); |
| pdata->mfd = evdata->info->par; |
| |
| switch (*blank) { |
| case FB_BLANK_UNBLANK: |
| schedule_delayed_work(&pdata->check_status, |
| msecs_to_jiffies(interval)); |
| break; |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| pr_debug("%s : ESD thread running\n", __func__); |
| break; |
| case FB_BLANK_POWERDOWN: |
| case FB_BLANK_HSYNC_SUSPEND: |
| cancel_delayed_work(&pdata->check_status); |
| break; |
| default: |
| pr_err("Unknown case in FB_EVENT_BLANK event\n"); |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static int param_dsi_status_disable(const char *val, struct kernel_param *kp) |
| { |
| int ret = 0; |
| int int_val; |
| |
| ret = kstrtos32(val, 0, &int_val); |
| if (ret) |
| return ret; |
| |
| pr_info("%s: Set DSI status disable to %d\n", |
| __func__, int_val); |
| *((int *)kp->arg) = int_val; |
| return ret; |
| } |
| |
| static int param_set_interval(const char *val, struct kernel_param *kp) |
| { |
| int ret = 0; |
| int int_val; |
| |
| ret = kstrtos32(val, 0, &int_val); |
| if (ret) |
| return ret; |
| if (int_val < STATUS_CHECK_INTERVAL_MIN_MS) { |
| pr_err("%s: Invalid value %d used, ignoring\n", |
| __func__, int_val); |
| ret = -EINVAL; |
| } else { |
| pr_info("%s: Set check interval to %d msecs\n", |
| __func__, int_val); |
| *((int *)kp->arg) = int_val; |
| } |
| return ret; |
| } |
| |
| int __init mdss_dsi_status_init(void) |
| { |
| int rc = 0; |
| |
| pstatus_data = kzalloc(sizeof(struct dsi_status_data), GFP_KERNEL); |
| if (!pstatus_data) |
| return -ENOMEM; |
| |
| pstatus_data->fb_notifier.notifier_call = fb_event_callback; |
| |
| rc = fb_register_client(&pstatus_data->fb_notifier); |
| if (rc < 0) { |
| pr_err("%s: fb_register_client failed, returned with rc=%d\n", |
| __func__, rc); |
| kfree(pstatus_data); |
| return -EPERM; |
| } |
| |
| pr_info("%s: DSI status check interval:%d\n", __func__, interval); |
| |
| INIT_DELAYED_WORK(&pstatus_data->check_status, check_dsi_ctrl_status); |
| |
| pr_debug("%s: DSI ctrl status work queue initialized\n", __func__); |
| |
| return rc; |
| } |
| |
| void __exit mdss_dsi_status_exit(void) |
| { |
| fb_unregister_client(&pstatus_data->fb_notifier); |
| cancel_delayed_work_sync(&pstatus_data->check_status); |
| kfree(pstatus_data); |
| pr_debug("%s: DSI ctrl status work queue removed\n", __func__); |
| } |
| |
| module_param_call(interval, param_set_interval, param_get_uint, |
| &interval, 0644); |
| MODULE_PARM_DESC(interval, |
| "Duration in milliseconds to send BTA command for DSI status check"); |
| |
| module_param_call(dsi_status_disable, param_dsi_status_disable, param_get_uint, |
| &dsi_status_disable, 0644); |
| MODULE_PARM_DESC(dsi_status_disable, |
| "Disable DSI status check"); |
| |
| module_init(mdss_dsi_status_init); |
| module_exit(mdss_dsi_status_exit); |
| |
| MODULE_LICENSE("GPL v2"); |