2024-09-09 08:52:07 +00:00
|
|
|
/*
|
|
|
|
* Core MDSS framebuffer driver.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2007 Google Incorporated
|
2024-09-09 08:57:42 +00:00
|
|
|
* Copyright (c) 2008-2015, The Linux Foundation. All rights reserved.
|
2024-09-09 08:52:07 +00:00
|
|
|
*
|
|
|
|
* This software is licensed under the terms of the GNU General Public
|
|
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
|
|
* may be copied, distributed, and modified under those terms.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/videodev2.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
#include <linux/bootmem.h>
|
|
|
|
#include <linux/console.h>
|
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/dma-buf.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
#include <linux/fb.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/memory.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/msm_mdp.h>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_address.h>
|
|
|
|
#include <linux/of_platform.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/sync.h>
|
|
|
|
#include <linux/sw_sync.h>
|
|
|
|
#include <linux/file.h>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/dma-buf.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
#include "mdss_fb.h"
|
2024-09-09 08:57:42 +00:00
|
|
|
#include "mdss_mdp_splash_logo.h"
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include "mdss_debug.h"
|
|
|
|
#include "mdss_smmu.h"
|
|
|
|
#include "mdss_mdp.h"
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
|
|
|
|
#define MDSS_FB_NUM 3
|
|
|
|
#else
|
|
|
|
#define MDSS_FB_NUM 2
|
|
|
|
#endif
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
#ifndef EXPORT_COMPAT
|
|
|
|
#define EXPORT_COMPAT(x)
|
|
|
|
#endif
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
#define MAX_FBI_LIST 32
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
#define BLANK_FLAG_LP FB_BLANK_NORMAL
|
|
|
|
#define BLANK_FLAG_ULP FB_BLANK_VSYNC_SUSPEND
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static struct fb_info *fbi_list[MAX_FBI_LIST];
|
|
|
|
static int fbi_list_index;
|
|
|
|
|
|
|
|
static u32 mdss_fb_pseudo_palette[16] = {
|
|
|
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
|
|
|
|
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
|
|
|
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
|
|
|
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct msm_mdp_interface *mdp_instance;
|
|
|
|
|
|
|
|
static int mdss_fb_register(struct msm_fb_data_type *mfd);
|
|
|
|
static int mdss_fb_open(struct fb_info *info, int user);
|
|
|
|
static int mdss_fb_release(struct fb_info *info, int user);
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_release_all(struct fb_info *info, bool release_all);
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info);
|
|
|
|
static int mdss_fb_check_var(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info);
|
|
|
|
static int mdss_fb_set_par(struct fb_info *info);
|
|
|
|
static int mdss_fb_blank_sub(int blank_mode, struct fb_info *info,
|
|
|
|
int op_enable);
|
|
|
|
static int mdss_fb_suspend_sub(struct msm_fb_data_type *mfd);
|
|
|
|
static int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd,
|
2024-09-09 08:57:42 +00:00
|
|
|
unsigned long arg, struct file *file);
|
|
|
|
static int mdss_fb_fbmem_ion_mmap(struct fb_info *info,
|
|
|
|
struct vm_area_struct *vma);
|
|
|
|
static int mdss_fb_alloc_fb_ion_memory(struct msm_fb_data_type *mfd,
|
|
|
|
size_t size);
|
2024-09-09 08:52:07 +00:00
|
|
|
static void mdss_fb_release_fences(struct msm_fb_data_type *mfd);
|
2024-09-09 08:57:42 +00:00
|
|
|
static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p,
|
|
|
|
unsigned long val, void *data);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int __mdss_fb_display_thread(void *data);
|
|
|
|
static int mdss_fb_pan_idle(struct msm_fb_data_type *mfd);
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_send_panel_event(struct msm_fb_data_type *mfd,
|
|
|
|
int event, void *arg);
|
2024-09-09 08:57:42 +00:00
|
|
|
static void mdss_fb_set_mdp_sync_pt_threshold(struct msm_fb_data_type *mfd,
|
|
|
|
int type);
|
2024-09-09 08:52:07 +00:00
|
|
|
void mdss_fb_no_update_notify_timer_cb(unsigned long data)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data;
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd) {
|
2024-09-09 08:52:07 +00:00
|
|
|
pr_err("%s mfd NULL\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->no_update.value = NOTIFY_TYPE_NO_UPDATE;
|
|
|
|
complete(&mfd->no_update.comp);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
void mdss_fb_bl_update_notify(struct msm_fb_data_type *mfd,
|
|
|
|
uint32_t notification_type)
|
|
|
|
{
|
|
|
|
struct mdss_overlay_private *mdp5_data = NULL;
|
|
|
|
if (!mfd) {
|
|
|
|
pr_err("%s mfd NULL\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
if (mfd->update.is_suspend) {
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (mfd->update.ref_count > 0) {
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
mfd->update.value = notification_type;
|
|
|
|
complete(&mfd->update.comp);
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
|
|
|
|
mutex_lock(&mfd->no_update.lock);
|
|
|
|
if (mfd->no_update.ref_count > 0) {
|
|
|
|
mutex_unlock(&mfd->no_update.lock);
|
|
|
|
mfd->no_update.value = notification_type;
|
|
|
|
complete(&mfd->no_update.comp);
|
|
|
|
mutex_lock(&mfd->no_update.lock);
|
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->no_update.lock);
|
|
|
|
mdp5_data = mfd_to_mdp5_data(mfd);
|
|
|
|
if (mdp5_data) {
|
|
|
|
if (notification_type == NOTIFY_TYPE_BL_AD_ATTEN_UPDATE) {
|
|
|
|
mdp5_data->ad_bl_events++;
|
|
|
|
sysfs_notify_dirent(mdp5_data->ad_bl_event_sd);
|
|
|
|
} else if (notification_type == NOTIFY_TYPE_BL_UPDATE) {
|
|
|
|
mdp5_data->bl_events++;
|
|
|
|
sysfs_notify_dirent(mdp5_data->bl_event_sd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_notify_update(struct msm_fb_data_type *mfd,
|
|
|
|
unsigned long *argp)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret;
|
|
|
|
unsigned int notify = 0x0, to_user = 0x0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = copy_from_user(¬ify, argp, sizeof(unsigned int));
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret) {
|
|
|
|
pr_err("%s:ioctl failed\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (notify > NOTIFY_UPDATE_POWER_OFF)
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (notify == NOTIFY_UPDATE_INIT) {
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
mfd->update.init_done = true;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
ret = 1;
|
|
|
|
} else if (notify == NOTIFY_UPDATE_DEINIT) {
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
mfd->update.init_done = false;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
complete(&mfd->update.comp);
|
|
|
|
complete(&mfd->no_update.comp);
|
|
|
|
ret = 1;
|
|
|
|
} else if (mfd->update.is_suspend) {
|
|
|
|
to_user = NOTIFY_TYPE_SUSPEND;
|
|
|
|
mfd->update.is_suspend = 0;
|
|
|
|
ret = 1;
|
|
|
|
} else if (notify == NOTIFY_UPDATE_START) {
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
if (mfd->update.init_done)
|
|
|
|
reinit_completion(&mfd->update.comp);
|
|
|
|
else {
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
pr_err("notify update start called without init\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
mfd->update.ref_count++;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
ret = wait_for_completion_interruptible_timeout(
|
|
|
|
&mfd->update.comp, 4 * HZ);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
mfd->update.ref_count--;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
to_user = (unsigned int)mfd->update.value;
|
|
|
|
if (mfd->update.type == NOTIFY_TYPE_SUSPEND) {
|
|
|
|
to_user = (unsigned int)mfd->update.type;
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
} else if (notify == NOTIFY_UPDATE_STOP) {
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
if (mfd->update.init_done)
|
|
|
|
reinit_completion(&mfd->no_update.comp);
|
|
|
|
else {
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
pr_err("notify update stop called without init\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
mfd->no_update.ref_count++;
|
|
|
|
mutex_unlock(&mfd->no_update.lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
ret = wait_for_completion_interruptible_timeout(
|
|
|
|
&mfd->no_update.comp, 4 * HZ);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&mfd->no_update.lock);
|
|
|
|
mfd->no_update.ref_count--;
|
|
|
|
mutex_unlock(&mfd->no_update.lock);
|
|
|
|
to_user = (unsigned int)mfd->no_update.value;
|
|
|
|
} else {
|
|
|
|
if (mdss_fb_is_power_on(mfd)) {
|
|
|
|
reinit_completion(&mfd->power_off_comp);
|
|
|
|
ret = wait_for_completion_interruptible_timeout(
|
|
|
|
&mfd->power_off_comp, 1 * HZ);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret == 0)
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
else if (ret > 0)
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = copy_to_user(argp, &to_user, sizeof(unsigned int));
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lcd_backlight_registered;
|
|
|
|
|
|
|
|
static void mdss_fb_set_bl_brightness(struct led_classdev *led_cdev,
|
|
|
|
enum led_brightness value)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = dev_get_drvdata(led_cdev->dev->parent);
|
|
|
|
int bl_lvl;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->boot_notification_led) {
|
|
|
|
led_trigger_event(mfd->boot_notification_led, 0);
|
|
|
|
mfd->boot_notification_led = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value > mfd->panel_info->brightness_max)
|
|
|
|
value = mfd->panel_info->brightness_max;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
/* This maps android backlight level 0 to 255 into
|
|
|
|
driver backlight level 0 to bl_max with rounding */
|
|
|
|
MDSS_BRIGHT_TO_BL(bl_lvl, value, mfd->panel_info->bl_max,
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->panel_info->brightness_max);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!bl_lvl && value)
|
|
|
|
bl_lvl = 1;
|
|
|
|
|
|
|
|
if (!IS_CALIB_MODE_BL(mfd) && (!mfd->ext_bl_ctrl || !value ||
|
|
|
|
!mfd->bl_level)) {
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
mdss_fb_set_backlight(mfd, bl_lvl);
|
|
|
|
mutex_unlock(&mfd->bl_lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_classdev backlight_led = {
|
|
|
|
.name = "lcd-backlight",
|
2024-09-09 08:57:42 +00:00
|
|
|
.brightness = MDSS_MAX_BL_BRIGHTNESS / 2,
|
2024-09-09 08:52:07 +00:00
|
|
|
.brightness_set = mdss_fb_set_bl_brightness,
|
2024-09-09 08:57:42 +00:00
|
|
|
.max_brightness = MDSS_MAX_BL_BRIGHTNESS,
|
2024-09-09 08:52:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_type(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret = 0;
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
|
|
|
|
switch (mfd->panel.type) {
|
|
|
|
case NO_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "no panel\n");
|
|
|
|
break;
|
|
|
|
case HDMI_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "hdmi panel\n");
|
|
|
|
break;
|
|
|
|
case LVDS_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "lvds panel\n");
|
|
|
|
break;
|
|
|
|
case DTV_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "dtv panel\n");
|
|
|
|
break;
|
|
|
|
case MIPI_VIDEO_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "mipi dsi video panel\n");
|
|
|
|
break;
|
|
|
|
case MIPI_CMD_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "mipi dsi cmd panel\n");
|
|
|
|
break;
|
|
|
|
case WRITEBACK_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "writeback panel\n");
|
|
|
|
break;
|
|
|
|
case EDP_PANEL:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "edp panel\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "unknown panel\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_get_panel_xres(struct mdss_panel_info *pinfo)
|
|
|
|
{
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
int xres;
|
|
|
|
|
|
|
|
pdata = container_of(pinfo, struct mdss_panel_data, panel_info);
|
|
|
|
|
|
|
|
xres = pinfo->xres;
|
|
|
|
if (pdata->next && pdata->next->active)
|
|
|
|
xres += mdss_fb_get_panel_xres(&pdata->next->panel_info);
|
|
|
|
|
|
|
|
return xres;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int mdss_fb_validate_split(int left, int right,
|
|
|
|
struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int rc = -EINVAL;
|
|
|
|
u32 panel_xres = mdss_fb_get_panel_xres(mfd->panel_info);
|
|
|
|
|
|
|
|
pr_debug("%pS: split_mode = %d left=%d right=%d panel_xres=%d\n",
|
|
|
|
__builtin_return_address(0), mfd->split_mode,
|
|
|
|
left, right, panel_xres);
|
|
|
|
|
|
|
|
/* more validate condition could be added if needed */
|
|
|
|
if (left && right) {
|
|
|
|
if (panel_xres == left + right) {
|
|
|
|
mfd->split_fb_left = left;
|
|
|
|
mfd->split_fb_right = right;
|
|
|
|
rc = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) {
|
|
|
|
mfd->split_fb_left = mfd->panel_info->xres;
|
|
|
|
mfd->split_fb_right = panel_xres - mfd->split_fb_left;
|
|
|
|
rc = 0;
|
|
|
|
} else {
|
|
|
|
mfd->split_fb_left = mfd->split_fb_right = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_store_split(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
int data[2] = {0};
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
|
|
|
|
if (2 != sscanf(buf, "%d %d", &data[0], &data[1]))
|
|
|
|
pr_debug("Not able to read split values\n");
|
|
|
|
else if (!mdss_fb_validate_split(data[0], data[1], mfd))
|
|
|
|
pr_debug("split left=%d right=%d\n", data[0], data[1]);
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_show_split(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret = 0;
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d %d\n",
|
|
|
|
mfd->split_fb_left, mfd->split_fb_right);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_get_split(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
if ((mfd->split_mode == MDP_SPLIT_MODE_NONE) &&
|
|
|
|
(mfd->split_fb_left && mfd->split_fb_right))
|
|
|
|
mfd->split_mode = MDP_DUAL_LM_SINGLE_DISPLAY;
|
|
|
|
|
|
|
|
pr_debug("split fb%d left=%d right=%d mode=%d\n", mfd->index,
|
|
|
|
mfd->split_fb_left, mfd->split_fb_right, mfd->split_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_src_split_info(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
|
|
|
|
if (is_split_lm(mfd) && (fbi->var.yres > fbi->var.xres)) {
|
|
|
|
pr_debug("always split mode enabled\n");
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE,
|
|
|
|
"src_split_always\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_thermal_level(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "thermal_level=%d\n",
|
|
|
|
mfd->thermal_level);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_set_thermal_level(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int rc = 0;
|
|
|
|
int thermal_level = 0;
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &thermal_level);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("kstrtoint failed. rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("Thermal level set to %d\n", thermal_level);
|
|
|
|
mfd->thermal_level = thermal_level;
|
|
|
|
sysfs_notify(&mfd->fbi->dev->kobj, NULL, "msm_fb_thermal_level");
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_mdp_show_blank_event(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pr_debug("fb%d panel_power_state = %d\n", mfd->index,
|
|
|
|
mfd->panel_power_state);
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "panel_power_on = %d\n",
|
|
|
|
mfd->panel_power_state);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __mdss_fb_idle_notify_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct delayed_work *dw = to_delayed_work(work);
|
|
|
|
struct msm_fb_data_type *mfd = container_of(dw, struct msm_fb_data_type,
|
|
|
|
idle_notify_work);
|
|
|
|
|
|
|
|
/* Notify idle-ness here */
|
|
|
|
pr_debug("Idle timeout %dms expired!\n", mfd->idle_time);
|
|
|
|
if (mfd->idle_time)
|
|
|
|
sysfs_notify(&mfd->fbi->dev->kobj, NULL, "idle_notify");
|
|
|
|
mfd->idle_state = MDSS_FB_IDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_idle_time(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "%d", mfd->idle_time);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_set_idle_time(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int rc = 0;
|
|
|
|
int idle_time = 0;
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &idle_time);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("kstrtoint failed. rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("Idle time = %d\n", idle_time);
|
|
|
|
mfd->idle_time = idle_time;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_idle_notify(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s",
|
|
|
|
work_busy(&mfd->idle_notify_work.work) ? "no" : "yes");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_panel_info(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
struct mdss_panel_info *pinfo = mfd->panel_info;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE,
|
|
|
|
"pu_en=%d\nxstart=%d\nwalign=%d\nystart=%d\nhalign=%d\n"
|
|
|
|
"min_w=%d\nmin_h=%d\nroi_merge=%d\ndyn_fps_en=%d\n"
|
|
|
|
"min_fps=%d\nmax_fps=%d\npanel_name=%s\n"
|
|
|
|
"primary_panel=%d\nis_pluggable=%d\ndisplay_id=%s\n"
|
|
|
|
"is_cec_supported=%d\n",
|
|
|
|
pinfo->partial_update_enabled, pinfo->xstart_pix_align,
|
|
|
|
pinfo->width_pix_align, pinfo->ystart_pix_align,
|
|
|
|
pinfo->height_pix_align, pinfo->min_width,
|
|
|
|
pinfo->min_height, pinfo->partial_update_roi_merge,
|
|
|
|
pinfo->dynamic_fps, pinfo->min_fps, pinfo->max_fps,
|
|
|
|
pinfo->panel_name, pinfo->is_prim_panel,
|
|
|
|
pinfo->is_pluggable, pinfo->display_id,
|
|
|
|
pinfo->is_cec_supported);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_panel_status(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = fbi->par;
|
|
|
|
int ret;
|
|
|
|
int panel_status;
|
|
|
|
|
|
|
|
if (mdss_panel_is_power_off(mfd->panel_power_state)) {
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "panel_status=%s\n", "suspend");
|
|
|
|
} else {
|
|
|
|
panel_status = mdss_fb_send_panel_event(mfd,
|
|
|
|
MDSS_EVENT_DSI_PANEL_STATUS, NULL);
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "panel_status=%s\n",
|
|
|
|
panel_status > 0 ? "alive" : "dead");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_force_panel_dead(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("no panel connected!\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sscanf(buf, "%d", &pdata->panel_info.panel_force_dead) != 1)
|
|
|
|
pr_err("sccanf buf error!\n");
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mdss_fb_blanking_mode_switch() - Function triggers dynamic mode switch
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
* @mode: Enabled/Disable LowPowerMode
|
|
|
|
* 1: Enter into LowPowerMode
|
|
|
|
* 0: Exit from LowPowerMode
|
|
|
|
*
|
|
|
|
* This Function dynamically switches to and from video mode. This
|
|
|
|
* swtich involves the panel turning off backlight during trantision.
|
|
|
|
*/
|
|
|
|
static int mdss_fb_blanking_mode_switch(struct msm_fb_data_type *mfd, int mode)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
u32 bl_lvl = 0;
|
|
|
|
struct mdss_panel_info *pinfo = NULL;
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
|
|
|
|
if (!mfd || !mfd->panel_info)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pinfo = mfd->panel_info;
|
|
|
|
|
|
|
|
if (!pinfo->mipi.dms_mode) {
|
|
|
|
pr_warn("Panel does not support dynamic switch!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == pinfo->mipi.mode) {
|
|
|
|
pr_debug("Already in requested mode!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
pr_debug("Enter mode: %d\n", mode);
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
|
|
|
|
pdata->panel_info.dynamic_switch_pending = true;
|
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("mdss_fb_pan_idle for fb%d failed. ret=%d\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
pdata->panel_info.dynamic_switch_pending = false;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
bl_lvl = mfd->bl_level;
|
|
|
|
mdss_fb_set_backlight(mfd, 0);
|
|
|
|
mutex_unlock(&mfd->bl_lock);
|
|
|
|
|
|
|
|
lock_fb_info(mfd->fbi);
|
|
|
|
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi,
|
|
|
|
mfd->op_enable);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("can't turn off display!\n");
|
|
|
|
unlock_fb_info(mfd->fbi);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd->op_enable = false;
|
|
|
|
|
|
|
|
ret = mfd->mdp.configure_panel(mfd, mode, 1);
|
|
|
|
mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type);
|
|
|
|
|
|
|
|
mfd->op_enable = true;
|
|
|
|
|
|
|
|
ret = mdss_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi,
|
|
|
|
mfd->op_enable);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("can't turn on display!\n");
|
|
|
|
unlock_fb_info(mfd->fbi);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
unlock_fb_info(mfd->fbi);
|
|
|
|
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
mfd->allow_bl_update = true;
|
|
|
|
mdss_fb_set_backlight(mfd, bl_lvl);
|
|
|
|
mutex_unlock(&mfd->bl_lock);
|
|
|
|
|
|
|
|
pdata->panel_info.dynamic_switch_pending = false;
|
|
|
|
pdata->panel_info.is_lpm_mode = mode ? 1 : 0;
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
pr_err("can't turn on display!\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("Exit mode: %d\n", mode);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_change_dfps_mode(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
struct mdss_panel_info *pinfo;
|
|
|
|
u32 dfps_mode;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("no panel connected!\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
pinfo = &pdata->panel_info;
|
|
|
|
|
|
|
|
if (sscanf(buf, "%d", &dfps_mode) != 1) {
|
|
|
|
pr_err("sccanf buf error!\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dfps_mode >= DFPS_MODE_MAX) {
|
|
|
|
pinfo->dynamic_fps = false;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mfd->idle_time != 0) {
|
|
|
|
pr_err("ERROR: Idle time is not disabled.\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pinfo->current_fps != pinfo->default_fps) {
|
|
|
|
pr_err("ERROR: panel not configured to default fps\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
pinfo->dynamic_fps = true;
|
|
|
|
pinfo->dfps_update = dfps_mode;
|
|
|
|
|
|
|
|
if (pdata->next)
|
|
|
|
pdata->next->panel_info.dfps_update = dfps_mode;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t mdss_fb_get_dfps_mode(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
struct mdss_panel_info *pinfo;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("no panel connected!\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
pinfo = &pdata->panel_info;
|
|
|
|
|
|
|
|
ret = scnprintf(buf, PAGE_SIZE, "dfps enabled=%d mode=%d\n",
|
|
|
|
pinfo->dynamic_fps, pinfo->dfps_update);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static DEVICE_ATTR(msm_fb_type, S_IRUGO, mdss_fb_get_type, NULL);
|
2024-09-09 08:57:42 +00:00
|
|
|
static DEVICE_ATTR(msm_fb_split, S_IRUGO | S_IWUSR, mdss_fb_show_split,
|
|
|
|
mdss_fb_store_split);
|
|
|
|
static DEVICE_ATTR(show_blank_event, S_IRUGO, mdss_mdp_show_blank_event, NULL);
|
|
|
|
static DEVICE_ATTR(idle_time, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
|
|
mdss_fb_get_idle_time, mdss_fb_set_idle_time);
|
|
|
|
static DEVICE_ATTR(idle_notify, S_IRUGO, mdss_fb_get_idle_notify, NULL);
|
|
|
|
static DEVICE_ATTR(msm_fb_panel_info, S_IRUGO, mdss_fb_get_panel_info, NULL);
|
|
|
|
static DEVICE_ATTR(msm_fb_src_split_info, S_IRUGO, mdss_fb_get_src_split_info,
|
|
|
|
NULL);
|
|
|
|
static DEVICE_ATTR(msm_fb_thermal_level, S_IRUGO | S_IWUSR,
|
|
|
|
mdss_fb_get_thermal_level, mdss_fb_set_thermal_level);
|
|
|
|
static DEVICE_ATTR(msm_fb_panel_status, S_IRUGO | S_IWUSR,
|
|
|
|
mdss_fb_get_panel_status, mdss_fb_force_panel_dead);
|
|
|
|
static DEVICE_ATTR(msm_fb_dfps_mode, S_IRUGO | S_IWUSR,
|
|
|
|
mdss_fb_get_dfps_mode, mdss_fb_change_dfps_mode);
|
2024-09-09 08:52:07 +00:00
|
|
|
static struct attribute *mdss_fb_attrs[] = {
|
|
|
|
&dev_attr_msm_fb_type.attr,
|
2024-09-09 08:57:42 +00:00
|
|
|
&dev_attr_msm_fb_split.attr,
|
|
|
|
&dev_attr_show_blank_event.attr,
|
|
|
|
&dev_attr_idle_time.attr,
|
|
|
|
&dev_attr_idle_notify.attr,
|
|
|
|
&dev_attr_msm_fb_panel_info.attr,
|
|
|
|
&dev_attr_msm_fb_src_split_info.attr,
|
|
|
|
&dev_attr_msm_fb_thermal_level.attr,
|
|
|
|
&dev_attr_msm_fb_panel_status.attr,
|
|
|
|
&dev_attr_msm_fb_dfps_mode.attr,
|
2024-09-09 08:52:07 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_group mdss_fb_attr_group = {
|
|
|
|
.attrs = mdss_fb_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int mdss_fb_create_sysfs(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = sysfs_create_group(&mfd->fbi->dev->kobj, &mdss_fb_attr_group);
|
|
|
|
if (rc)
|
|
|
|
pr_err("sysfs group creation failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_remove_sysfs(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(&mfd->fbi->dev->kobj, &mdss_fb_attr_group);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void mdss_fb_shutdown(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
mfd->shutdown_pending = true;
|
|
|
|
|
|
|
|
/* wake up threads waiting on idle or kickoff queues */
|
|
|
|
wake_up_all(&mfd->idle_wait_q);
|
|
|
|
wake_up_all(&mfd->kickoff_wait_q);
|
|
|
|
|
|
|
|
lock_fb_info(mfd->fbi);
|
|
|
|
mdss_fb_release_all(mfd->fbi, true);
|
|
|
|
sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event");
|
|
|
|
unlock_fb_info(mfd->fbi);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_input_event_handler(struct input_handle *handle,
|
|
|
|
unsigned int type,
|
|
|
|
unsigned int code,
|
|
|
|
int value)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = handle->handler->private;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if ((type != EV_ABS) || !mdss_fb_is_power_on(mfd))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (mfd->mdp.input_event_handler) {
|
|
|
|
rc = mfd->mdp.input_event_handler(mfd);
|
|
|
|
if (rc)
|
|
|
|
pr_err("mdp input event handler failed\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_input_connect(struct input_handler *handler,
|
|
|
|
struct input_dev *dev,
|
|
|
|
const struct input_device_id *id)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct input_handle *handle;
|
|
|
|
|
|
|
|
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
|
|
|
|
if (!handle)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
handle->dev = dev;
|
|
|
|
handle->handler = handler;
|
|
|
|
handle->name = handler->name;
|
|
|
|
|
|
|
|
rc = input_register_handle(handle);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("failed to register input handle, rc = %d\n", rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = input_open_device(handle);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("failed to open input device, rc = %d\n", rc);
|
|
|
|
goto error_unregister;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_unregister:
|
|
|
|
input_unregister_handle(handle);
|
|
|
|
error:
|
|
|
|
kfree(handle);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_input_disconnect(struct input_handle *handle)
|
|
|
|
{
|
|
|
|
input_close_device(handle);
|
|
|
|
input_unregister_handle(handle);
|
|
|
|
kfree(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Structure for specifying event parameters on which to receive callbacks.
|
|
|
|
* This structure will trigger a callback in case of a touch event (specified by
|
|
|
|
* EV_ABS) where there is a change in X and Y coordinates,
|
|
|
|
*/
|
|
|
|
static const struct input_device_id mdss_fb_input_ids[] = {
|
|
|
|
{
|
|
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
|
|
|
|
.evbit = { BIT_MASK(EV_ABS) },
|
|
|
|
.absbit = { [BIT_WORD(ABS_MT_POSITION_X)] =
|
|
|
|
BIT_MASK(ABS_MT_POSITION_X) |
|
|
|
|
BIT_MASK(ABS_MT_POSITION_Y) },
|
|
|
|
},
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
|
|
|
|
static int mdss_fb_register_input_handler(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct input_handler *handler;
|
|
|
|
|
|
|
|
if (mfd->input_handler)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
handler = kzalloc(sizeof(*handler), GFP_KERNEL);
|
|
|
|
if (!handler)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
handler->event = mdss_fb_input_event_handler;
|
|
|
|
handler->connect = mdss_fb_input_connect;
|
|
|
|
handler->disconnect = mdss_fb_input_disconnect,
|
|
|
|
handler->name = "mdss_fb",
|
|
|
|
handler->id_table = mdss_fb_input_ids;
|
|
|
|
handler->private = mfd;
|
|
|
|
|
|
|
|
rc = input_register_handler(handler);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Unable to register the input handler\n");
|
|
|
|
kfree(handler);
|
|
|
|
} else {
|
|
|
|
mfd->input_handler = handler;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_unregister_input_handler(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
if (!mfd->input_handler)
|
|
|
|
return;
|
|
|
|
|
|
|
|
input_unregister_handler(mfd->input_handler);
|
|
|
|
kfree(mfd->input_handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_videomode_from_panel_timing(struct fb_videomode *videomode,
|
|
|
|
struct mdss_panel_timing *pt)
|
|
|
|
{
|
|
|
|
videomode->name = pt->name;
|
|
|
|
videomode->xres = pt->xres;
|
|
|
|
videomode->yres = pt->yres;
|
|
|
|
videomode->left_margin = pt->h_back_porch;
|
|
|
|
videomode->right_margin = pt->h_front_porch;
|
|
|
|
videomode->hsync_len = pt->h_pulse_width;
|
|
|
|
videomode->upper_margin = pt->v_back_porch;
|
|
|
|
videomode->lower_margin = pt->v_front_porch;
|
|
|
|
videomode->vsync_len = pt->v_pulse_width;
|
|
|
|
videomode->refresh = pt->frame_rate;
|
|
|
|
videomode->flag = 0;
|
|
|
|
videomode->vmode = 0;
|
|
|
|
videomode->sync = 0;
|
|
|
|
|
|
|
|
if (videomode->refresh) {
|
|
|
|
unsigned long clk_rate, h_total, v_total;
|
|
|
|
|
|
|
|
h_total = videomode->xres + videomode->left_margin
|
|
|
|
+ videomode->right_margin + videomode->hsync_len;
|
|
|
|
v_total = videomode->yres + videomode->lower_margin
|
|
|
|
+ videomode->upper_margin + videomode->vsync_len;
|
|
|
|
clk_rate = h_total * v_total * videomode->refresh;
|
|
|
|
videomode->pixclock =
|
|
|
|
KHZ2PICOS(clk_rate / 1000);
|
|
|
|
} else {
|
|
|
|
videomode->pixclock =
|
|
|
|
KHZ2PICOS((unsigned long)pt->clk_rate / 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_init_panel_modes(struct msm_fb_data_type *mfd,
|
|
|
|
struct mdss_panel_data *pdata)
|
|
|
|
{
|
|
|
|
struct fb_info *fbi = mfd->fbi;
|
|
|
|
struct fb_videomode *modedb;
|
|
|
|
struct mdss_panel_timing *pt;
|
|
|
|
struct list_head *pos;
|
|
|
|
int num_timings = 0;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
/* check if multiple modes are supported */
|
|
|
|
if (!pdata->timings_list.prev || !pdata->timings_list.next)
|
|
|
|
INIT_LIST_HEAD(&pdata->timings_list);
|
|
|
|
|
|
|
|
if (!fbi || !pdata->current_timing || list_empty(&pdata->timings_list))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
list_for_each(pos, &pdata->timings_list)
|
|
|
|
num_timings++;
|
|
|
|
|
|
|
|
modedb = devm_kzalloc(fbi->dev, num_timings * sizeof(*modedb),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!modedb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
list_for_each_entry(pt, &pdata->timings_list, list) {
|
|
|
|
struct mdss_panel_timing *spt = NULL;
|
|
|
|
|
|
|
|
mdss_fb_videomode_from_panel_timing(modedb + i, pt);
|
|
|
|
if (pdata->next) {
|
|
|
|
spt = mdss_panel_get_timing_by_name(pdata->next,
|
|
|
|
modedb[i].name);
|
|
|
|
if (!IS_ERR_OR_NULL(spt))
|
|
|
|
modedb[i].xres += spt->xres;
|
|
|
|
else
|
|
|
|
pr_debug("no matching split config for %s\n",
|
|
|
|
modedb[i].name);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if no panel timing found for current, need to
|
|
|
|
* disable it otherwise mark it as active
|
|
|
|
*/
|
|
|
|
if (pt == pdata->current_timing)
|
|
|
|
pdata->next->active = !IS_ERR_OR_NULL(spt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pt == pdata->current_timing) {
|
|
|
|
pr_debug("found current mode: %s\n", pt->name);
|
|
|
|
fbi->mode = modedb + i;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
fbi->monspecs.modedb = modedb;
|
|
|
|
fbi->monspecs.modedb_len = num_timings;
|
|
|
|
|
|
|
|
/* destroy and recreate modelist */
|
|
|
|
fb_destroy_modelist(&fbi->modelist);
|
|
|
|
|
|
|
|
if (fbi->mode)
|
|
|
|
fb_videomode_to_var(&fbi->var, fbi->mode);
|
|
|
|
fb_videomode_to_modelist(modedb, num_timings, &fbi->modelist);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = NULL;
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
struct fb_info *fbi;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (fbi_list_index >= MAX_FBI_LIST)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&pdev->dev);
|
|
|
|
if (!pdata)
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mdp_instance) {
|
|
|
|
pr_err("mdss mdp resource not initialized yet\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/*
|
|
|
|
* alloc framebuffer info + par data
|
|
|
|
*/
|
|
|
|
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
|
|
|
|
if (fbi == NULL) {
|
|
|
|
pr_err("can't allocate framebuffer info data!\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
mfd->key = MFD_KEY;
|
|
|
|
mfd->fbi = fbi;
|
|
|
|
mfd->panel_info = &pdata->panel_info;
|
|
|
|
mfd->panel.type = pdata->panel_info.type;
|
|
|
|
mfd->panel.id = mfd->index;
|
|
|
|
mfd->fb_page = MDSS_FB_NUM;
|
|
|
|
mfd->index = fbi_list_index;
|
|
|
|
mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->ext_ad_ctrl = -1;
|
|
|
|
if (mfd->panel_info && mfd->panel_info->brightness_max > 0)
|
|
|
|
MDSS_BRIGHT_TO_BL(mfd->bl_level, backlight_led.brightness,
|
|
|
|
mfd->panel_info->bl_max, mfd->panel_info->brightness_max);
|
|
|
|
else
|
|
|
|
mfd->bl_level = 0;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->bl_scale = 1024;
|
|
|
|
mfd->bl_min_lvl = 30;
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->ad_bl_level = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->fb_imgType = MDP_RGBA_8888;
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->calib_mode_bl = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
mfd->pdev = pdev;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
mfd->split_mode = MDP_SPLIT_MODE_NONE;
|
|
|
|
if (pdata->panel_info.is_split_display) {
|
|
|
|
struct mdss_panel_data *pnext = pdata->next;
|
|
|
|
|
|
|
|
mfd->split_fb_left = pdata->panel_info.lm_widths[0];
|
|
|
|
if (pnext)
|
|
|
|
mfd->split_fb_right = pnext->panel_info.lm_widths[0];
|
|
|
|
|
|
|
|
if (pdata->panel_info.use_pingpong_split)
|
|
|
|
mfd->split_mode = MDP_PINGPONG_SPLIT;
|
|
|
|
else
|
|
|
|
mfd->split_mode = MDP_DUAL_LM_DUAL_DISPLAY;
|
|
|
|
} else if ((pdata->panel_info.lm_widths[0] != 0) &&
|
|
|
|
(pdata->panel_info.lm_widths[1] != 0)) {
|
|
|
|
mfd->split_fb_left = pdata->panel_info.lm_widths[0];
|
|
|
|
mfd->split_fb_right = pdata->panel_info.lm_widths[1];
|
|
|
|
mfd->split_mode = MDP_DUAL_LM_SINGLE_DISPLAY;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("fb%d: split_mode:%d left:%d right:%d\n", mfd->index,
|
|
|
|
mfd->split_mode, mfd->split_fb_left, mfd->split_fb_right);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->mdp = *mdp_instance;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = of_property_read_bool(pdev->dev.of_node,
|
|
|
|
"qcom,boot-indication-enabled");
|
|
|
|
|
|
|
|
if (rc) {
|
|
|
|
led_trigger_register_simple("boot-indication",
|
|
|
|
&(mfd->boot_notification_led));
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&mfd->file_list);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mutex_init(&mfd->bl_lock);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_init(&mfd->switch_lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
fbi_list[fbi_list_index++] = fbi;
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, mfd);
|
|
|
|
|
|
|
|
rc = mdss_fb_register(mfd);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (mfd->mdp.init_fnc) {
|
|
|
|
rc = mfd->mdp.init_fnc(mfd);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("init_fnc failed\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = pm_runtime_set_active(mfd->fbi->dev);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("pm_runtime: fail to set active.\n");
|
|
|
|
pm_runtime_enable(mfd->fbi->dev);
|
|
|
|
|
|
|
|
/* android supports only one lcd-backlight/lcd for now */
|
|
|
|
if (!lcd_backlight_registered) {
|
2024-09-09 08:57:42 +00:00
|
|
|
backlight_led.brightness = mfd->panel_info->brightness_max;
|
|
|
|
backlight_led.max_brightness = mfd->panel_info->brightness_max;
|
2024-09-09 08:52:07 +00:00
|
|
|
if (led_classdev_register(&pdev->dev, &backlight_led))
|
|
|
|
pr_err("led_classdev_register failed\n");
|
|
|
|
else
|
|
|
|
lcd_backlight_registered = 1;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_fb_init_panel_modes(mfd, pdata);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mdss_fb_create_sysfs(mfd);
|
|
|
|
mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->mdp_sync_pt_data.fence_name = "mdp-fence";
|
|
|
|
if (mfd->mdp_sync_pt_data.timeline == NULL) {
|
2024-09-09 08:52:07 +00:00
|
|
|
char timeline_name[16];
|
|
|
|
snprintf(timeline_name, sizeof(timeline_name),
|
|
|
|
"mdss_fb_%d", mfd->index);
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->mdp_sync_pt_data.timeline =
|
|
|
|
sw_sync_timeline_create(timeline_name);
|
|
|
|
if (mfd->mdp_sync_pt_data.timeline == NULL) {
|
|
|
|
pr_err("cannot create release fence time line\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->mdp_sync_pt_data.notifier.notifier_call =
|
|
|
|
__mdss_fb_sync_buf_done_callback;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type);
|
|
|
|
|
|
|
|
if (mfd->mdp.splash_init_fnc)
|
|
|
|
mfd->mdp.splash_init_fnc(mfd);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Register with input driver for a callback for command mode panels.
|
|
|
|
* When there is an input event, mdp clocks will be turned on to reduce
|
|
|
|
* latency when a frame update happens.
|
|
|
|
* For video mode panels, idle timeout will be delayed so that userspace
|
|
|
|
* does not get an idle event while new frames are expected. In case of
|
|
|
|
* an idle event, user space tries to fall back to GPU composition which
|
|
|
|
* can lead to increased load when there are new frames.
|
|
|
|
*/
|
|
|
|
if ((mfd->panel_info->type == MIPI_CMD_PANEL) ||
|
|
|
|
(mfd->panel_info->type == MIPI_VIDEO_PANEL))
|
|
|
|
if (mdss_fb_register_input_handler(mfd))
|
|
|
|
pr_err("failed to register input handler\n");
|
|
|
|
|
|
|
|
INIT_DELAYED_WORK(&mfd->idle_notify_work, __mdss_fb_idle_notify_work);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void mdss_fb_set_mdp_sync_pt_threshold(struct msm_fb_data_type *mfd,
|
|
|
|
int type)
|
|
|
|
{
|
|
|
|
if (!mfd)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case WRITEBACK_PANEL:
|
|
|
|
mfd->mdp_sync_pt_data.threshold = 1;
|
|
|
|
mfd->mdp_sync_pt_data.retire_threshold = 0;
|
|
|
|
break;
|
|
|
|
case MIPI_CMD_PANEL:
|
|
|
|
mfd->mdp_sync_pt_data.threshold = 1;
|
|
|
|
mfd->mdp_sync_pt_data.retire_threshold = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
mfd->mdp_sync_pt_data.threshold = 2;
|
|
|
|
mfd->mdp_sync_pt_data.retire_threshold = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
|
|
|
|
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mdss_fb_remove_sysfs(mfd);
|
|
|
|
|
|
|
|
pm_runtime_disable(mfd->fbi->dev);
|
|
|
|
|
|
|
|
if (mfd->key != MFD_KEY)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_fb_unregister_input_handler(mfd);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (mdss_fb_suspend_sub(mfd))
|
|
|
|
pr_err("msm_fb_remove: can't stop the device %d\n",
|
|
|
|
mfd->index);
|
|
|
|
|
|
|
|
/* remove /dev/fb* */
|
|
|
|
unregister_framebuffer(mfd->fbi);
|
|
|
|
|
|
|
|
if (lcd_backlight_registered) {
|
|
|
|
lcd_backlight_registered = 0;
|
|
|
|
led_classdev_unregister(&backlight_led);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_send_panel_event(struct msm_fb_data_type *mfd,
|
|
|
|
int event, void *arg)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("no panel connected\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("sending event=%d for fb%d\n", event, mfd->index);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
do {
|
|
|
|
if (pdata->event_handler)
|
|
|
|
ret = pdata->event_handler(pdata, event, arg);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pdata = pdata->next;
|
|
|
|
} while (!ret && pdata);
|
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_suspend_sub(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if ((!mfd) || (mfd->key != MFD_KEY))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pr_debug("mdss_fb suspend index=%d\n", mfd->index);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
ret = mdss_fb_send_panel_event(mfd, MDSS_EVENT_SUSPEND, NULL);
|
|
|
|
if (ret) {
|
|
|
|
pr_warn("unable to suspend fb%d (%d)\n", mfd->index, ret);
|
2024-09-09 08:57:42 +00:00
|
|
|
goto exit;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mfd->suspend.op_enable = mfd->op_enable;
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->suspend.panel_power_state = mfd->panel_power_state;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (mfd->op_enable) {
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* Ideally, display should have either been blanked by now, or
|
|
|
|
* should have transitioned to a low power state. If not, then
|
|
|
|
* as a fall back option, enter ulp state to leave the display
|
|
|
|
* on, but turn off all interface clocks.
|
|
|
|
*/
|
|
|
|
if (mdss_fb_is_power_on(mfd)) {
|
|
|
|
ret = mdss_fb_blank_sub(BLANK_FLAG_ULP, mfd->fbi,
|
|
|
|
mfd->suspend.op_enable);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("can't turn off display!\n");
|
|
|
|
goto exit;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
mfd->op_enable = false;
|
|
|
|
fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED);
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
exit:
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_resume_sub(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if ((!mfd) || (mfd->key != MFD_KEY))
|
|
|
|
return 0;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reinit_completion(&mfd->power_set_comp);
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->is_power_setting = true;
|
|
|
|
pr_debug("mdss_fb resume index=%d\n", mfd->index);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
ret = mdss_fb_send_panel_event(mfd, MDSS_EVENT_RESUME, NULL);
|
|
|
|
if (ret) {
|
|
|
|
pr_warn("unable to resume fb%d (%d)\n", mfd->index, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* resume state var recover */
|
|
|
|
mfd->op_enable = mfd->suspend.op_enable;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* If the fb was explicitly blanked or transitioned to ulp during
|
|
|
|
* suspend, then undo it during resume with the appropriate unblank
|
|
|
|
* flag. If fb was in ulp state when entering suspend, then nothing
|
|
|
|
* needs to be done.
|
|
|
|
*/
|
|
|
|
if (mdss_panel_is_power_on(mfd->suspend.panel_power_state) &&
|
|
|
|
!mdss_panel_is_power_on_ulp(mfd->suspend.panel_power_state)) {
|
|
|
|
int unblank_flag = mdss_panel_is_power_on_interactive(
|
|
|
|
mfd->suspend.panel_power_state) ? FB_BLANK_UNBLANK :
|
|
|
|
BLANK_FLAG_LP;
|
|
|
|
|
|
|
|
ret = mdss_fb_blank_sub(unblank_flag, mfd->fbi, mfd->op_enable);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret)
|
|
|
|
pr_warn("can't turn on display!\n");
|
|
|
|
else
|
|
|
|
fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING);
|
|
|
|
}
|
|
|
|
mfd->is_power_setting = false;
|
|
|
|
complete_all(&mfd->power_set_comp);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
|
|
|
|
static int mdss_fb_suspend(struct platform_device *pdev, pm_message_t state)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
|
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
dev_dbg(&pdev->dev, "display suspend\n");
|
|
|
|
|
|
|
|
return mdss_fb_suspend_sub(mfd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_resume(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
|
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
dev_dbg(&pdev->dev, "display resume\n");
|
|
|
|
|
|
|
|
return mdss_fb_resume_sub(mfd);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define mdss_fb_suspend NULL
|
|
|
|
#define mdss_fb_resume NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
static int mdss_fb_pm_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
dev_dbg(dev, "display pm suspend\n");
|
|
|
|
|
|
|
|
return mdss_fb_suspend_sub(mfd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_pm_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
|
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
dev_dbg(dev, "display pm resume\n");
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* It is possible that the runtime status of the fb device may
|
|
|
|
* have been active when the system was suspended. Reset the runtime
|
|
|
|
* status to suspended state after a complete system resume.
|
|
|
|
*/
|
|
|
|
pm_runtime_disable(dev);
|
|
|
|
pm_runtime_set_suspended(dev);
|
|
|
|
pm_runtime_enable(dev);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return mdss_fb_resume_sub(mfd);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct dev_pm_ops mdss_fb_pm_ops = {
|
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(mdss_fb_pm_suspend, mdss_fb_pm_resume)
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct of_device_id mdss_fb_dt_match[] = {
|
|
|
|
{ .compatible = "qcom,mdss-fb",},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
EXPORT_COMPAT("qcom,mdss-fb");
|
|
|
|
|
|
|
|
static struct platform_driver mdss_fb_driver = {
|
|
|
|
.probe = mdss_fb_probe,
|
|
|
|
.remove = mdss_fb_remove,
|
|
|
|
.suspend = mdss_fb_suspend,
|
|
|
|
.resume = mdss_fb_resume,
|
2024-09-09 08:57:42 +00:00
|
|
|
.shutdown = mdss_fb_shutdown,
|
2024-09-09 08:52:07 +00:00
|
|
|
.driver = {
|
|
|
|
.name = "mdss_fb",
|
|
|
|
.of_match_table = mdss_fb_dt_match,
|
|
|
|
.pm = &mdss_fb_pm_ops,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static void mdss_fb_scale_bl(struct msm_fb_data_type *mfd, u32 *bl_lvl)
|
|
|
|
{
|
|
|
|
u32 temp = *bl_lvl;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
pr_debug("input = %d, scale = %d\n", temp, mfd->bl_scale);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (temp >= mfd->bl_min_lvl) {
|
2024-09-09 08:57:42 +00:00
|
|
|
if (temp > mfd->panel_info->bl_max) {
|
|
|
|
pr_warn("%s: invalid bl level\n",
|
|
|
|
__func__);
|
|
|
|
temp = mfd->panel_info->bl_max;
|
|
|
|
}
|
|
|
|
if (mfd->bl_scale > 1024) {
|
|
|
|
pr_warn("%s: invalid bl scale\n",
|
|
|
|
__func__);
|
|
|
|
mfd->bl_scale = 1024;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* bl_scale is the numerator of
|
|
|
|
* scaling fraction (x/1024)
|
|
|
|
*/
|
2024-09-09 08:52:07 +00:00
|
|
|
temp = (temp * mfd->bl_scale) / 1024;
|
|
|
|
|
|
|
|
/*if less than minimum level, use min level*/
|
|
|
|
if (temp < mfd->bl_min_lvl)
|
|
|
|
temp = mfd->bl_min_lvl;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("output = %d\n", temp);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
(*bl_lvl) = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* must call this function from within mfd->bl_lock */
|
|
|
|
void mdss_fb_set_backlight(struct msm_fb_data_type *mfd, u32 bkl_lvl)
|
|
|
|
{
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
u32 temp = bkl_lvl;
|
2024-09-09 08:57:42 +00:00
|
|
|
bool ad_bl_notify_needed = false;
|
|
|
|
bool bl_notify_needed = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if ((((mdss_fb_is_power_off(mfd) && mfd->dcm_state != DCM_ENTER)
|
|
|
|
|| !mfd->allow_bl_update) && !IS_CALIB_MODE_BL(mfd)) ||
|
|
|
|
mfd->panel_info->cont_splash_enabled) {
|
|
|
|
mfd->unset_bl_level = bkl_lvl;
|
2024-09-09 08:52:07 +00:00
|
|
|
return;
|
2024-09-09 08:57:42 +00:00
|
|
|
} else if (mdss_fb_is_power_on(mfd) && mfd->panel_info->panel_dead) {
|
|
|
|
mfd->unset_bl_level = mfd->bl_level;
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->unset_bl_level = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
|
|
|
|
if ((pdata) && (pdata->set_backlight)) {
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->mdp.ad_calc_bl)
|
|
|
|
(*mfd->mdp.ad_calc_bl)(mfd, temp, &temp,
|
|
|
|
&ad_bl_notify_needed);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!IS_CALIB_MODE_BL(mfd))
|
|
|
|
mdss_fb_scale_bl(mfd, &temp);
|
|
|
|
/*
|
|
|
|
* Even though backlight has been scaled, want to show that
|
|
|
|
* backlight has been set to bkl_lvl to those that read from
|
|
|
|
* sysfs node. Thus, need to set bl_level even if it appears
|
|
|
|
* the backlight has already been set to the level it is at,
|
|
|
|
* as well as setting bl_level to bkl_lvl even though the
|
|
|
|
* backlight has been set to the scaled value.
|
|
|
|
*/
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->bl_level_scaled == temp) {
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->bl_level = bkl_lvl;
|
2024-09-09 08:57:42 +00:00
|
|
|
} else {
|
|
|
|
if (mfd->bl_level != bkl_lvl)
|
|
|
|
bl_notify_needed = true;
|
|
|
|
pr_debug("backlight sent to panel :%d\n", temp);
|
|
|
|
pdata->set_backlight(pdata, temp);
|
|
|
|
mfd->bl_level = bkl_lvl;
|
|
|
|
mfd->bl_level_scaled = temp;
|
|
|
|
}
|
|
|
|
if (ad_bl_notify_needed)
|
|
|
|
mdss_fb_bl_update_notify(mfd,
|
|
|
|
NOTIFY_TYPE_BL_AD_ATTEN_UPDATE);
|
|
|
|
if (bl_notify_needed)
|
|
|
|
mdss_fb_bl_update_notify(mfd,
|
|
|
|
NOTIFY_TYPE_BL_UPDATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void mdss_fb_update_backlight(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
struct mdss_panel_data *pdata;
|
|
|
|
u32 temp;
|
|
|
|
bool bl_notify = false;
|
|
|
|
|
|
|
|
if (!mfd->unset_bl_level)
|
|
|
|
return;
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
if (!mfd->allow_bl_update) {
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if ((pdata) && (pdata->set_backlight)) {
|
|
|
|
mfd->bl_level = mfd->unset_bl_level;
|
|
|
|
temp = mfd->bl_level;
|
|
|
|
if (mfd->mdp.ad_calc_bl)
|
|
|
|
(*mfd->mdp.ad_calc_bl)(mfd, temp, &temp,
|
|
|
|
&bl_notify);
|
|
|
|
if (bl_notify)
|
|
|
|
mdss_fb_bl_update_notify(mfd,
|
|
|
|
NOTIFY_TYPE_BL_AD_ATTEN_UPDATE);
|
|
|
|
mdss_fb_bl_update_notify(mfd, NOTIFY_TYPE_BL_UPDATE);
|
|
|
|
pdata->set_backlight(pdata, temp);
|
|
|
|
mfd->bl_level_scaled = mfd->unset_bl_level;
|
|
|
|
mfd->allow_bl_update = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->bl_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_start_disp_thread(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
pr_debug("%pS: start display thread fb%d\n",
|
|
|
|
__builtin_return_address(0), mfd->index);
|
|
|
|
|
|
|
|
/* this is needed for new split request from debugfs */
|
|
|
|
mdss_fb_get_split(mfd);
|
|
|
|
|
|
|
|
atomic_set(&mfd->commits_pending, 0);
|
|
|
|
mfd->disp_thread = kthread_run(__mdss_fb_display_thread,
|
|
|
|
mfd, "mdss_fb%d", mfd->index);
|
|
|
|
|
|
|
|
if (IS_ERR(mfd->disp_thread)) {
|
|
|
|
pr_err("ERROR: unable to start display thread %d\n",
|
|
|
|
mfd->index);
|
|
|
|
ret = PTR_ERR(mfd->disp_thread);
|
|
|
|
mfd->disp_thread = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_fb_stop_disp_thread(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
pr_debug("%pS: stop display thread fb%d\n",
|
|
|
|
__builtin_return_address(0), mfd->index);
|
|
|
|
|
|
|
|
kthread_stop(mfd->disp_thread);
|
|
|
|
mfd->disp_thread = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mdss_panel_validate_debugfs_info(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
struct mdss_panel_info *panel_info = mfd->panel_info;
|
|
|
|
struct fb_info *fbi = mfd->fbi;
|
|
|
|
struct fb_var_screeninfo *var = &fbi->var;
|
|
|
|
struct mdss_panel_data *pdata = container_of(panel_info,
|
|
|
|
struct mdss_panel_data, panel_info);
|
|
|
|
|
|
|
|
if (panel_info->debugfs_info->override_flag) {
|
|
|
|
if (mfd->mdp.off_fnc) {
|
|
|
|
mfd->panel_reconfig = true;
|
|
|
|
mfd->mdp.off_fnc(mfd);
|
|
|
|
mfd->panel_reconfig = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
pr_debug("Overriding panel_info with debugfs_info\n");
|
|
|
|
panel_info->debugfs_info->override_flag = 0;
|
|
|
|
mdss_panel_debugfsinfo_to_panelinfo(panel_info);
|
|
|
|
if (is_panel_split(mfd) && pdata->next)
|
|
|
|
mdss_fb_validate_split(pdata->panel_info.xres,
|
|
|
|
pdata->next->panel_info.xres, mfd);
|
|
|
|
mdss_panelinfo_to_fb_var(panel_info, var);
|
|
|
|
if (mdss_fb_send_panel_event(mfd, MDSS_EVENT_CHECK_PARAMS,
|
|
|
|
panel_info))
|
|
|
|
pr_err("Failed to send panel event CHECK_PARAMS\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_blank_blank(struct msm_fb_data_type *mfd,
|
|
|
|
int req_power_state)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
int cur_power_state, current_bl;
|
|
|
|
|
|
|
|
if (!mfd)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!mdss_fb_is_power_on(mfd) || !mfd->mdp.off_fnc)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cur_power_state = mfd->panel_power_state;
|
|
|
|
|
|
|
|
pr_debug("Transitioning from %d --> %d\n", cur_power_state,
|
|
|
|
req_power_state);
|
|
|
|
|
|
|
|
if (cur_power_state == req_power_state) {
|
|
|
|
pr_debug("No change in power state\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
mfd->update.type = NOTIFY_TYPE_SUSPEND;
|
|
|
|
mfd->update.is_suspend = 1;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
complete(&mfd->update.comp);
|
|
|
|
del_timer(&mfd->no_update.timer);
|
|
|
|
mfd->no_update.value = NOTIFY_TYPE_SUSPEND;
|
|
|
|
complete(&mfd->no_update.comp);
|
|
|
|
|
|
|
|
mfd->op_enable = false;
|
|
|
|
if (mdss_panel_is_power_off(req_power_state)) {
|
|
|
|
/* Stop Display thread */
|
|
|
|
if (mfd->disp_thread)
|
|
|
|
mdss_fb_stop_disp_thread(mfd);
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
current_bl = mfd->bl_level;
|
|
|
|
mfd->allow_bl_update = true;
|
|
|
|
mdss_fb_set_backlight(mfd, 0);
|
|
|
|
mfd->allow_bl_update = false;
|
|
|
|
mfd->unset_bl_level = current_bl;
|
|
|
|
mutex_unlock(&mfd->bl_lock);
|
|
|
|
}
|
|
|
|
mfd->panel_power_state = req_power_state;
|
|
|
|
|
|
|
|
ret = mfd->mdp.off_fnc(mfd);
|
|
|
|
if (ret)
|
|
|
|
mfd->panel_power_state = cur_power_state;
|
|
|
|
else if (mdss_panel_is_power_off(req_power_state))
|
|
|
|
mdss_fb_release_fences(mfd);
|
|
|
|
mfd->op_enable = true;
|
|
|
|
complete(&mfd->power_off_comp);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_blank_unblank(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
int cur_power_state;
|
|
|
|
|
|
|
|
if (!mfd)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mfd->panel_info->debugfs_info)
|
|
|
|
mdss_panel_validate_debugfs_info(mfd);
|
|
|
|
|
|
|
|
/* Start Display thread */
|
|
|
|
if (mfd->disp_thread == NULL) {
|
|
|
|
ret = mdss_fb_start_disp_thread(mfd);
|
|
|
|
if (IS_ERR_VALUE(ret))
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
cur_power_state = mfd->panel_power_state;
|
|
|
|
pr_debug("Transitioning from %d --> %d\n", cur_power_state,
|
|
|
|
MDSS_PANEL_POWER_ON);
|
|
|
|
|
|
|
|
if (mdss_panel_is_power_on_interactive(cur_power_state)) {
|
|
|
|
pr_debug("No change in power state\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mfd->mdp.on_fnc) {
|
|
|
|
struct mdss_panel_info *panel_info = mfd->panel_info;
|
|
|
|
struct fb_var_screeninfo *var = &mfd->fbi->var;
|
|
|
|
|
|
|
|
ret = mfd->mdp.on_fnc(mfd);
|
|
|
|
if (ret) {
|
|
|
|
mdss_fb_stop_disp_thread(mfd);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_ON;
|
|
|
|
mfd->panel_info->panel_dead = false;
|
|
|
|
mutex_lock(&mfd->update.lock);
|
|
|
|
mfd->update.type = NOTIFY_TYPE_UPDATE;
|
|
|
|
mfd->update.is_suspend = 0;
|
|
|
|
mutex_unlock(&mfd->update.lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Panel info can change depending in the information
|
|
|
|
* programmed in the controller.
|
|
|
|
* Update this info in the upstream structs.
|
|
|
|
*/
|
|
|
|
mdss_panelinfo_to_fb_var(panel_info, var);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Start the work thread to signal idle time */
|
|
|
|
if (mfd->idle_time)
|
|
|
|
schedule_delayed_work(&mfd->idle_notify_work,
|
|
|
|
msecs_to_jiffies(mfd->idle_time));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset the backlight only if the panel was off */
|
|
|
|
if (mdss_panel_is_power_off(cur_power_state)) {
|
|
|
|
mutex_lock(&mfd->bl_lock);
|
|
|
|
if (!mfd->allow_bl_update) {
|
|
|
|
mfd->allow_bl_update = true;
|
|
|
|
/*
|
|
|
|
* If in AD calibration mode then frameworks would not
|
|
|
|
* be allowed to update backlight hence post unblank
|
|
|
|
* the backlight would remain 0 (0 is set in blank).
|
|
|
|
* Hence resetting back to calibration mode value
|
|
|
|
*/
|
|
|
|
if (!IS_CALIB_MODE_BL(mfd))
|
|
|
|
mdss_fb_set_backlight(mfd, mfd->unset_bl_level);
|
|
|
|
else
|
|
|
|
mdss_fb_set_backlight(mfd, mfd->calib_mode_bl);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* it blocks the backlight update between unblank and
|
|
|
|
* first kickoff to avoid backlight turn on before black
|
|
|
|
* frame is transferred to panel through unblank call.
|
|
|
|
*/
|
|
|
|
mfd->allow_bl_update = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&mfd->bl_lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
error:
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_blank_sub(int blank_mode, struct fb_info *info,
|
|
|
|
int op_enable)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
int ret = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
int cur_power_state, req_power_state = MDSS_PANEL_POWER_OFF;
|
|
|
|
char trace_buffer[32];
|
|
|
|
|
|
|
|
if (!mfd || !op_enable)
|
|
|
|
return -EPERM;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->dcm_state == DCM_ENTER)
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("%pS mode:%d\n", __builtin_return_address(0),
|
|
|
|
blank_mode);
|
|
|
|
|
|
|
|
snprintf(trace_buffer, sizeof(trace_buffer), "fb%d blank %d",
|
|
|
|
mfd->index, blank_mode);
|
|
|
|
ATRACE_BEGIN(trace_buffer);
|
|
|
|
|
|
|
|
cur_power_state = mfd->panel_power_state;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Low power (lp) and ultra low pwoer (ulp) modes are currently only
|
|
|
|
* supported for command mode panels. For all other panel, treat lp
|
|
|
|
* mode as full unblank and ulp mode as full blank.
|
|
|
|
*/
|
|
|
|
if (mfd->panel_info->type != MIPI_CMD_PANEL) {
|
|
|
|
if (BLANK_FLAG_LP == blank_mode) {
|
|
|
|
pr_debug("lp mode only valid for cmd mode panels\n");
|
|
|
|
if (mdss_fb_is_power_on_interactive(mfd))
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
blank_mode = FB_BLANK_UNBLANK;
|
|
|
|
} else if (BLANK_FLAG_ULP == blank_mode) {
|
|
|
|
pr_debug("ulp mode valid for cmd mode panels\n");
|
|
|
|
if (mdss_fb_is_power_off(mfd))
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
blank_mode = FB_BLANK_POWERDOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
switch (blank_mode) {
|
|
|
|
case FB_BLANK_UNBLANK:
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("unblank called. cur pwr state=%d\n", cur_power_state);
|
|
|
|
ret = mdss_fb_blank_unblank(mfd);
|
|
|
|
break;
|
|
|
|
case BLANK_FLAG_ULP:
|
|
|
|
req_power_state = MDSS_PANEL_POWER_LP2;
|
|
|
|
pr_debug("ultra low power mode requested\n");
|
|
|
|
if (mdss_fb_is_power_off(mfd)) {
|
|
|
|
pr_debug("Unsupp transition: off --> ulp\n");
|
|
|
|
return 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
ret = mdss_fb_blank_blank(mfd, req_power_state);
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
2024-09-09 08:57:42 +00:00
|
|
|
case BLANK_FLAG_LP:
|
|
|
|
req_power_state = MDSS_PANEL_POWER_LP1;
|
|
|
|
pr_debug(" power mode requested\n");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If low power mode is requested when panel is already off,
|
|
|
|
* then first unblank the panel before entering low power mode
|
|
|
|
*/
|
|
|
|
if (mdss_fb_is_power_off(mfd) && mfd->mdp.on_fnc) {
|
|
|
|
pr_debug("off --> lp. switch to on first\n");
|
|
|
|
ret = mdss_fb_blank_unblank(mfd);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_blank_blank(mfd, req_power_state);
|
|
|
|
break;
|
2024-09-09 08:52:07 +00:00
|
|
|
case FB_BLANK_HSYNC_SUSPEND:
|
|
|
|
case FB_BLANK_POWERDOWN:
|
|
|
|
default:
|
2024-09-09 08:57:42 +00:00
|
|
|
req_power_state = MDSS_PANEL_POWER_OFF;
|
|
|
|
pr_debug("blank powerdown called\n");
|
|
|
|
ret = mdss_fb_blank_blank(mfd, req_power_state);
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Notify listeners */
|
|
|
|
sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event");
|
|
|
|
|
|
|
|
ATRACE_END(trace_buffer);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_blank(int blank_mode, struct fb_info *info)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret;
|
|
|
|
struct mdss_panel_data *pdata;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (mfd->op_enable == 0) {
|
|
|
|
if (blank_mode == FB_BLANK_UNBLANK)
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_ON;
|
|
|
|
else if (blank_mode == BLANK_FLAG_ULP)
|
|
|
|
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP2;
|
|
|
|
else if (blank_mode == BLANK_FLAG_LP)
|
|
|
|
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP1;
|
2024-09-09 08:52:07 +00:00
|
|
|
else
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_OFF;
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("mode: %d\n", blank_mode);
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
|
|
|
|
if (pdata->panel_info.is_lpm_mode &&
|
|
|
|
blank_mode == FB_BLANK_UNBLANK) {
|
|
|
|
pr_debug("panel is in lpm mode\n");
|
|
|
|
mfd->mdp.configure_panel(mfd, 0, 1);
|
|
|
|
mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type);
|
|
|
|
pdata->panel_info.is_lpm_mode = false;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return mdss_fb_blank_sub(blank_mode, info, mfd->op_enable);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static inline int mdss_fb_create_ion_client(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
mfd->fb_ion_client = msm_ion_client_create("mdss_fb_iclient");
|
|
|
|
if (IS_ERR_OR_NULL(mfd->fb_ion_client)) {
|
|
|
|
pr_err("Err:client not created, val %d\n",
|
|
|
|
PTR_RET(mfd->fb_ion_client));
|
|
|
|
mfd->fb_ion_client = NULL;
|
|
|
|
return PTR_RET(mfd->fb_ion_client);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mdss_fb_free_fb_ion_memory(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
if (!mfd) {
|
|
|
|
pr_err("no mfd\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mfd->fbi->screen_base)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!mfd->fb_ion_client || !mfd->fb_ion_handle) {
|
|
|
|
pr_err("invalid input parameters for fb%d\n", mfd->index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd->fbi->screen_base = NULL;
|
|
|
|
mfd->fbi->fix.smem_start = 0;
|
|
|
|
|
|
|
|
ion_unmap_kernel(mfd->fb_ion_client, mfd->fb_ion_handle);
|
|
|
|
|
|
|
|
if (mfd->mdp.fb_mem_get_iommu_domain) {
|
|
|
|
dma_buf_unmap_attachment(mfd->fb_attachment, mfd->fb_table,
|
|
|
|
DMA_BIDIRECTIONAL);
|
|
|
|
dma_buf_detach(mfd->fbmem_buf, mfd->fb_attachment);
|
|
|
|
dma_buf_put(mfd->fbmem_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
ion_free(mfd->fb_ion_client, mfd->fb_ion_handle);
|
|
|
|
mfd->fb_ion_handle = NULL;
|
|
|
|
mfd->fbmem_buf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_fb_alloc_fb_ion_memory(struct msm_fb_data_type *mfd, size_t fb_size)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
void *vaddr;
|
|
|
|
int domain;
|
|
|
|
|
|
|
|
if (!mfd) {
|
|
|
|
pr_err("Invalid input param - no mfd\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mfd->fb_ion_client) {
|
|
|
|
rc = mdss_fb_create_ion_client(mfd);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("fb ion client couldn't be created - %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("size for mmap = %zu\n", fb_size);
|
|
|
|
mfd->fb_ion_handle = ion_alloc(mfd->fb_ion_client, fb_size, SZ_4K,
|
|
|
|
ION_HEAP(ION_SYSTEM_HEAP_ID), 0);
|
|
|
|
if (IS_ERR_OR_NULL(mfd->fb_ion_handle)) {
|
|
|
|
pr_err("unable to alloc fbmem from ion - %ld\n",
|
|
|
|
PTR_ERR(mfd->fb_ion_handle));
|
|
|
|
return PTR_ERR(mfd->fb_ion_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mfd->mdp.fb_mem_get_iommu_domain) {
|
|
|
|
mfd->fbmem_buf = ion_share_dma_buf(mfd->fb_ion_client,
|
|
|
|
mfd->fb_ion_handle);
|
|
|
|
if (IS_ERR(mfd->fbmem_buf)) {
|
|
|
|
rc = PTR_ERR(mfd->fbmem_buf);
|
|
|
|
goto fb_mmap_failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
domain = mfd->mdp.fb_mem_get_iommu_domain();
|
|
|
|
|
|
|
|
mfd->fb_attachment = mdss_smmu_dma_buf_attach(mfd->fbmem_buf,
|
|
|
|
&mfd->pdev->dev, domain);
|
|
|
|
if (IS_ERR(mfd->fb_attachment)) {
|
|
|
|
rc = PTR_ERR(mfd->fb_attachment);
|
|
|
|
goto err_put;
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd->fb_table = dma_buf_map_attachment(mfd->fb_attachment,
|
|
|
|
DMA_BIDIRECTIONAL);
|
|
|
|
if (IS_ERR(mfd->fb_table)) {
|
|
|
|
rc = PTR_ERR(mfd->fb_table);
|
|
|
|
goto err_detach;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pr_err("No IOMMU Domain\n");
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto fb_mmap_failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
vaddr = ion_map_kernel(mfd->fb_ion_client, mfd->fb_ion_handle);
|
|
|
|
if (IS_ERR_OR_NULL(vaddr)) {
|
|
|
|
pr_err("ION memory mapping failed - %ld\n", PTR_ERR(vaddr));
|
|
|
|
rc = PTR_ERR(vaddr);
|
|
|
|
goto err_unmap;
|
|
|
|
}
|
|
|
|
pr_debug("alloc 0x%zuB vaddr = %p for fb%d\n", fb_size,
|
|
|
|
vaddr, mfd->index);
|
|
|
|
|
|
|
|
mfd->fbi->screen_base = (char *) vaddr;
|
|
|
|
mfd->fbi->fix.smem_len = fb_size;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
err_unmap:
|
|
|
|
dma_buf_unmap_attachment(mfd->fb_attachment, mfd->fb_table,
|
|
|
|
DMA_BIDIRECTIONAL);
|
|
|
|
err_detach:
|
|
|
|
dma_buf_detach(mfd->fbmem_buf, mfd->fb_attachment);
|
|
|
|
err_put:
|
|
|
|
dma_buf_put(mfd->fbmem_buf);
|
|
|
|
fb_mmap_failed:
|
|
|
|
ion_free(mfd->fb_ion_client, mfd->fb_ion_handle);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mdss_fb_fbmem_ion_mmap() - Custom fb mmap() function for MSM driver.
|
|
|
|
*
|
|
|
|
* @info - Framebuffer info.
|
|
|
|
* @vma - VM area which is part of the process virtual memory.
|
|
|
|
*
|
|
|
|
* This framebuffer mmap function differs from standard mmap() function by
|
|
|
|
* allowing for customized page-protection and dynamically allocate framebuffer
|
|
|
|
* memory from system heap and map to iommu virtual address.
|
|
|
|
*
|
|
|
|
* Return: virtual address is returned through vma
|
|
|
|
*/
|
|
|
|
static int mdss_fb_fbmem_ion_mmap(struct fb_info *info,
|
|
|
|
struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
size_t req_size, fb_size;
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct sg_table *table;
|
|
|
|
unsigned long addr = vma->vm_start;
|
|
|
|
unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
|
|
|
|
struct scatterlist *sg;
|
|
|
|
unsigned int i;
|
|
|
|
struct page *page;
|
|
|
|
|
|
|
|
if (!mfd || !mfd->pdev || !mfd->pdev->dev.of_node) {
|
|
|
|
pr_err("Invalid device node\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
req_size = vma->vm_end - vma->vm_start;
|
|
|
|
fb_size = mfd->fbi->fix.smem_len;
|
|
|
|
if (req_size > fb_size) {
|
|
|
|
pr_warn("requested map is greater than framebuffer\n");
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mfd->fbi->screen_base) {
|
|
|
|
rc = mdss_fb_alloc_fb_ion_memory(mfd, fb_size);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("fb mmap failed!!!!\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
table = mfd->fb_table;
|
|
|
|
if (IS_ERR(table)) {
|
|
|
|
pr_err("Unable to get sg_table from ion:%ld\n", PTR_ERR(table));
|
|
|
|
mfd->fbi->screen_base = NULL;
|
|
|
|
return PTR_ERR(table);
|
|
|
|
} else if (!table) {
|
|
|
|
pr_err("sg_list is NULL\n");
|
|
|
|
mfd->fbi->screen_base = NULL;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
page = sg_page(table->sgl);
|
|
|
|
if (page) {
|
|
|
|
for_each_sg(table->sgl, sg, table->nents, i) {
|
|
|
|
unsigned long remainder = vma->vm_end - addr;
|
|
|
|
unsigned long len = sg->length;
|
|
|
|
|
|
|
|
page = sg_page(sg);
|
|
|
|
|
|
|
|
if (offset >= sg->length) {
|
|
|
|
offset -= sg->length;
|
|
|
|
continue;
|
|
|
|
} else if (offset) {
|
|
|
|
page += offset / PAGE_SIZE;
|
|
|
|
len = sg->length - offset;
|
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
len = min(len, remainder);
|
|
|
|
|
|
|
|
if (mfd->mdp_fb_page_protection ==
|
|
|
|
MDP_FB_PAGE_PROTECTION_WRITECOMBINE)
|
|
|
|
vma->vm_page_prot =
|
|
|
|
pgprot_writecombine(vma->vm_page_prot);
|
|
|
|
|
|
|
|
pr_debug("vma=%p, addr=%x len=%ld\n",
|
|
|
|
vma, (unsigned int)addr, len);
|
|
|
|
pr_debug("vm_start=%x vm_end=%x vm_page_prot=%ld\n",
|
|
|
|
(unsigned int)vma->vm_start,
|
|
|
|
(unsigned int)vma->vm_end,
|
|
|
|
(unsigned long int)vma->vm_page_prot);
|
|
|
|
|
|
|
|
io_remap_pfn_range(vma, addr, page_to_pfn(page), len,
|
|
|
|
vma->vm_page_prot);
|
|
|
|
addr += len;
|
|
|
|
if (addr >= vma->vm_end)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pr_err("PAGE is null\n");
|
|
|
|
mdss_fb_free_fb_ion_memory(mfd);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/*
|
2024-09-09 08:57:42 +00:00
|
|
|
* mdss_fb_physical_mmap() - Custom fb mmap() function for MSM driver.
|
|
|
|
*
|
|
|
|
* @info - Framebuffer info.
|
|
|
|
* @vma - VM area which is part of the process virtual memory.
|
|
|
|
*
|
|
|
|
* This framebuffer mmap function differs from standard mmap() function as
|
|
|
|
* map to framebuffer memory from the CMA memory which is allocated during
|
|
|
|
* bootup.
|
|
|
|
*
|
|
|
|
* Return: virtual address is returned through vma
|
2024-09-09 08:52:07 +00:00
|
|
|
*/
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_physical_mmap(struct fb_info *info,
|
|
|
|
struct vm_area_struct *vma)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
/* Get frame buffer memory range. */
|
|
|
|
unsigned long start = info->fix.smem_start;
|
|
|
|
u32 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
|
|
|
|
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
|
|
|
if (!start) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_warn("No framebuffer memory is allocated\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set VM flags. */
|
|
|
|
start &= PAGE_MASK;
|
|
|
|
if ((vma->vm_end <= vma->vm_start) ||
|
2024-09-09 08:57:42 +00:00
|
|
|
(off >= len) ||
|
|
|
|
((vma->vm_end - vma->vm_start) > (len - off)))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
off += start;
|
|
|
|
if (off < start)
|
|
|
|
return -EINVAL;
|
|
|
|
vma->vm_pgoff = off >> PAGE_SHIFT;
|
|
|
|
/* This is an IO map - tell maydump to skip this VMA */
|
2024-09-09 08:57:42 +00:00
|
|
|
vma->vm_flags |= VM_IO;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (mfd->mdp_fb_page_protection == MDP_FB_PAGE_PROTECTION_WRITECOMBINE)
|
|
|
|
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
|
|
|
|
|
|
|
|
/* Remap the frame buffer I/O range */
|
|
|
|
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
|
2024-09-09 08:57:42 +00:00
|
|
|
vma->vm_end - vma->vm_start,
|
|
|
|
vma->vm_page_prot))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EAGAIN;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
int rc = -EINVAL;
|
|
|
|
|
|
|
|
if (mfd->fb_mmap_type == MDP_FB_MMAP_ION_ALLOC) {
|
|
|
|
rc = mdss_fb_fbmem_ion_mmap(info, vma);
|
|
|
|
} else if (mfd->fb_mmap_type == MDP_FB_MMAP_PHYSICAL_ALLOC) {
|
|
|
|
rc = mdss_fb_physical_mmap(info, vma);
|
|
|
|
} else {
|
|
|
|
if (!info->fix.smem_start && !mfd->fb_ion_handle) {
|
|
|
|
rc = mdss_fb_fbmem_ion_mmap(info, vma);
|
|
|
|
mfd->fb_mmap_type = MDP_FB_MMAP_ION_ALLOC;
|
|
|
|
} else {
|
|
|
|
rc = mdss_fb_physical_mmap(info, vma);
|
|
|
|
mfd->fb_mmap_type = MDP_FB_MMAP_PHYSICAL_ALLOC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("fb mmap failed with rc = %d\n", rc);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static struct fb_ops mdss_fb_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.fb_open = mdss_fb_open,
|
|
|
|
.fb_release = mdss_fb_release,
|
|
|
|
.fb_check_var = mdss_fb_check_var, /* vinfo check */
|
|
|
|
.fb_set_par = mdss_fb_set_par, /* set the video mode */
|
|
|
|
.fb_blank = mdss_fb_blank, /* blank display */
|
|
|
|
.fb_pan_display = mdss_fb_pan_display, /* pan display */
|
2024-09-09 08:57:42 +00:00
|
|
|
.fb_ioctl_v2 = mdss_fb_ioctl, /* perform fb specific ioctl */
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
.fb_compat_ioctl_v2 = mdss_fb_compat_ioctl,
|
|
|
|
#endif
|
2024-09-09 08:52:07 +00:00
|
|
|
.fb_mmap = mdss_fb_mmap,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int mdss_fb_alloc_fbmem_iommu(struct msm_fb_data_type *mfd, int dom)
|
|
|
|
{
|
|
|
|
void *virt = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
phys_addr_t phys = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
size_t size = 0;
|
|
|
|
struct platform_device *pdev = mfd->pdev;
|
2024-09-09 08:57:42 +00:00
|
|
|
int rc = 0;
|
|
|
|
struct device_node *fbmem_pnode = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!pdev || !pdev->dev.of_node) {
|
|
|
|
pr_err("Invalid device node\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
fbmem_pnode = of_parse_phandle(pdev->dev.of_node,
|
|
|
|
"linux,contiguous-region", 0);
|
|
|
|
if (!fbmem_pnode) {
|
|
|
|
pr_debug("fbmem is not reserved for %s\n", pdev->name);
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->fbi->screen_base = NULL;
|
|
|
|
mfd->fbi->fix.smem_start = 0;
|
|
|
|
return 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
} else {
|
|
|
|
const u32 *addr;
|
|
|
|
u64 len;
|
|
|
|
|
|
|
|
addr = of_get_address(fbmem_pnode, 0, &len, NULL);
|
|
|
|
if (!addr) {
|
|
|
|
pr_err("fbmem size is not specified\n");
|
|
|
|
of_node_put(fbmem_pnode);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
size = (size_t)len;
|
|
|
|
of_node_put(fbmem_pnode);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("%s frame buffer reserve_size=0x%zx\n", __func__, size);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (size < PAGE_ALIGN(mfd->fbi->fix.line_length *
|
|
|
|
mfd->fbi->var.yres_virtual))
|
|
|
|
pr_warn("reserve size is smaller than framebuffer size\n");
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = mdss_smmu_dma_alloc_coherent(&pdev->dev, size, &phys, &mfd->iova,
|
|
|
|
&virt, GFP_KERNEL, dom);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("unable to alloc fbmem size=%zx\n", size);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (MDSS_LPAE_CHECK(phys)) {
|
|
|
|
pr_warn("fb mem phys %pa > 4GB is not supported.\n", &phys);
|
|
|
|
mdss_smmu_dma_free_coherent(&pdev->dev, size, &virt,
|
|
|
|
phys, mfd->iova, dom);
|
|
|
|
return -ERANGE;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("alloc 0x%zxB @ (%pa phys) (0x%p virt) (%pa iova) for fb%d\n",
|
|
|
|
size, &phys, virt, &mfd->iova, mfd->index);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
mfd->fbi->screen_base = virt;
|
|
|
|
mfd->fbi->fix.smem_start = phys;
|
|
|
|
mfd->fbi->fix.smem_len = size;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_alloc_fbmem(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->mdp.fb_mem_alloc_fnc) {
|
2024-09-09 08:52:07 +00:00
|
|
|
return mfd->mdp.fb_mem_alloc_fnc(mfd);
|
2024-09-09 08:57:42 +00:00
|
|
|
} else if (mfd->mdp.fb_mem_get_iommu_domain) {
|
2024-09-09 08:52:07 +00:00
|
|
|
int dom = mfd->mdp.fb_mem_get_iommu_domain();
|
|
|
|
if (dom >= 0)
|
|
|
|
return mdss_fb_alloc_fbmem_iommu(mfd, dom);
|
|
|
|
else
|
|
|
|
return -ENOMEM;
|
|
|
|
} else {
|
|
|
|
pr_err("no fb memory allocator function defined\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_register(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret = -ENODEV;
|
|
|
|
int bpp;
|
2024-09-09 08:57:42 +00:00
|
|
|
char panel_name[20];
|
2024-09-09 08:52:07 +00:00
|
|
|
struct mdss_panel_info *panel_info = mfd->panel_info;
|
|
|
|
struct fb_info *fbi = mfd->fbi;
|
|
|
|
struct fb_fix_screeninfo *fix;
|
|
|
|
struct fb_var_screeninfo *var;
|
|
|
|
int *id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fb info initialization
|
|
|
|
*/
|
|
|
|
fix = &fbi->fix;
|
|
|
|
var = &fbi->var;
|
|
|
|
|
|
|
|
fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */
|
|
|
|
fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */
|
|
|
|
fix->ywrapstep = 0; /* No support */
|
|
|
|
fix->mmio_start = 0; /* No MMIO Address */
|
|
|
|
fix->mmio_len = 0; /* No MMIO Address */
|
|
|
|
fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */
|
|
|
|
|
|
|
|
var->xoffset = 0, /* Offset from virtual to visible */
|
|
|
|
var->yoffset = 0, /* resolution */
|
|
|
|
var->grayscale = 0, /* No graylevels */
|
|
|
|
var->nonstd = 0, /* standard pixel format */
|
|
|
|
var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */
|
|
|
|
var->height = -1, /* height of picture in mm */
|
|
|
|
var->width = -1, /* width of picture in mm */
|
|
|
|
var->accel_flags = 0, /* acceleration flags */
|
|
|
|
var->sync = 0, /* see FB_SYNC_* */
|
|
|
|
var->rotate = 0, /* angle we rotate counter clockwise */
|
|
|
|
mfd->op_enable = false;
|
|
|
|
|
|
|
|
switch (mfd->fb_imgType) {
|
|
|
|
case MDP_RGB_565:
|
|
|
|
fix->type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
fix->xpanstep = 1;
|
|
|
|
fix->ypanstep = 1;
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
|
|
|
var->blue.offset = 0;
|
|
|
|
var->green.offset = 5;
|
|
|
|
var->red.offset = 11;
|
|
|
|
var->blue.length = 5;
|
|
|
|
var->green.length = 6;
|
|
|
|
var->red.length = 5;
|
|
|
|
var->blue.msb_right = 0;
|
|
|
|
var->green.msb_right = 0;
|
|
|
|
var->red.msb_right = 0;
|
|
|
|
var->transp.offset = 0;
|
|
|
|
var->transp.length = 0;
|
|
|
|
bpp = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDP_RGB_888:
|
|
|
|
fix->type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
fix->xpanstep = 1;
|
|
|
|
fix->ypanstep = 1;
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
|
|
|
var->blue.offset = 0;
|
|
|
|
var->green.offset = 8;
|
|
|
|
var->red.offset = 16;
|
|
|
|
var->blue.length = 8;
|
|
|
|
var->green.length = 8;
|
|
|
|
var->red.length = 8;
|
|
|
|
var->blue.msb_right = 0;
|
|
|
|
var->green.msb_right = 0;
|
|
|
|
var->red.msb_right = 0;
|
|
|
|
var->transp.offset = 0;
|
|
|
|
var->transp.length = 0;
|
|
|
|
bpp = 3;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDP_ARGB_8888:
|
|
|
|
fix->type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
fix->xpanstep = 1;
|
|
|
|
fix->ypanstep = 1;
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
2024-09-09 08:57:42 +00:00
|
|
|
var->blue.offset = 24;
|
|
|
|
var->green.offset = 16;
|
|
|
|
var->red.offset = 8;
|
2024-09-09 08:52:07 +00:00
|
|
|
var->blue.length = 8;
|
|
|
|
var->green.length = 8;
|
|
|
|
var->red.length = 8;
|
|
|
|
var->blue.msb_right = 0;
|
|
|
|
var->green.msb_right = 0;
|
|
|
|
var->red.msb_right = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
var->transp.offset = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
var->transp.length = 8;
|
|
|
|
bpp = 4;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDP_RGBA_8888:
|
|
|
|
fix->type = FB_TYPE_PACKED_PIXELS;
|
|
|
|
fix->xpanstep = 1;
|
|
|
|
fix->ypanstep = 1;
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
2024-09-09 08:57:42 +00:00
|
|
|
var->blue.offset = 16;
|
|
|
|
var->green.offset = 8;
|
|
|
|
var->red.offset = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
var->blue.length = 8;
|
|
|
|
var->green.length = 8;
|
|
|
|
var->red.length = 8;
|
|
|
|
var->blue.msb_right = 0;
|
|
|
|
var->green.msb_right = 0;
|
|
|
|
var->red.msb_right = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
var->transp.offset = 24;
|
2024-09-09 08:52:07 +00:00
|
|
|
var->transp.length = 8;
|
|
|
|
bpp = 4;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDP_YCRYCB_H2V1:
|
|
|
|
fix->type = FB_TYPE_INTERLEAVED_PLANES;
|
|
|
|
fix->xpanstep = 2;
|
|
|
|
fix->ypanstep = 1;
|
|
|
|
var->vmode = FB_VMODE_NONINTERLACED;
|
|
|
|
|
|
|
|
/* how about R/G/B offset? */
|
|
|
|
var->blue.offset = 0;
|
|
|
|
var->green.offset = 5;
|
|
|
|
var->red.offset = 11;
|
|
|
|
var->blue.length = 5;
|
|
|
|
var->green.length = 6;
|
|
|
|
var->red.length = 5;
|
|
|
|
var->blue.msb_right = 0;
|
|
|
|
var->green.msb_right = 0;
|
|
|
|
var->red.msb_right = 0;
|
|
|
|
var->transp.offset = 0;
|
|
|
|
var->transp.length = 0;
|
|
|
|
bpp = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
pr_err("msm_fb_init: fb %d unkown image type!\n",
|
|
|
|
mfd->index);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_panelinfo_to_fb_var(panel_info, var);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
fix->type = panel_info->is_3d_panel;
|
|
|
|
if (mfd->mdp.fb_stride)
|
|
|
|
fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres,
|
|
|
|
bpp);
|
|
|
|
else
|
|
|
|
fix->line_length = var->xres * bpp;
|
|
|
|
|
|
|
|
var->xres_virtual = var->xres;
|
|
|
|
var->yres_virtual = panel_info->yres * mfd->fb_page;
|
|
|
|
var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* Populate smem length here for uspace to get the
|
|
|
|
* Framebuffer size when FBIO_FSCREENINFO ioctl is called.
|
|
|
|
*/
|
|
|
|
fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* id field for fb app */
|
2024-09-09 08:52:07 +00:00
|
|
|
id = (int *)&mfd->panel;
|
|
|
|
|
|
|
|
snprintf(fix->id, sizeof(fix->id), "mdssfb_%x", (u32) *id);
|
|
|
|
|
|
|
|
fbi->fbops = &mdss_fb_ops;
|
|
|
|
fbi->flags = FBINFO_FLAG_DEFAULT;
|
|
|
|
fbi->pseudo_palette = mdss_fb_pseudo_palette;
|
|
|
|
|
|
|
|
mfd->ref_cnt = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
|
|
|
|
mfd->dcm_state = DCM_UNINIT;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mdss_fb_alloc_fbmem(mfd))
|
|
|
|
pr_warn("unable to allocate fb memory in fb register\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
mfd->op_enable = true;
|
|
|
|
|
|
|
|
mutex_init(&mfd->update.lock);
|
|
|
|
mutex_init(&mfd->no_update.lock);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_init(&mfd->mdp_sync_pt_data.sync_mutex);
|
|
|
|
atomic_set(&mfd->mdp_sync_pt_data.commit_cnt, 0);
|
|
|
|
atomic_set(&mfd->commits_pending, 0);
|
|
|
|
atomic_set(&mfd->ioctl_ref_cnt, 0);
|
|
|
|
atomic_set(&mfd->kickoff_pending, 0);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
init_timer(&mfd->no_update.timer);
|
|
|
|
mfd->no_update.timer.function = mdss_fb_no_update_notify_timer_cb;
|
|
|
|
mfd->no_update.timer.data = (unsigned long)mfd;
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->update.ref_count = 0;
|
|
|
|
mfd->no_update.ref_count = 0;
|
|
|
|
mfd->update.init_done = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
init_completion(&mfd->update.comp);
|
|
|
|
init_completion(&mfd->no_update.comp);
|
2024-09-09 08:57:42 +00:00
|
|
|
init_completion(&mfd->power_off_comp);
|
2024-09-09 08:52:07 +00:00
|
|
|
init_completion(&mfd->power_set_comp);
|
2024-09-09 08:57:42 +00:00
|
|
|
init_waitqueue_head(&mfd->commit_wait_q);
|
|
|
|
init_waitqueue_head(&mfd->idle_wait_q);
|
|
|
|
init_waitqueue_head(&mfd->ioctl_q);
|
|
|
|
init_waitqueue_head(&mfd->kickoff_wait_q);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
|
|
|
|
if (ret)
|
|
|
|
pr_err("fb_alloc_cmap() failed!\n");
|
|
|
|
|
|
|
|
if (register_framebuffer(fbi) < 0) {
|
|
|
|
fb_dealloc_cmap(&fbi->cmap);
|
|
|
|
|
|
|
|
mfd->op_enable = false;
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
snprintf(panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d",
|
|
|
|
mfd->index);
|
|
|
|
mdss_panel_debugfs_init(panel_info, panel_name);
|
|
|
|
pr_info("FrameBuffer[%d] %dx%d registered successfully!\n", mfd->index,
|
|
|
|
fbi->var.xres, fbi->var.yres);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
return 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_open(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct mdss_fb_file_info *file_info = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
int result;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct task_struct *task = current->group_leader;
|
|
|
|
|
|
|
|
if (mfd->shutdown_pending) {
|
|
|
|
pr_err_once("Shutdown pending. Aborting operation. Request from pid:%d name=%s\n",
|
|
|
|
current->tgid, task->comm);
|
|
|
|
sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event");
|
|
|
|
return -ESHUTDOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
file_info = kmalloc(sizeof(*file_info), GFP_KERNEL);
|
|
|
|
if (!file_info) {
|
|
|
|
pr_err("unable to alloc file info\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
file_info->file = info->file;
|
|
|
|
list_add(&file_info->list, &mfd->file_list);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
result = pm_runtime_get_sync(info->dev);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (result < 0) {
|
2024-09-09 08:52:07 +00:00
|
|
|
pr_err("pm_runtime: fail to wake up\n");
|
2024-09-09 08:57:42 +00:00
|
|
|
goto pm_error;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!mfd->ref_cnt) {
|
|
|
|
result = mdss_fb_blank_sub(FB_BLANK_UNBLANK, info,
|
|
|
|
mfd->op_enable);
|
|
|
|
if (result) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("can't turn on fb%d! rc=%d\n", mfd->index,
|
|
|
|
result);
|
|
|
|
goto blank_error;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mfd->ref_cnt++;
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("mfd refcount:%d file:%p\n", mfd->ref_cnt, info->file);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
blank_error:
|
|
|
|
pm_runtime_put(info->dev);
|
|
|
|
pm_error:
|
|
|
|
list_del(&file_info->list);
|
|
|
|
kfree(file_info);
|
|
|
|
return result;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_release_all(struct fb_info *info, bool release_all)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct mdss_fb_file_info *file_info = NULL, *temp_file_info = NULL;
|
|
|
|
struct file *file = info->file;
|
2024-09-09 08:52:07 +00:00
|
|
|
int ret = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
bool node_found = false;
|
|
|
|
struct task_struct *task = current->group_leader;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!mfd->ref_cnt) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_info("try to close unopened fb %d! from pid:%d name:%s\n",
|
|
|
|
mfd->index, current->tgid, task->comm);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!wait_event_timeout(mfd->ioctl_q,
|
|
|
|
!atomic_read(&mfd->ioctl_ref_cnt) || !release_all,
|
|
|
|
msecs_to_jiffies(1000)))
|
|
|
|
pr_warn("fb%d ioctl could not finish. waited 1 sec.\n",
|
|
|
|
mfd->index);
|
|
|
|
|
|
|
|
/* wait only for the last release */
|
|
|
|
if (release_all || (mfd->ref_cnt == 1)) {
|
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret && (ret != -ESHUTDOWN))
|
|
|
|
pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d ignoring.\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("release_all = %s\n", release_all ? "true" : "false");
|
|
|
|
|
|
|
|
list_for_each_entry_safe(file_info, temp_file_info, &mfd->file_list,
|
|
|
|
list) {
|
|
|
|
if (!release_all && file_info->file != file)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
pr_debug("found file node mfd->ref=%d\n", mfd->ref_cnt);
|
|
|
|
list_del(&file_info->list);
|
|
|
|
kfree(file_info);
|
|
|
|
|
|
|
|
mfd->ref_cnt--;
|
|
|
|
pm_runtime_put(info->dev);
|
|
|
|
|
|
|
|
node_found = true;
|
|
|
|
|
|
|
|
if (!release_all)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!node_found || (release_all && mfd->ref_cnt))
|
|
|
|
pr_warn("file node not found or wrong ref cnt: release all:%d refcnt:%d\n",
|
|
|
|
release_all, mfd->ref_cnt);
|
|
|
|
|
|
|
|
pr_debug("current process=%s pid=%d mfd->ref=%d file:%p\n",
|
|
|
|
task->comm, current->tgid, mfd->ref_cnt, info->file);
|
|
|
|
|
|
|
|
if (!mfd->ref_cnt || release_all) {
|
|
|
|
/* resources (if any) will be released during blank */
|
|
|
|
if (mfd->mdp.release_fnc)
|
|
|
|
mfd->mdp.release_fnc(mfd, NULL);
|
|
|
|
|
|
|
|
if (mfd->mdp.pp_release_fnc) {
|
|
|
|
ret = (*mfd->mdp.pp_release_fnc)(mfd);
|
|
|
|
if (ret)
|
|
|
|
pr_err("PP release failed ret %d\n", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* reset backlight before blank to prevent backlight from
|
|
|
|
* enabling ahead of unblank. for some special cases like
|
|
|
|
* adb shell stop/start.
|
|
|
|
*/
|
|
|
|
mdss_fb_set_backlight(mfd, 0);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info,
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->op_enable);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("can't turn off fb%d! rc=%d current process=%s pid=%d\n",
|
|
|
|
mfd->index, ret, task->comm, current->tgid);
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->fb_ion_handle)
|
|
|
|
mdss_fb_free_fb_ion_memory(mfd);
|
|
|
|
|
|
|
|
atomic_set(&mfd->ioctl_ref_cnt, 0);
|
|
|
|
} else {
|
|
|
|
if (mfd->mdp.release_fnc)
|
|
|
|
ret = mfd->mdp.release_fnc(mfd, file);
|
|
|
|
|
|
|
|
/* display commit is needed to release resources */
|
|
|
|
if (ret)
|
|
|
|
mdss_fb_pan_display(&mfd->fbi->var, mfd->fbi);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_release(struct fb_info *info, int user)
|
|
|
|
{
|
|
|
|
return mdss_fb_release_all(info, false);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static void mdss_fb_power_setting_idle(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (mfd->is_power_setting) {
|
|
|
|
ret = wait_for_completion_timeout(
|
|
|
|
&mfd->power_set_comp,
|
|
|
|
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT));
|
|
|
|
if (ret < 0)
|
|
|
|
ret = -ERESTARTSYS;
|
|
|
|
else if (!ret)
|
|
|
|
pr_err("%s wait for power_set_comp timeout %d %d",
|
|
|
|
__func__, ret, mfd->is_power_setting);
|
|
|
|
if (ret <= 0) {
|
|
|
|
mfd->is_power_setting = false;
|
|
|
|
complete_all(&mfd->power_set_comp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void __mdss_fb_copy_fence(struct msm_sync_pt_data *sync_pt_data,
|
|
|
|
struct sync_fence **fences, u32 *fence_cnt)
|
|
|
|
{
|
|
|
|
pr_debug("%s: wait for fences\n", sync_pt_data->fence_name);
|
|
|
|
|
|
|
|
mutex_lock(&sync_pt_data->sync_mutex);
|
|
|
|
/*
|
|
|
|
* Assuming that acq_fen_cnt is sanitized in bufsync ioctl
|
|
|
|
* to check for sync_pt_data->acq_fen_cnt <= MDP_MAX_FENCE_FD
|
|
|
|
*/
|
|
|
|
*fence_cnt = sync_pt_data->acq_fen_cnt;
|
|
|
|
sync_pt_data->acq_fen_cnt = 0;
|
|
|
|
if (*fence_cnt)
|
|
|
|
memcpy(fences, sync_pt_data->acq_fen,
|
|
|
|
*fence_cnt * sizeof(struct sync_fence *));
|
|
|
|
mutex_unlock(&sync_pt_data->sync_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __mdss_fb_wait_for_fence_sub(struct msm_sync_pt_data *sync_pt_data,
|
|
|
|
struct sync_fence **fences, int fence_cnt)
|
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
unsigned long max_wait = msecs_to_jiffies(WAIT_MAX_FENCE_TIMEOUT);
|
|
|
|
unsigned long timeout = jiffies + max_wait;
|
|
|
|
long wait_ms, wait_jf;
|
|
|
|
|
|
|
|
/* buf sync */
|
|
|
|
for (i = 0; i < fence_cnt && !ret; i++) {
|
|
|
|
wait_jf = timeout - jiffies;
|
|
|
|
wait_ms = jiffies_to_msecs(wait_jf);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In this loop, if one of the previous fence took long
|
|
|
|
* time, give a chance for the next fence to check if
|
|
|
|
* fence is already signalled. If not signalled it breaks
|
|
|
|
* in the final wait timeout.
|
|
|
|
*/
|
|
|
|
if (wait_jf < 0)
|
|
|
|
wait_ms = WAIT_MIN_FENCE_TIMEOUT;
|
|
|
|
else
|
|
|
|
wait_ms = min_t(long, WAIT_FENCE_FIRST_TIMEOUT,
|
|
|
|
wait_ms);
|
|
|
|
|
|
|
|
ret = sync_fence_wait(fences[i], wait_ms);
|
|
|
|
|
|
|
|
if (ret == -ETIME) {
|
|
|
|
wait_jf = timeout - jiffies;
|
|
|
|
wait_ms = jiffies_to_msecs(wait_jf);
|
|
|
|
if (wait_jf < 0)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
wait_ms = min_t(long, WAIT_FENCE_FINAL_TIMEOUT,
|
|
|
|
wait_ms);
|
|
|
|
|
|
|
|
pr_warn("%s: sync_fence_wait timed out! ",
|
|
|
|
sync_pt_data->fence_name);
|
|
|
|
pr_cont("Waiting %ld.%ld more seconds\n",
|
|
|
|
(wait_ms/MSEC_PER_SEC), (wait_ms%MSEC_PER_SEC));
|
|
|
|
|
|
|
|
ret = sync_fence_wait(fences[i], wait_ms);
|
|
|
|
|
|
|
|
if (ret == -ETIME)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
sync_fence_put(fences[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("%s: sync_fence_wait failed! ret = %x\n",
|
|
|
|
sync_pt_data->fence_name, ret);
|
|
|
|
for (; i < fence_cnt; i++)
|
|
|
|
sync_fence_put(fences[i]);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_fb_wait_for_fence(struct msm_sync_pt_data *sync_pt_data)
|
|
|
|
{
|
|
|
|
struct sync_fence *fences[MDP_MAX_FENCE_FD];
|
|
|
|
int fence_cnt = 0;
|
|
|
|
|
|
|
|
__mdss_fb_copy_fence(sync_pt_data, fences, &fence_cnt);
|
|
|
|
|
|
|
|
if (fence_cnt)
|
|
|
|
__mdss_fb_wait_for_fence_sub(sync_pt_data,
|
|
|
|
fences, fence_cnt);
|
|
|
|
|
|
|
|
return fence_cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mdss_fb_signal_timeline() - signal a single release fence
|
|
|
|
* @sync_pt_data: Sync point data structure for the timeline which
|
|
|
|
* should be signaled.
|
|
|
|
*
|
|
|
|
* This is called after a frame has been pushed to display. This signals the
|
|
|
|
* timeline to release the fences associated with this frame.
|
|
|
|
*/
|
|
|
|
void mdss_fb_signal_timeline(struct msm_sync_pt_data *sync_pt_data)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&sync_pt_data->sync_mutex);
|
|
|
|
if (atomic_add_unless(&sync_pt_data->commit_cnt, -1, 0) &&
|
|
|
|
sync_pt_data->timeline) {
|
|
|
|
sw_sync_timeline_inc(sync_pt_data->timeline, 1);
|
|
|
|
sync_pt_data->timeline_value++;
|
|
|
|
|
|
|
|
pr_debug("%s: buffer signaled! timeline val=%d remaining=%d\n",
|
|
|
|
sync_pt_data->fence_name, sync_pt_data->timeline_value,
|
|
|
|
atomic_read(&sync_pt_data->commit_cnt));
|
|
|
|
} else {
|
|
|
|
pr_debug("%s timeline signaled without commits val=%d\n",
|
|
|
|
sync_pt_data->fence_name, sync_pt_data->timeline_value);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&sync_pt_data->sync_mutex);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/**
|
|
|
|
* mdss_fb_release_fences() - signal all pending release fences
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
*
|
|
|
|
* Release all currently pending release fences, including those that are in
|
|
|
|
* the process to be commited.
|
|
|
|
*
|
|
|
|
* Note: this should only be called during close or suspend sequence.
|
|
|
|
*/
|
|
|
|
static void mdss_fb_release_fences(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
struct msm_sync_pt_data *sync_pt_data = &mfd->mdp_sync_pt_data;
|
|
|
|
int val;
|
|
|
|
|
|
|
|
mutex_lock(&sync_pt_data->sync_mutex);
|
|
|
|
if (sync_pt_data->timeline) {
|
|
|
|
val = sync_pt_data->threshold +
|
|
|
|
atomic_read(&sync_pt_data->commit_cnt);
|
|
|
|
sw_sync_timeline_inc(sync_pt_data->timeline, val);
|
|
|
|
sync_pt_data->timeline_value += val;
|
|
|
|
atomic_set(&sync_pt_data->commit_cnt, 0);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&sync_pt_data->sync_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void mdss_fb_release_kickoff(struct msm_fb_data_type *mfd)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->wait_for_kickoff) {
|
|
|
|
atomic_set(&mfd->kickoff_pending, 0);
|
|
|
|
wake_up_all(&mfd->kickoff_wait_q);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/**
|
|
|
|
* __mdss_fb_sync_buf_done_callback() - process async display events
|
|
|
|
* @p: Notifier block registered for async events.
|
|
|
|
* @event: Event enum to identify the event.
|
|
|
|
* @data: Optional argument provided with the event.
|
|
|
|
*
|
|
|
|
* See enum mdp_notify_event for events handled.
|
|
|
|
*/
|
|
|
|
static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p,
|
|
|
|
unsigned long event, void *data)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_sync_pt_data *sync_pt_data;
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
int fence_cnt;
|
|
|
|
int ret = NOTIFY_OK;
|
|
|
|
|
|
|
|
sync_pt_data = container_of(p, struct msm_sync_pt_data, notifier);
|
|
|
|
mfd = container_of(sync_pt_data, struct msm_fb_data_type,
|
|
|
|
mdp_sync_pt_data);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case MDP_NOTIFY_FRAME_BEGIN:
|
|
|
|
if (mfd->idle_time && !mod_delayed_work(system_wq,
|
|
|
|
&mfd->idle_notify_work,
|
|
|
|
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT)))
|
|
|
|
pr_debug("fb%d: start idle delayed work\n",
|
|
|
|
mfd->index);
|
|
|
|
|
|
|
|
mfd->idle_state = MDSS_FB_NOT_IDLE;
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_READY:
|
|
|
|
if (sync_pt_data->async_wait_fences &&
|
|
|
|
sync_pt_data->temp_fen_cnt) {
|
|
|
|
fence_cnt = sync_pt_data->temp_fen_cnt;
|
|
|
|
sync_pt_data->temp_fen_cnt = 0;
|
|
|
|
ret = __mdss_fb_wait_for_fence_sub(sync_pt_data,
|
|
|
|
sync_pt_data->temp_fen, fence_cnt);
|
|
|
|
}
|
|
|
|
if (mfd->idle_time && !mod_delayed_work(system_wq,
|
|
|
|
&mfd->idle_notify_work,
|
|
|
|
msecs_to_jiffies(mfd->idle_time)))
|
|
|
|
pr_debug("fb%d: restarted idle work\n",
|
|
|
|
mfd->index);
|
|
|
|
if (ret == -ETIME)
|
|
|
|
ret = NOTIFY_BAD;
|
|
|
|
mfd->idle_state = MDSS_FB_IDLE_TIMER_RUNNING;
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_FLUSHED:
|
|
|
|
pr_debug("%s: frame flushed\n", sync_pt_data->fence_name);
|
|
|
|
sync_pt_data->flushed = true;
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_TIMEOUT:
|
|
|
|
pr_err("%s: frame timeout\n", sync_pt_data->fence_name);
|
|
|
|
mdss_fb_signal_timeline(sync_pt_data);
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_DONE:
|
|
|
|
pr_debug("%s: frame done\n", sync_pt_data->fence_name);
|
|
|
|
mdss_fb_signal_timeline(sync_pt_data);
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_CFG_DONE:
|
|
|
|
if (sync_pt_data->async_wait_fences)
|
|
|
|
__mdss_fb_copy_fence(sync_pt_data,
|
|
|
|
sync_pt_data->temp_fen,
|
|
|
|
&sync_pt_data->temp_fen_cnt);
|
|
|
|
break;
|
|
|
|
case MDP_NOTIFY_FRAME_CTX_DONE:
|
|
|
|
mdss_fb_release_kickoff(mfd);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/**
|
|
|
|
* mdss_fb_pan_idle() - wait for panel programming to be idle
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
*
|
|
|
|
* Wait for any pending programming to be done if in the process of programming
|
|
|
|
* hardware configuration. After this function returns it is safe to perform
|
|
|
|
* software updates for next frame.
|
|
|
|
*/
|
|
|
|
static int mdss_fb_pan_idle(struct msm_fb_data_type *mfd)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ret = wait_event_timeout(mfd->idle_wait_q,
|
|
|
|
(!atomic_read(&mfd->commits_pending) ||
|
|
|
|
mfd->shutdown_pending),
|
|
|
|
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT));
|
|
|
|
if (!ret) {
|
|
|
|
pr_err("%pS: wait for idle timeout commits=%d\n",
|
|
|
|
__builtin_return_address(0),
|
|
|
|
atomic_read(&mfd->commits_pending));
|
|
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt",
|
|
|
|
"dbg_bus", "vbif_dbg_bus");
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
} else if (mfd->shutdown_pending) {
|
|
|
|
pr_debug("Shutdown signalled\n");
|
|
|
|
ret = -ESHUTDOWN;
|
|
|
|
} else {
|
|
|
|
ret = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_wait_for_kickoff(struct msm_fb_data_type *mfd)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd->wait_for_kickoff)
|
|
|
|
return mdss_fb_pan_idle(mfd);
|
|
|
|
|
|
|
|
ret = wait_event_timeout(mfd->kickoff_wait_q,
|
|
|
|
(!atomic_read(&mfd->kickoff_pending) ||
|
|
|
|
mfd->shutdown_pending),
|
2024-09-09 08:52:07 +00:00
|
|
|
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT));
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!ret) {
|
|
|
|
pr_err("%pS: wait for kickoff timeout koff=%d commits=%d\n",
|
|
|
|
__builtin_return_address(0),
|
|
|
|
atomic_read(&mfd->kickoff_pending),
|
|
|
|
atomic_read(&mfd->commits_pending));
|
|
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt",
|
|
|
|
"dbg_bus", "vbif_dbg_bus");
|
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
} else if (mfd->shutdown_pending) {
|
|
|
|
pr_debug("Shutdown signalled\n");
|
|
|
|
ret = -ESHUTDOWN;
|
|
|
|
} else {
|
|
|
|
ret = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_pan_display_ex(struct fb_info *info,
|
|
|
|
struct mdp_display_commit *disp_commit)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct fb_var_screeninfo *var = &disp_commit->var;
|
|
|
|
u32 wait_for_finish = disp_commit->wait_for_finish;
|
|
|
|
int ret = 0;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd || (!mfd->op_enable))
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if ((mdss_fb_is_power_off(mfd)) &&
|
|
|
|
!((mfd->dcm_state == DCM_ENTER) &&
|
|
|
|
(mfd->panel.type == MIPI_CMD_PANEL)))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (var->xoffset > (info->var.xres_virtual - info->var.xres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (var->yoffset > (info->var.yres_virtual - info->var.yres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_wait_for_kickoff(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("wait_for_kick failed. rc=%d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mfd->mdp.pre_commit_fnc) {
|
|
|
|
ret = mfd->mdp.pre_commit_fnc(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("fb%d: pre commit failed %d\n",
|
|
|
|
mfd->index, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&mfd->mdp_sync_pt_data.sync_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (info->fix.xpanstep)
|
|
|
|
info->var.xoffset =
|
|
|
|
(var->xoffset / info->fix.xpanstep) * info->fix.xpanstep;
|
|
|
|
|
|
|
|
if (info->fix.ypanstep)
|
|
|
|
info->var.yoffset =
|
|
|
|
(var->yoffset / info->fix.ypanstep) * info->fix.ypanstep;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->msm_fb_backup.info = *info;
|
|
|
|
mfd->msm_fb_backup.disp_commit = *disp_commit;
|
|
|
|
|
|
|
|
atomic_inc(&mfd->mdp_sync_pt_data.commit_cnt);
|
|
|
|
atomic_inc(&mfd->commits_pending);
|
|
|
|
atomic_inc(&mfd->kickoff_pending);
|
|
|
|
wake_up_all(&mfd->commit_wait_q);
|
|
|
|
mutex_unlock(&mfd->mdp_sync_pt_data.sync_mutex);
|
|
|
|
if (wait_for_finish) {
|
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret)
|
|
|
|
pr_err("mdss_fb_pan_idle failed. rc=%d\n", ret);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 mdss_fb_get_mode_switch(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
/* If there is no attached mfd then there is no pending mode switch */
|
|
|
|
if (!mfd)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (mfd->pending_switch)
|
|
|
|
return mfd->switch_new_mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __ioctl_transition_dyn_mode_state() - State machine for mode switch
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
* @cmd: ioctl that was called
|
|
|
|
* @validate: used with atomic commit when doing validate layers
|
|
|
|
*
|
|
|
|
* This function assists with dynamic mode switch of DSI panel. States
|
|
|
|
* are used to make sure that panel mode switch occurs on next
|
|
|
|
* prepare/sync/commit (for legacy) and validate/pre_commit (for
|
|
|
|
* atomic commit) pairing. This state machine insure that calculation
|
|
|
|
* and return values (such as buffer release fences) are based on the
|
|
|
|
* panel mode being switching into.
|
|
|
|
*/
|
|
|
|
static int __ioctl_transition_dyn_mode_state(struct msm_fb_data_type *mfd,
|
|
|
|
unsigned int cmd, int validate)
|
|
|
|
{
|
|
|
|
if (mfd->switch_state == MDSS_MDP_NO_UPDATE_REQUESTED)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
switch (cmd) {
|
|
|
|
case MSMFB_BUFFER_SYNC:
|
|
|
|
if (mfd->switch_state == MDSS_MDP_WAIT_FOR_SYNC) {
|
|
|
|
if (mfd->switch_new_mode != SWITCH_RESOLUTION)
|
|
|
|
mdss_fb_set_mdp_sync_pt_threshold(mfd,
|
|
|
|
mfd->switch_new_mode);
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_COMMIT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MSMFB_OVERLAY_PREPARE:
|
|
|
|
if (mfd->switch_state == MDSS_MDP_WAIT_FOR_PREP) {
|
|
|
|
if (mfd->switch_new_mode != SWITCH_RESOLUTION)
|
|
|
|
mfd->pending_switch = true;
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_SYNC;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MSMFB_ATOMIC_COMMIT:
|
|
|
|
if ((mfd->switch_state == MDSS_MDP_WAIT_FOR_PREP) && validate) {
|
|
|
|
if (mfd->switch_new_mode != SWITCH_RESOLUTION)
|
|
|
|
mfd->pending_switch = true;
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_SYNC;
|
|
|
|
} else if (mfd->switch_state == MDSS_MDP_WAIT_FOR_SYNC) {
|
|
|
|
if (mfd->switch_new_mode != SWITCH_RESOLUTION)
|
|
|
|
mdss_fb_set_mdp_sync_pt_threshold(mfd,
|
|
|
|
mfd->switch_new_mode);
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_COMMIT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_fb_atomic_commit(struct fb_info *info,
|
|
|
|
struct mdp_layer_commit *commit, struct file *file)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct mdp_layer_commit_v1 *commit_v1;
|
|
|
|
bool wait_for_finish;
|
|
|
|
int ret = -EPERM;
|
|
|
|
|
|
|
|
if (!mfd || (!mfd->op_enable)) {
|
|
|
|
pr_err("mfd is NULL or operation not permitted\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((mdss_fb_is_power_off(mfd)) &&
|
|
|
|
!((mfd->dcm_state == DCM_ENTER) &&
|
|
|
|
(mfd->panel.type == MIPI_CMD_PANEL))) {
|
|
|
|
pr_err("commit is not supported when interface is in off state\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* only supports version 1.0 */
|
|
|
|
if (commit->version != MDP_COMMIT_VERSION_1_0) {
|
|
|
|
pr_err("commit version is not supported\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mfd->mdp.pre_commit || !mfd->mdp.atomic_validate) {
|
|
|
|
pr_err("commit callback is not registered\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
commit_v1 = &commit->commit_v1;
|
|
|
|
if (commit_v1->flags & MDP_VALIDATE_LAYER) {
|
|
|
|
ret = mdss_fb_wait_for_kickoff(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("wait for kickoff failed\n");
|
|
|
|
} else {
|
|
|
|
__ioctl_transition_dyn_mode_state(mfd,
|
|
|
|
MSMFB_ATOMIC_COMMIT, 1);
|
|
|
|
ret = mfd->mdp.atomic_validate(mfd, file, commit_v1);
|
|
|
|
if (!ret)
|
|
|
|
mfd->validate_pending = true;
|
|
|
|
}
|
|
|
|
goto end;
|
|
|
|
} else {
|
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("pan display idle call failed\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = mfd->mdp.pre_commit(mfd, file, commit_v1);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("atomic pre commit failed\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wait_for_finish = commit_v1->flags & MDP_COMMIT_WAIT_FOR_FINISH;
|
|
|
|
mfd->msm_fb_backup.atomic_commit = true;
|
|
|
|
mfd->msm_fb_backup.disp_commit.l_roi = commit_v1->left_roi;
|
|
|
|
mfd->msm_fb_backup.disp_commit.r_roi = commit_v1->right_roi;
|
|
|
|
|
|
|
|
mutex_lock(&mfd->mdp_sync_pt_data.sync_mutex);
|
|
|
|
atomic_inc(&mfd->mdp_sync_pt_data.commit_cnt);
|
|
|
|
atomic_inc(&mfd->commits_pending);
|
|
|
|
atomic_inc(&mfd->kickoff_pending);
|
|
|
|
wake_up_all(&mfd->commit_wait_q);
|
|
|
|
mutex_unlock(&mfd->mdp_sync_pt_data.sync_mutex);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (wait_for_finish)
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
|
|
|
|
end:
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct mdp_display_commit disp_commit;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* during mode switch through mode sysfs node, it will trigger a
|
|
|
|
* pan_display after switch. This assumes that fb has been adjusted,
|
|
|
|
* however when using overlays we may not have the right size at this
|
|
|
|
* point, so it needs to go through PREPARE first. Abort pan_display
|
|
|
|
* operations until that happens
|
|
|
|
*/
|
|
|
|
if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) {
|
|
|
|
pr_debug("fb%d: pan_display skipped during switch\n",
|
|
|
|
mfd->index);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
memset(&disp_commit, 0, sizeof(disp_commit));
|
|
|
|
disp_commit.wait_for_finish = true;
|
|
|
|
memcpy(&disp_commit.var, var, sizeof(struct fb_var_screeninfo));
|
|
|
|
return mdss_fb_pan_display_ex(info, &disp_commit);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_pan_display_sub(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd->op_enable)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if ((mdss_fb_is_power_off(mfd)) &&
|
|
|
|
!((mfd->dcm_state == DCM_ENTER) &&
|
|
|
|
(mfd->panel.type == MIPI_CMD_PANEL)))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (var->xoffset > (info->var.xres_virtual - info->var.xres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (var->yoffset > (info->var.yres_virtual - info->var.yres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (info->fix.xpanstep)
|
|
|
|
info->var.xoffset =
|
|
|
|
(var->xoffset / info->fix.xpanstep) * info->fix.xpanstep;
|
|
|
|
|
|
|
|
if (info->fix.ypanstep)
|
|
|
|
info->var.yoffset =
|
|
|
|
(var->yoffset / info->fix.ypanstep) * info->fix.ypanstep;
|
|
|
|
|
|
|
|
if (mfd->mdp.dma_fnc)
|
|
|
|
mfd->mdp.dma_fnc(mfd);
|
|
|
|
else
|
|
|
|
pr_warn("dma function not set for panel type=%d\n",
|
|
|
|
mfd->panel.type);
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_grayscale_to_mdp_format(u32 grayscale)
|
|
|
|
{
|
|
|
|
switch (grayscale) {
|
|
|
|
case V4L2_PIX_FMT_RGB24:
|
|
|
|
return MDP_RGB_888;
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
return MDP_Y_CBCR_H2V2;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static void mdss_fb_var_to_panelinfo(struct fb_var_screeninfo *var,
|
|
|
|
struct mdss_panel_info *pinfo)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int format = -EINVAL;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
pinfo->xres = var->xres;
|
|
|
|
pinfo->yres = var->yres;
|
|
|
|
pinfo->lcdc.v_front_porch = var->lower_margin;
|
|
|
|
pinfo->lcdc.v_back_porch = var->upper_margin;
|
|
|
|
pinfo->lcdc.v_pulse_width = var->vsync_len;
|
|
|
|
pinfo->lcdc.h_front_porch = var->right_margin;
|
|
|
|
pinfo->lcdc.h_back_porch = var->left_margin;
|
|
|
|
pinfo->lcdc.h_pulse_width = var->hsync_len;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
if (var->grayscale > 1) {
|
|
|
|
format = mdss_grayscale_to_mdp_format(var->grayscale);
|
|
|
|
if (!IS_ERR_VALUE(format))
|
|
|
|
pinfo->out_format = format;
|
|
|
|
else
|
|
|
|
pr_warn("Failed to map grayscale value (%d) to an MDP format\n",
|
|
|
|
var->grayscale);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if greater than 1M, then rate would fall below 1mhz which is not
|
|
|
|
* even supported. In this case it means clock rate is actually
|
|
|
|
* passed directly in hz.
|
|
|
|
*/
|
|
|
|
if (var->pixclock > SZ_1M)
|
|
|
|
pinfo->clk_rate = var->pixclock;
|
|
|
|
else
|
|
|
|
pinfo->clk_rate = PICOS2KHZ(var->pixclock) * 1000;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
void mdss_panelinfo_to_fb_var(struct mdss_panel_info *pinfo,
|
|
|
|
struct fb_var_screeninfo *var)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
var->xres = mdss_fb_get_panel_xres(pinfo);
|
|
|
|
var->yres = pinfo->yres;
|
|
|
|
var->lower_margin = pinfo->lcdc.v_front_porch -
|
|
|
|
pinfo->prg_fet;
|
|
|
|
var->upper_margin = pinfo->lcdc.v_back_porch +
|
|
|
|
pinfo->prg_fet;
|
|
|
|
var->vsync_len = pinfo->lcdc.v_pulse_width;
|
|
|
|
var->right_margin = pinfo->lcdc.h_front_porch;
|
|
|
|
var->left_margin = pinfo->lcdc.h_back_porch;
|
|
|
|
var->hsync_len = pinfo->lcdc.h_pulse_width;
|
|
|
|
|
|
|
|
if (pinfo->clk_rate)
|
|
|
|
var->pixclock = KHZ2PICOS((unsigned long int)
|
|
|
|
pinfo->clk_rate/1000);
|
|
|
|
if (pinfo->physical_width)
|
|
|
|
var->width = pinfo->physical_width;
|
|
|
|
if (pinfo->physical_height)
|
|
|
|
var->height = pinfo->physical_height;
|
|
|
|
|
|
|
|
pr_debug("ScreenInfo: res=%dx%d [%d, %d] [%d, %d]\n",
|
|
|
|
var->xres, var->yres, var->left_margin,
|
|
|
|
var->right_margin, var->upper_margin,
|
|
|
|
var->lower_margin);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* __mdss_fb_perform_commit() - process a frame to display
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
*
|
|
|
|
* Processes all layers and buffers programmed and ensures all pending release
|
|
|
|
* fences are signaled once the buffer is transfered to display.
|
|
|
|
*/
|
|
|
|
static int __mdss_fb_perform_commit(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
struct msm_sync_pt_data *sync_pt_data = &mfd->mdp_sync_pt_data;
|
|
|
|
struct msm_fb_backup_type *fb_backup = &mfd->msm_fb_backup;
|
|
|
|
int ret = -ENOSYS;
|
|
|
|
u32 new_dsi_mode, dynamic_dsi_switch = 0;
|
|
|
|
|
|
|
|
if (!sync_pt_data->async_wait_fences)
|
|
|
|
mdss_fb_wait_for_fence(sync_pt_data);
|
|
|
|
sync_pt_data->flushed = false;
|
|
|
|
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
if (mfd->switch_state == MDSS_MDP_WAIT_FOR_COMMIT) {
|
|
|
|
dynamic_dsi_switch = 1;
|
|
|
|
new_dsi_mode = mfd->switch_new_mode;
|
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (dynamic_dsi_switch) {
|
|
|
|
pr_debug("Triggering dyn mode switch to %d\n", new_dsi_mode);
|
|
|
|
ret = mfd->mdp.mode_switch(mfd, new_dsi_mode);
|
|
|
|
if (ret)
|
|
|
|
pr_err("DSI mode switch has failed");
|
|
|
|
else
|
|
|
|
mfd->pending_switch = false;
|
|
|
|
}
|
|
|
|
if (fb_backup->disp_commit.flags & MDP_DISPLAY_COMMIT_OVERLAY) {
|
2024-09-09 08:52:07 +00:00
|
|
|
if (mfd->mdp.kickoff_fnc)
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mfd->mdp.kickoff_fnc(mfd,
|
|
|
|
&fb_backup->disp_commit);
|
|
|
|
else
|
|
|
|
pr_warn("no kickoff function setup for fb%d\n",
|
|
|
|
mfd->index);
|
|
|
|
} else if (fb_backup->atomic_commit) {
|
|
|
|
if (mfd->mdp.kickoff_fnc)
|
|
|
|
ret = mfd->mdp.kickoff_fnc(mfd,
|
|
|
|
&fb_backup->disp_commit);
|
|
|
|
else
|
|
|
|
pr_warn("no kickoff function setup for fb%d\n",
|
|
|
|
mfd->index);
|
|
|
|
fb_backup->atomic_commit = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_pan_display_sub(&fb_backup->disp_commit.var,
|
|
|
|
&fb_backup->info);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret)
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("pan display failed %x on fb%d\n", ret,
|
|
|
|
mfd->index);
|
|
|
|
}
|
|
|
|
if (!ret)
|
|
|
|
mdss_fb_update_backlight(mfd);
|
|
|
|
|
|
|
|
if (IS_ERR_VALUE(ret) || !sync_pt_data->flushed) {
|
|
|
|
mdss_fb_release_kickoff(mfd);
|
|
|
|
mdss_fb_signal_timeline(sync_pt_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dynamic_dsi_switch) {
|
|
|
|
mfd->mdp.mode_switch_post(mfd, new_dsi_mode);
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
mfd->switch_state = MDSS_MDP_NO_UPDATE_REQUESTED;
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
mfd->panel.type = new_dsi_mode;
|
|
|
|
pr_debug("Dynamic mode switch completed\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __mdss_fb_display_thread(void *data)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = data;
|
|
|
|
int ret;
|
|
|
|
struct sched_param param;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this priority was found during empiric testing to have appropriate
|
|
|
|
* realtime scheduling to process display updates and interact with
|
|
|
|
* other real time and normal priority tasks
|
|
|
|
*/
|
|
|
|
param.sched_priority = 16;
|
|
|
|
ret = sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
|
|
if (ret)
|
|
|
|
pr_warn("set priority failed for fb%d display thread\n",
|
|
|
|
mfd->index);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
wait_event(mfd->commit_wait_q,
|
|
|
|
(atomic_read(&mfd->commits_pending) ||
|
|
|
|
kthread_should_stop()));
|
|
|
|
|
|
|
|
if (kthread_should_stop())
|
|
|
|
break;
|
|
|
|
|
|
|
|
MDSS_XLOG(mfd->index, XLOG_FUNC_ENTRY);
|
|
|
|
ret = __mdss_fb_perform_commit(mfd);
|
|
|
|
MDSS_XLOG(mfd->index, XLOG_FUNC_EXIT);
|
|
|
|
|
|
|
|
atomic_dec(&mfd->commits_pending);
|
|
|
|
wake_up_all(&mfd->idle_wait_q);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
mdss_fb_release_kickoff(mfd);
|
|
|
|
atomic_set(&mfd->commits_pending, 0);
|
|
|
|
wake_up_all(&mfd->idle_wait_q);
|
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_check_var(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (var->rotate != FB_ROTATE_UR && var->rotate != FB_ROTATE_UD)
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
switch (var->bits_per_pixel) {
|
|
|
|
case 16:
|
|
|
|
if ((var->green.offset != 5) ||
|
|
|
|
!((var->blue.offset == 11)
|
|
|
|
|| (var->blue.offset == 0)) ||
|
|
|
|
!((var->red.offset == 11)
|
|
|
|
|| (var->red.offset == 0)) ||
|
|
|
|
(var->blue.length != 5) ||
|
|
|
|
(var->green.length != 6) ||
|
|
|
|
(var->red.length != 5) ||
|
|
|
|
(var->blue.msb_right != 0) ||
|
|
|
|
(var->green.msb_right != 0) ||
|
|
|
|
(var->red.msb_right != 0) ||
|
|
|
|
(var->transp.offset != 0) ||
|
|
|
|
(var->transp.length != 0))
|
|
|
|
return -EINVAL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 24:
|
|
|
|
if ((var->blue.offset != 0) ||
|
|
|
|
(var->green.offset != 8) ||
|
|
|
|
(var->red.offset != 16) ||
|
|
|
|
(var->blue.length != 8) ||
|
|
|
|
(var->green.length != 8) ||
|
|
|
|
(var->red.length != 8) ||
|
|
|
|
(var->blue.msb_right != 0) ||
|
|
|
|
(var->green.msb_right != 0) ||
|
|
|
|
(var->red.msb_right != 0) ||
|
|
|
|
!(((var->transp.offset == 0) &&
|
|
|
|
(var->transp.length == 0)) ||
|
|
|
|
((var->transp.offset == 24) &&
|
|
|
|
(var->transp.length == 8))))
|
|
|
|
return -EINVAL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 32:
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Check user specified color format BGRA/ARGB/RGBA
|
2024-09-09 08:52:07 +00:00
|
|
|
and verify the position of the RGB components */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!((var->transp.offset == 24) &&
|
|
|
|
(var->blue.offset == 0) &&
|
|
|
|
(var->green.offset == 8) &&
|
|
|
|
(var->red.offset == 16)) &&
|
|
|
|
!((var->transp.offset == 24) &&
|
|
|
|
(var->blue.offset == 16) &&
|
|
|
|
(var->green.offset == 8) &&
|
|
|
|
(var->red.offset == 0)))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Check the common values for both RGBA and ARGB */
|
|
|
|
|
|
|
|
if ((var->blue.length != 8) ||
|
|
|
|
(var->green.length != 8) ||
|
|
|
|
(var->red.length != 8) ||
|
|
|
|
(var->transp.length != 8) ||
|
|
|
|
(var->blue.msb_right != 0) ||
|
|
|
|
(var->green.msb_right != 0) ||
|
|
|
|
(var->red.msb_right != 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((var->xres_virtual <= 0) || (var->yres_virtual <= 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if ((var->xres == 0) || (var->yres == 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (var->xoffset > (var->xres_virtual - var->xres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (var->yoffset > (var->yres_virtual - var->yres))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (info->mode) {
|
|
|
|
const struct fb_videomode *mode;
|
|
|
|
|
|
|
|
mode = fb_match_mode(var, &info->modelist);
|
|
|
|
if (mode == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
} else if (mfd->panel_info && !(var->activate & FB_ACTIVATE_TEST)) {
|
|
|
|
struct mdss_panel_info *panel_info;
|
2024-09-09 08:52:07 +00:00
|
|
|
int rc;
|
2024-09-09 08:57:42 +00:00
|
|
|
panel_info = kzalloc(sizeof(struct mdss_panel_info),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!panel_info)
|
|
|
|
return -ENOMEM;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
memcpy(panel_info, mfd->panel_info,
|
|
|
|
sizeof(struct mdss_panel_info));
|
|
|
|
mdss_fb_var_to_panelinfo(var, panel_info);
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = mdss_fb_send_panel_event(mfd, MDSS_EVENT_CHECK_PARAMS,
|
2024-09-09 08:57:42 +00:00
|
|
|
panel_info);
|
|
|
|
if (IS_ERR_VALUE(rc)) {
|
|
|
|
kfree(panel_info);
|
2024-09-09 08:52:07 +00:00
|
|
|
return rc;
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->panel_reconfig = rc;
|
2024-09-09 08:57:42 +00:00
|
|
|
kfree(panel_info);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_videomode_switch(struct msm_fb_data_type *mfd,
|
|
|
|
const struct fb_videomode *mode)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
struct mdss_panel_data *pdata, *tmp;
|
|
|
|
struct mdss_panel_timing *timing;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("no panel connected\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make sure that we are idle while switching */
|
|
|
|
mdss_fb_wait_for_kickoff(mfd);
|
|
|
|
|
|
|
|
pr_debug("fb%d: changing display mode to %s\n", mfd->index, mode->name);
|
|
|
|
|
|
|
|
tmp = pdata;
|
|
|
|
do {
|
|
|
|
if (!tmp->event_handler) {
|
|
|
|
pr_warn("no event handler for panel\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
timing = mdss_panel_get_timing_by_name(tmp, mode->name);
|
|
|
|
ret = tmp->event_handler(tmp,
|
|
|
|
MDSS_EVENT_PANEL_TIMING_SWITCH, timing);
|
|
|
|
|
|
|
|
tmp->active = timing != NULL;
|
|
|
|
tmp = tmp->next;
|
|
|
|
} while (tmp && !ret);
|
|
|
|
|
|
|
|
if (!ret && mfd->mdp.configure_panel) {
|
|
|
|
int dest_ctrl = 1;
|
|
|
|
|
|
|
|
/* todo: currently assumes no changes in video/cmd mode */
|
|
|
|
if (!mdss_fb_is_power_off(mfd)) {
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_PREP;
|
|
|
|
mfd->switch_new_mode = SWITCH_RESOLUTION;
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
dest_ctrl = 0;
|
|
|
|
}
|
|
|
|
ret = mfd->mdp.configure_panel(mfd,
|
|
|
|
pdata->panel_info.mipi.mode, dest_ctrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
if (pdata->next && pdata->next->active)
|
|
|
|
mfd->split_mode = MDP_DUAL_LM_DUAL_DISPLAY;
|
|
|
|
else
|
|
|
|
mfd->split_mode = MDP_SPLIT_MODE_NONE;
|
|
|
|
mdss_fb_validate_split(0, 0, mfd);
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("fb%d: %s mode change complete\n", mfd->index, mode->name);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_set_par(struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct fb_var_screeninfo *var = &info->var;
|
|
|
|
int old_imgType;
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ret = mdss_fb_pan_idle(mfd);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("mdss_fb_pan_idle failed. rc=%d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
old_imgType = mfd->fb_imgType;
|
|
|
|
switch (var->bits_per_pixel) {
|
|
|
|
case 16:
|
|
|
|
if (var->red.offset == 0)
|
|
|
|
mfd->fb_imgType = MDP_BGR_565;
|
|
|
|
else
|
|
|
|
mfd->fb_imgType = MDP_RGB_565;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 24:
|
|
|
|
if ((var->transp.offset == 0) && (var->transp.length == 0))
|
|
|
|
mfd->fb_imgType = MDP_RGB_888;
|
|
|
|
else if ((var->transp.offset == 24) &&
|
|
|
|
(var->transp.length == 8)) {
|
|
|
|
mfd->fb_imgType = MDP_ARGB_8888;
|
|
|
|
info->var.bits_per_pixel = 32;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 32:
|
2024-09-09 08:57:42 +00:00
|
|
|
if ((var->red.offset == 0) &&
|
|
|
|
(var->green.offset == 8) &&
|
|
|
|
(var->blue.offset == 16) &&
|
|
|
|
(var->transp.offset == 24))
|
|
|
|
mfd->fb_imgType = MDP_RGBA_8888;
|
|
|
|
else if ((var->red.offset == 16) &&
|
|
|
|
(var->green.offset == 8) &&
|
|
|
|
(var->blue.offset == 0) &&
|
|
|
|
(var->transp.offset == 24))
|
|
|
|
mfd->fb_imgType = MDP_BGRA_8888;
|
|
|
|
else if ((var->red.offset == 8) &&
|
|
|
|
(var->green.offset == 16) &&
|
|
|
|
(var->blue.offset == 24) &&
|
|
|
|
(var->transp.offset == 0))
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd->fb_imgType = MDP_ARGB_8888;
|
|
|
|
else
|
2024-09-09 08:57:42 +00:00
|
|
|
mfd->fb_imgType = MDP_RGBA_8888;
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (info->mode) {
|
|
|
|
const struct fb_videomode *mode;
|
|
|
|
|
|
|
|
mode = fb_match_mode(var, &info->modelist);
|
|
|
|
if (!mode)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pr_debug("found mode: %s\n", mode->name);
|
|
|
|
|
|
|
|
if (fb_mode_is_equal(mode, info->mode)) {
|
|
|
|
pr_debug("mode is equal to current mode\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = mdss_fb_videomode_switch(mfd, mode);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (mfd->mdp.fb_stride)
|
|
|
|
mfd->fbi->fix.line_length = mfd->mdp.fb_stride(mfd->index,
|
|
|
|
var->xres,
|
|
|
|
var->bits_per_pixel / 8);
|
|
|
|
else
|
|
|
|
mfd->fbi->fix.line_length = var->xres * var->bits_per_pixel / 8;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* if memory is not allocated yet, change memory size for fb */
|
|
|
|
if (!info->fix.smem_start)
|
|
|
|
mfd->fbi->fix.smem_len = PAGE_ALIGN(mfd->fbi->fix.line_length *
|
|
|
|
mfd->fbi->var.yres) * mfd->fb_page;
|
|
|
|
|
|
|
|
if (mfd->panel_reconfig || (mfd->fb_imgType != old_imgType)) {
|
|
|
|
mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable);
|
|
|
|
mdss_fb_var_to_panelinfo(var, mfd->panel_info);
|
|
|
|
mdss_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable);
|
|
|
|
mfd->panel_reconfig = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_fb_dcm(struct msm_fb_data_type *mfd, int req_state)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (req_state == mfd->dcm_state) {
|
|
|
|
pr_warn("Already in correct DCM/DTM state\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (req_state) {
|
|
|
|
case DCM_UNBLANK:
|
|
|
|
if (mfd->dcm_state == DCM_UNINIT &&
|
|
|
|
mdss_fb_is_power_off(mfd) && mfd->mdp.on_fnc) {
|
|
|
|
if (mfd->disp_thread == NULL) {
|
|
|
|
ret = mdss_fb_start_disp_thread(mfd);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = mfd->mdp.on_fnc(mfd);
|
|
|
|
if (ret == 0) {
|
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_ON;
|
|
|
|
mfd->dcm_state = DCM_UNBLANK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DCM_ENTER:
|
|
|
|
if (mfd->dcm_state == DCM_UNBLANK) {
|
|
|
|
/*
|
|
|
|
* Keep unblank path available for only
|
|
|
|
* DCM operation
|
|
|
|
*/
|
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
|
|
|
|
mfd->dcm_state = DCM_ENTER;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DCM_EXIT:
|
|
|
|
if (mfd->dcm_state == DCM_ENTER) {
|
|
|
|
/* Release the unblank path for exit */
|
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_ON;
|
|
|
|
mfd->dcm_state = DCM_EXIT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DCM_BLANK:
|
|
|
|
if ((mfd->dcm_state == DCM_EXIT ||
|
|
|
|
mfd->dcm_state == DCM_UNBLANK) &&
|
|
|
|
mdss_fb_is_power_on(mfd) && mfd->mdp.off_fnc) {
|
|
|
|
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
|
|
|
|
ret = mfd->mdp.off_fnc(mfd);
|
|
|
|
if (ret == 0)
|
|
|
|
mfd->dcm_state = DCM_UNINIT;
|
|
|
|
else
|
|
|
|
pr_err("DCM_BLANK failed\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mfd->disp_thread)
|
|
|
|
mdss_fb_stop_disp_thread(mfd);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DTM_ENTER:
|
|
|
|
if (mfd->dcm_state == DCM_UNINIT)
|
|
|
|
mfd->dcm_state = DTM_ENTER;
|
|
|
|
break;
|
|
|
|
case DTM_EXIT:
|
|
|
|
if (mfd->dcm_state == DTM_ENTER)
|
|
|
|
mfd->dcm_state = DCM_UNINIT;
|
|
|
|
break;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_cursor(struct fb_info *info, void __user *p)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct fb_cursor cursor;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!mfd->mdp.cursor_update)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
ret = copy_from_user(&cursor, p, sizeof(cursor));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return mfd->mdp.cursor_update(mfd, &cursor);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
int mdss_fb_async_position_update(struct fb_info *info,
|
|
|
|
struct mdp_position_update *update_pos)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
|
|
|
|
if (!update_pos->input_layer_cnt) {
|
|
|
|
pr_err("no input layers for position update\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return mfd->mdp.async_position_update(mfd, update_pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_async_position_update_ioctl(struct fb_info *info,
|
|
|
|
unsigned long *argp)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct mdp_position_update update_pos;
|
|
|
|
int ret, rc;
|
|
|
|
u32 buffer_size, layer_cnt;
|
|
|
|
struct mdp_async_layer *layer_list = NULL;
|
|
|
|
struct mdp_async_layer __user *input_layer_list;
|
|
|
|
|
|
|
|
if (!mfd->mdp.async_position_update)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
ret = copy_from_user(&update_pos, argp, sizeof(update_pos));
|
|
|
|
if (ret) {
|
|
|
|
pr_err("copy from user failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
input_layer_list = update_pos.input_layers;
|
|
|
|
|
|
|
|
layer_cnt = update_pos.input_layer_cnt;
|
|
|
|
if (!layer_cnt) {
|
|
|
|
pr_err("no async layers to update\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer_size = sizeof(struct mdp_async_layer) * layer_cnt;
|
|
|
|
layer_list = kmalloc(buffer_size, GFP_KERNEL);
|
|
|
|
if (!layer_list) {
|
|
|
|
pr_err("unable to allocate memory for layers\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = copy_from_user(layer_list, input_layer_list, buffer_size);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("layer list copy from user failed\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
update_pos.input_layers = layer_list;
|
|
|
|
|
|
|
|
ret = mdss_fb_async_position_update(info, &update_pos);
|
|
|
|
if (ret)
|
|
|
|
pr_err("async position update failed ret:%d\n", ret);
|
|
|
|
|
|
|
|
rc = copy_to_user(input_layer_list, layer_list, buffer_size);
|
|
|
|
if (rc)
|
|
|
|
pr_err("layer error code copy to user failed\n");
|
|
|
|
|
|
|
|
update_pos.input_layers = input_layer_list;
|
|
|
|
rc = copy_to_user(argp, &update_pos,
|
|
|
|
sizeof(struct mdp_position_update));
|
|
|
|
if (rc)
|
|
|
|
pr_err("copy to user for layers failed");
|
|
|
|
|
|
|
|
end:
|
|
|
|
kfree(layer_list);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int mdss_fb_set_lut(struct fb_info *info, void __user *p)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
struct fb_cmap cmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!mfd->mdp.lut_update)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
ret = copy_from_user(&cmap, p, sizeof(cmap));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
mfd->mdp.lut_update(mfd, &cmap);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/**
|
|
|
|
* mdss_fb_sync_get_fence() - get fence from timeline
|
|
|
|
* @timeline: Timeline to create the fence on
|
|
|
|
* @fence_name: Name of the fence that will be created for debugging
|
|
|
|
* @val: Timeline value at which the fence will be signaled
|
|
|
|
*
|
|
|
|
* Function returns a fence on the timeline given with the name provided.
|
|
|
|
* The fence created will be signaled when the timeline is advanced.
|
|
|
|
*/
|
|
|
|
struct sync_fence *mdss_fb_sync_get_fence(struct sw_sync_timeline *timeline,
|
|
|
|
const char *fence_name, int val)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct sync_pt *sync_pt;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct sync_fence *fence;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
pr_debug("%s: buf sync fence timeline=%d\n", fence_name, val);
|
|
|
|
|
|
|
|
sync_pt = sw_sync_pt_create(timeline, val);
|
|
|
|
if (sync_pt == NULL) {
|
|
|
|
pr_err("%s: cannot create sync point\n", fence_name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create fence */
|
|
|
|
fence = sync_fence_create(fence_name, sync_pt);
|
|
|
|
if (fence == NULL) {
|
|
|
|
sync_pt_free(sync_pt);
|
|
|
|
pr_err("%s: cannot create fence\n", fence_name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fence;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_handle_buf_sync_ioctl(struct msm_sync_pt_data *sync_pt_data,
|
|
|
|
struct mdp_buf_sync *buf_sync)
|
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
int acq_fen_fd[MDP_MAX_FENCE_FD];
|
|
|
|
struct sync_fence *fence, *rel_fence, *retire_fence;
|
|
|
|
int rel_fen_fd;
|
|
|
|
int retire_fen_fd;
|
|
|
|
int val;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if ((buf_sync->acq_fen_fd_cnt > MDP_MAX_FENCE_FD) ||
|
2024-09-09 08:57:42 +00:00
|
|
|
(sync_pt_data->timeline == NULL))
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (buf_sync->acq_fen_fd_cnt)
|
|
|
|
ret = copy_from_user(acq_fen_fd, buf_sync->acq_fen_fd,
|
|
|
|
buf_sync->acq_fen_fd_cnt * sizeof(int));
|
|
|
|
if (ret) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("%s: copy_from_user failed\n", sync_pt_data->fence_name);
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
i = mdss_fb_wait_for_fence(sync_pt_data);
|
|
|
|
if (i > 0)
|
|
|
|
pr_warn("%s: waited on %d active fences\n",
|
|
|
|
sync_pt_data->fence_name, i);
|
|
|
|
|
|
|
|
mutex_lock(&sync_pt_data->sync_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
for (i = 0; i < buf_sync->acq_fen_fd_cnt; i++) {
|
|
|
|
fence = sync_fence_fdget(acq_fen_fd[i]);
|
|
|
|
if (fence == NULL) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("%s: null fence! i=%d fd=%d\n",
|
|
|
|
sync_pt_data->fence_name, i,
|
|
|
|
acq_fen_fd[i]);
|
2024-09-09 08:52:07 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
sync_pt_data->acq_fen[i] = fence;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
sync_pt_data->acq_fen_cnt = i;
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret)
|
|
|
|
goto buf_sync_err_1;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
val = sync_pt_data->timeline_value + sync_pt_data->threshold +
|
|
|
|
atomic_read(&sync_pt_data->commit_cnt);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Set release fence */
|
|
|
|
rel_fence = mdss_fb_sync_get_fence(sync_pt_data->timeline,
|
|
|
|
sync_pt_data->fence_name, val);
|
|
|
|
if (IS_ERR_OR_NULL(rel_fence)) {
|
|
|
|
pr_err("%s: unable to retrieve release fence\n",
|
|
|
|
sync_pt_data->fence_name);
|
|
|
|
ret = rel_fence ? PTR_ERR(rel_fence) : -ENOMEM;
|
2024-09-09 08:52:07 +00:00
|
|
|
goto buf_sync_err_1;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/* create fd */
|
2024-09-09 08:57:42 +00:00
|
|
|
rel_fen_fd = get_unused_fd_flags(0);
|
|
|
|
if (rel_fen_fd < 0) {
|
|
|
|
pr_err("%s: get_unused_fd_flags failed error:0x%x\n",
|
|
|
|
sync_pt_data->fence_name, rel_fen_fd);
|
|
|
|
ret = rel_fen_fd;
|
2024-09-09 08:52:07 +00:00
|
|
|
goto buf_sync_err_2;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
sync_fence_install(rel_fence, rel_fen_fd);
|
|
|
|
|
|
|
|
ret = copy_to_user(buf_sync->rel_fen_fd, &rel_fen_fd, sizeof(int));
|
|
|
|
if (ret) {
|
|
|
|
pr_err("%s: copy_to_user failed\n", sync_pt_data->fence_name);
|
|
|
|
goto buf_sync_err_3;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(buf_sync->flags & MDP_BUF_SYNC_FLAG_RETIRE_FENCE))
|
|
|
|
goto skip_retire_fence;
|
|
|
|
|
|
|
|
if (sync_pt_data->get_retire_fence)
|
|
|
|
retire_fence = sync_pt_data->get_retire_fence(sync_pt_data);
|
|
|
|
else
|
|
|
|
retire_fence = NULL;
|
|
|
|
|
|
|
|
if (IS_ERR_OR_NULL(retire_fence)) {
|
|
|
|
val += sync_pt_data->retire_threshold;
|
|
|
|
retire_fence = mdss_fb_sync_get_fence(
|
|
|
|
sync_pt_data->timeline, "mdp-retire", val);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_ERR_OR_NULL(retire_fence)) {
|
|
|
|
pr_err("%s: unable to retrieve retire fence\n",
|
|
|
|
sync_pt_data->fence_name);
|
|
|
|
ret = retire_fence ? PTR_ERR(rel_fence) : -ENOMEM;
|
|
|
|
goto buf_sync_err_3;
|
|
|
|
}
|
|
|
|
retire_fen_fd = get_unused_fd_flags(0);
|
|
|
|
|
|
|
|
if (retire_fen_fd < 0) {
|
|
|
|
pr_err("%s: get_unused_fd_flags failed for retire fence error:0x%x\n",
|
|
|
|
sync_pt_data->fence_name, retire_fen_fd);
|
|
|
|
ret = retire_fen_fd;
|
|
|
|
sync_fence_put(retire_fence);
|
|
|
|
goto buf_sync_err_3;
|
|
|
|
}
|
|
|
|
|
|
|
|
sync_fence_install(retire_fence, retire_fen_fd);
|
|
|
|
|
|
|
|
ret = copy_to_user(buf_sync->retire_fen_fd, &retire_fen_fd,
|
|
|
|
sizeof(int));
|
2024-09-09 08:52:07 +00:00
|
|
|
if (ret) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("%s: copy_to_user failed for retire fence\n",
|
|
|
|
sync_pt_data->fence_name);
|
|
|
|
put_unused_fd(retire_fen_fd);
|
|
|
|
sync_fence_put(retire_fence);
|
2024-09-09 08:52:07 +00:00
|
|
|
goto buf_sync_err_3;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
skip_retire_fence:
|
|
|
|
mutex_unlock(&sync_pt_data->sync_mutex);
|
|
|
|
|
|
|
|
if (buf_sync->flags & MDP_BUF_SYNC_FLAG_WAIT)
|
|
|
|
mdss_fb_wait_for_fence(sync_pt_data);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
buf_sync_err_3:
|
2024-09-09 08:57:42 +00:00
|
|
|
put_unused_fd(rel_fen_fd);
|
2024-09-09 08:52:07 +00:00
|
|
|
buf_sync_err_2:
|
2024-09-09 08:57:42 +00:00
|
|
|
sync_fence_put(rel_fence);
|
2024-09-09 08:52:07 +00:00
|
|
|
buf_sync_err_1:
|
2024-09-09 08:57:42 +00:00
|
|
|
for (i = 0; i < sync_pt_data->acq_fen_cnt; i++)
|
|
|
|
sync_fence_put(sync_pt_data->acq_fen[i]);
|
|
|
|
sync_pt_data->acq_fen_cnt = 0;
|
|
|
|
mutex_unlock(&sync_pt_data->sync_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int mdss_fb_display_commit(struct fb_info *info,
|
|
|
|
unsigned long *argp)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct mdp_display_commit disp_commit;
|
|
|
|
ret = copy_from_user(&disp_commit, argp,
|
|
|
|
sizeof(disp_commit));
|
|
|
|
if (ret) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("%s:copy_from_user failed\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = mdss_fb_pan_display_ex(info, &disp_commit);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_atomic_commit_ioctl(struct fb_info *info,
|
|
|
|
unsigned long *argp, struct file *file)
|
|
|
|
{
|
|
|
|
int ret, i = 0, j = 0, rc;
|
|
|
|
struct mdp_layer_commit commit;
|
|
|
|
u32 buffer_size, layer_count;
|
|
|
|
struct mdp_input_layer *layer, *layer_list = NULL;
|
|
|
|
struct mdp_input_layer __user *input_layer_list;
|
|
|
|
struct mdp_scale_data *scale;
|
|
|
|
struct mdp_output_layer *output_layer = NULL;
|
|
|
|
struct mdp_output_layer __user *output_layer_user;
|
|
|
|
|
|
|
|
ret = copy_from_user(&commit, argp, sizeof(struct mdp_layer_commit));
|
|
|
|
if (ret) {
|
|
|
|
pr_err("%s:copy_from_user failed\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
output_layer_user = commit.commit_v1.output_layer;
|
|
|
|
if (output_layer_user) {
|
|
|
|
buffer_size = sizeof(struct mdp_output_layer);
|
|
|
|
output_layer = kzalloc(buffer_size, GFP_KERNEL);
|
|
|
|
if (!output_layer) {
|
|
|
|
pr_err("unable to allocate memory for output layer\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = copy_from_user(output_layer,
|
|
|
|
output_layer_user, buffer_size);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("layer list copy from user failed\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
commit.commit_v1.output_layer = output_layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
layer_count = commit.commit_v1.input_layer_cnt;
|
|
|
|
input_layer_list = commit.commit_v1.input_layers;
|
|
|
|
|
|
|
|
if (layer_count > MAX_LAYER_COUNT) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
} else if (layer_count) {
|
|
|
|
buffer_size = sizeof(struct mdp_input_layer) * layer_count;
|
|
|
|
layer_list = kzalloc(buffer_size, GFP_KERNEL);
|
|
|
|
if (!layer_list) {
|
|
|
|
pr_err("unable to allocate memory for layers\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = copy_from_user(layer_list, input_layer_list, buffer_size);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("layer list copy from user failed\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
commit.commit_v1.input_layers = layer_list;
|
|
|
|
|
|
|
|
for (i = 0; i < layer_count; i++) {
|
|
|
|
layer = &layer_list[i];
|
|
|
|
scale = NULL;
|
|
|
|
|
|
|
|
if (!(layer->flags & MDP_LAYER_PP)) {
|
|
|
|
layer->pp_info = NULL;
|
|
|
|
} else {
|
|
|
|
ret = mdss_mdp_copy_layer_pp_info(layer);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("failure to copy pp_info data for layer %d, ret = %d\n",
|
|
|
|
i, ret);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(layer->flags & MDP_LAYER_ENABLE_PIXEL_EXT)) {
|
|
|
|
layer->scale = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
scale = kzalloc(sizeof(struct mdp_scale_data),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!scale) {
|
|
|
|
pr_err("unable to allocate memory for overlays\n");
|
|
|
|
mdss_mdp_free_layer_pp_info(layer);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = copy_from_user(scale, layer->scale,
|
|
|
|
sizeof(struct mdp_scale_data));
|
|
|
|
if (ret) {
|
|
|
|
pr_err("layer list copy from user failed, scale = %p\n",
|
|
|
|
layer->scale);
|
|
|
|
kfree(scale);
|
|
|
|
scale = NULL;
|
|
|
|
mdss_mdp_free_layer_pp_info(layer);
|
|
|
|
ret = -EFAULT;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
layer->scale = scale;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = mdss_fb_atomic_commit(info, &commit, file);
|
|
|
|
if (ret)
|
|
|
|
pr_err("atomic commit failed ret:%d\n", ret);
|
|
|
|
|
|
|
|
if (layer_count) {
|
|
|
|
for (j = 0; j < layer_count; j++) {
|
|
|
|
rc = copy_to_user(&input_layer_list[j].error_code,
|
|
|
|
&layer_list[j].error_code, sizeof(int));
|
|
|
|
if (rc)
|
|
|
|
pr_err("layer error code copy to user failed\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
commit.commit_v1.input_layers = input_layer_list;
|
|
|
|
commit.commit_v1.output_layer = output_layer_user;
|
|
|
|
rc = copy_to_user(argp, &commit,
|
|
|
|
sizeof(struct mdp_layer_commit));
|
|
|
|
if (rc)
|
|
|
|
pr_err("copy to user for release & retire fence failed\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
err:
|
|
|
|
for (i--; i >= 0; i--) {
|
|
|
|
kfree(layer_list[i].scale);
|
|
|
|
layer_list[i].scale = NULL;
|
|
|
|
mdss_mdp_free_layer_pp_info(&layer_list[i]);
|
|
|
|
}
|
|
|
|
kfree(layer_list);
|
|
|
|
kfree(output_layer);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_fb_switch_check(struct msm_fb_data_type *mfd, u32 mode)
|
|
|
|
{
|
|
|
|
struct mdss_panel_info *pinfo = NULL;
|
|
|
|
int panel_type;
|
|
|
|
|
|
|
|
if (!mfd || !mfd->panel_info)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pinfo = mfd->panel_info;
|
|
|
|
|
|
|
|
if ((!mfd->op_enable) || (mdss_fb_is_power_off(mfd)))
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (pinfo->mipi.dms_mode != DYNAMIC_MODE_SWITCH_IMMEDIATE) {
|
|
|
|
pr_warn("Panel does not support immediate dynamic switch!\n");
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mfd->dcm_state != DCM_UNINIT) {
|
|
|
|
pr_warn("Switch not supported during DCM!\n");
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
if (mode == pinfo->type) {
|
|
|
|
pr_debug("Already in requested mode!\n");
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
|
|
|
|
panel_type = mfd->panel.type;
|
|
|
|
if (panel_type != MIPI_VIDEO_PANEL && panel_type != MIPI_CMD_PANEL) {
|
|
|
|
pr_debug("Panel not in mipi video or cmd mode, cannot change\n");
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mdss_fb_immediate_mode_switch(struct msm_fb_data_type *mfd, u32 mode)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u32 tranlated_mode;
|
|
|
|
|
|
|
|
if (mode)
|
|
|
|
tranlated_mode = MIPI_CMD_PANEL;
|
|
|
|
else
|
|
|
|
tranlated_mode = MIPI_VIDEO_PANEL;
|
|
|
|
|
|
|
|
pr_debug("%s: Request to switch to %d,", __func__, tranlated_mode);
|
|
|
|
|
|
|
|
ret = mdss_fb_switch_check(mfd, tranlated_mode);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
mutex_lock(&mfd->switch_lock);
|
|
|
|
if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) {
|
|
|
|
pr_err("%s: Mode switch already in progress\n", __func__);
|
|
|
|
ret = -EAGAIN;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
mfd->switch_state = MDSS_MDP_WAIT_FOR_PREP;
|
|
|
|
mfd->switch_new_mode = tranlated_mode;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&mfd->switch_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mdss_fb_mode_switch() - Function to change DSI mode
|
|
|
|
* @mfd: Framebuffer data structure for display
|
|
|
|
* @mode: Enabled/Disable LowPowerMode
|
|
|
|
* 1: Switch to Command Mode
|
|
|
|
* 0: Switch to video Mode
|
|
|
|
*
|
|
|
|
* This function is used to change from DSI mode based on the
|
|
|
|
* argument @mode on the next frame to be displayed.
|
|
|
|
*/
|
|
|
|
static int mdss_fb_mode_switch(struct msm_fb_data_type *mfd, u32 mode)
|
|
|
|
{
|
|
|
|
struct mdss_panel_info *pinfo = NULL;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!mfd || !mfd->panel_info)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pinfo = mfd->panel_info;
|
|
|
|
if (pinfo->mipi.dms_mode == DYNAMIC_MODE_SWITCH_SUSPEND_RESUME) {
|
|
|
|
ret = mdss_fb_blanking_mode_switch(mfd, mode);
|
|
|
|
} else if (pinfo->mipi.dms_mode == DYNAMIC_MODE_SWITCH_IMMEDIATE) {
|
|
|
|
ret = mdss_fb_immediate_mode_switch(mfd, mode);
|
|
|
|
} else {
|
|
|
|
pr_warn("Panel does not support dynamic mode switch!\n");
|
|
|
|
ret = -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __ioctl_wait_idle(struct msm_fb_data_type *mfd, u32 cmd)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (mfd->wait_for_kickoff &&
|
|
|
|
((cmd == MSMFB_OVERLAY_PREPARE) ||
|
|
|
|
(cmd == MSMFB_BUFFER_SYNC) ||
|
|
|
|
(cmd == MSMFB_OVERLAY_PLAY) ||
|
|
|
|
(cmd == MSMFB_CURSOR) ||
|
|
|
|
(cmd == MSMFB_METADATA_GET) ||
|
|
|
|
(cmd == MSMFB_METADATA_SET) ||
|
|
|
|
(cmd == MSMFB_OVERLAY_GET) ||
|
|
|
|
(cmd == MSMFB_OVERLAY_UNSET) ||
|
|
|
|
(cmd == MSMFB_OVERLAY_SET))) {
|
|
|
|
ret = mdss_fb_wait_for_kickoff(mfd);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret && (ret != -ESHUTDOWN))
|
|
|
|
pr_err("wait_idle failed. cmd=0x%x rc=%d\n", cmd, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mdss_fb_do_ioctl() - MDSS Framebuffer ioctl function
|
|
|
|
* @info: pointer to framebuffer info
|
|
|
|
* @cmd: ioctl command
|
|
|
|
* @arg: argument to ioctl
|
|
|
|
*
|
|
|
|
* This function provides an architecture agnostic implementation
|
|
|
|
* of the mdss framebuffer ioctl. This function can be called
|
|
|
|
* by compat ioctl or regular ioctl to handle the supported commands.
|
|
|
|
*/
|
|
|
|
int mdss_fb_do_ioctl(struct fb_info *info, unsigned int cmd,
|
|
|
|
unsigned long arg, struct file *file)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
int ret = -ENOSYS;
|
|
|
|
struct mdp_buf_sync buf_sync;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_sync_pt_data *sync_pt_data = NULL;
|
|
|
|
unsigned int dsi_mode = 0;
|
|
|
|
struct mdss_panel_data *pdata = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!info || !info->par)
|
|
|
|
return -EINVAL;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mfd = (struct msm_fb_data_type *)info->par;
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mfd)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mfd->shutdown_pending)
|
|
|
|
return -ESHUTDOWN;
|
|
|
|
|
|
|
|
pdata = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata || pdata->panel_info.dynamic_switch_pending)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
atomic_inc(&mfd->ioctl_ref_cnt);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
mdss_fb_power_setting_idle(mfd);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = __ioctl_wait_idle(mfd, cmd);
|
|
|
|
if (ret)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
__ioctl_transition_dyn_mode_state(mfd, cmd, 0);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case MSMFB_CURSOR:
|
|
|
|
ret = mdss_fb_cursor(info, argp);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSMFB_SET_LUT:
|
|
|
|
ret = mdss_fb_set_lut(info, argp);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSMFB_BUFFER_SYNC:
|
|
|
|
ret = copy_from_user(&buf_sync, argp, sizeof(buf_sync));
|
|
|
|
if (ret)
|
2024-09-09 08:57:42 +00:00
|
|
|
goto exit;
|
|
|
|
|
|
|
|
if (mfd->mdp.get_sync_fnc)
|
|
|
|
sync_pt_data = mfd->mdp.get_sync_fnc(mfd, &buf_sync);
|
|
|
|
if (!sync_pt_data) {
|
|
|
|
if ((!mfd->op_enable) || (mdss_fb_is_power_off(mfd))) {
|
|
|
|
ret = -EPERM;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
sync_pt_data = &mfd->mdp_sync_pt_data;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = mdss_fb_handle_buf_sync_ioctl(sync_pt_data, &buf_sync);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
ret = copy_to_user(argp, &buf_sync, sizeof(buf_sync));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSMFB_NOTIFY_UPDATE:
|
|
|
|
ret = mdss_fb_notify_update(mfd, argp);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSMFB_DISPLAY_COMMIT:
|
|
|
|
ret = mdss_fb_display_commit(info, argp);
|
|
|
|
break;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
case MSMFB_LPM_ENABLE:
|
|
|
|
ret = copy_from_user(&dsi_mode, argp, sizeof(dsi_mode));
|
|
|
|
if (ret) {
|
|
|
|
pr_err("%s: MSMFB_LPM_ENABLE ioctl failed\n", __func__);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = mdss_fb_mode_switch(mfd, dsi_mode);
|
|
|
|
break;
|
|
|
|
case MSMFB_ATOMIC_COMMIT:
|
|
|
|
ret = mdss_fb_atomic_commit_ioctl(info, argp, file);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSMFB_ASYNC_POSITION_UPDATE:
|
|
|
|
ret = mdss_fb_async_position_update_ioctl(info, argp);
|
|
|
|
break;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
default:
|
|
|
|
if (mfd->mdp.ioctl_handler)
|
|
|
|
ret = mfd->mdp.ioctl_handler(mfd, cmd, argp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == -ENOSYS)
|
|
|
|
pr_err("unsupported ioctl (%x)\n", cmd);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
exit:
|
|
|
|
if (!atomic_dec_return(&mfd->ioctl_ref_cnt))
|
|
|
|
wake_up_all(&mfd->ioctl_q);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd,
|
|
|
|
unsigned long arg, struct file *file)
|
|
|
|
{
|
|
|
|
if (!info || !info->par)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return mdss_fb_do_ioctl(info, cmd, arg, file);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
struct fb_info *msm_fb_get_writeback_fb(void)
|
|
|
|
{
|
|
|
|
int c = 0;
|
|
|
|
for (c = 0; c < fbi_list_index; ++c) {
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
mfd = (struct msm_fb_data_type *)fbi_list[c]->par;
|
|
|
|
if (mfd->panel.type == WRITEBACK_PANEL)
|
|
|
|
return fbi_list[c];
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(msm_fb_get_writeback_fb);
|
|
|
|
|
|
|
|
static int mdss_fb_register_extra_panel(struct platform_device *pdev,
|
|
|
|
struct mdss_panel_data *pdata)
|
|
|
|
{
|
|
|
|
struct mdss_panel_data *fb_pdata;
|
|
|
|
|
|
|
|
fb_pdata = dev_get_platdata(&pdev->dev);
|
|
|
|
if (!fb_pdata) {
|
|
|
|
pr_err("framebuffer device %s contains invalid panel data\n",
|
|
|
|
dev_name(&pdev->dev));
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fb_pdata->next) {
|
|
|
|
pr_err("split panel already setup for framebuffer device %s\n",
|
|
|
|
dev_name(&pdev->dev));
|
|
|
|
return -EEXIST;
|
|
|
|
}
|
|
|
|
|
|
|
|
fb_pdata->next = pdata;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mdss_register_panel(struct platform_device *pdev,
|
|
|
|
struct mdss_panel_data *pdata)
|
|
|
|
{
|
|
|
|
struct platform_device *fb_pdev, *mdss_pdev;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct device_node *node = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
int rc = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
bool master_panel = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!pdev || !pdev->dev.of_node) {
|
|
|
|
pr_err("Invalid device node\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mdp_instance) {
|
|
|
|
pr_err("mdss mdp resource not initialized yet\n");
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (pdata->get_fb_node)
|
|
|
|
node = pdata->get_fb_node(pdev);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!node) {
|
2024-09-09 08:57:42 +00:00
|
|
|
node = of_parse_phandle(pdev->dev.of_node,
|
|
|
|
"qcom,mdss-fb-map", 0);
|
|
|
|
if (!node) {
|
|
|
|
pr_err("Unable to find fb node for device: %s\n",
|
|
|
|
pdev->name);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
mdss_pdev = of_find_device_by_node(node->parent);
|
|
|
|
if (!mdss_pdev) {
|
|
|
|
pr_err("Unable to find mdss for node: %s\n", node->full_name);
|
|
|
|
rc = -ENODEV;
|
|
|
|
goto mdss_notfound;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pdata->active = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
fb_pdev = of_find_device_by_node(node);
|
|
|
|
if (fb_pdev) {
|
|
|
|
rc = mdss_fb_register_extra_panel(fb_pdev, pdata);
|
2024-09-09 08:57:42 +00:00
|
|
|
if (rc == 0)
|
|
|
|
master_panel = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
pr_info("adding framebuffer device %s\n", dev_name(&pdev->dev));
|
|
|
|
fb_pdev = of_platform_device_create(node, NULL,
|
|
|
|
&mdss_pdev->dev);
|
2024-09-09 08:57:42 +00:00
|
|
|
if (fb_pdev)
|
|
|
|
fb_pdev->dev.platform_data = pdata;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (master_panel && mdp_instance->panel_register_done)
|
2024-09-09 08:52:07 +00:00
|
|
|
mdp_instance->panel_register_done(pdata);
|
|
|
|
|
|
|
|
mdss_notfound:
|
|
|
|
of_node_put(node);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(mdss_register_panel);
|
|
|
|
|
|
|
|
int mdss_fb_register_mdp_instance(struct msm_mdp_interface *mdp)
|
|
|
|
{
|
|
|
|
if (mdp_instance) {
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_err("multiple MDP instance registration\n");
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mdp_instance = mdp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(mdss_fb_register_mdp_instance);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
int mdss_fb_get_phys_info(dma_addr_t *start, unsigned long *len, int fb_num)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct fb_info *info;
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (fb_num >= MAX_FBI_LIST)
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
info = fbi_list[fb_num];
|
|
|
|
if (!info)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
mfd = (struct msm_fb_data_type *)info->par;
|
|
|
|
if (!mfd)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (mfd->iova)
|
|
|
|
*start = mfd->iova;
|
|
|
|
else
|
|
|
|
*start = info->fix.smem_start;
|
|
|
|
*len = info->fix.smem_len;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(mdss_fb_get_phys_info);
|
|
|
|
|
|
|
|
int __init mdss_fb_init(void)
|
|
|
|
{
|
|
|
|
int rc = -ENODEV;
|
|
|
|
|
|
|
|
if (platform_driver_register(&mdss_fb_driver))
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(mdss_fb_init);
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
int mdss_fb_suspres_panel(struct device *dev, void *data)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd;
|
|
|
|
int rc = 0;
|
|
|
|
u32 event;
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
pr_err("Device state not defined\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
mfd = dev_get_drvdata(dev);
|
|
|
|
if (!mfd)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
event = *((bool *) data) ? MDSS_EVENT_RESUME : MDSS_EVENT_SUSPEND;
|
|
|
|
|
|
|
|
/* Do not send runtime suspend/resume for HDMI primary */
|
|
|
|
if (!mdss_fb_is_hdmi_primary(mfd)) {
|
|
|
|
rc = mdss_fb_send_panel_event(mfd, event, NULL);
|
|
|
|
if (rc)
|
|
|
|
pr_warn("unable to %s fb%d (%d)\n",
|
|
|
|
event == MDSS_EVENT_RESUME ?
|
|
|
|
"resume" : "suspend",
|
|
|
|
mfd->index, rc);
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mdss_fb_report_panel_dead() - Sends the PANEL_ALIVE=0 status to HAL layer.
|
|
|
|
* @mfd : frame buffer structure associated with fb device.
|
|
|
|
*
|
|
|
|
* This function is called if the panel fails to respond as expected to
|
|
|
|
* the register read/BTA or if the TE signal is not coming as expected
|
|
|
|
* from the panel. The function sends the PANEL_ALIVE=0 status to HAL
|
|
|
|
* layer.
|
|
|
|
*/
|
|
|
|
void mdss_fb_report_panel_dead(struct msm_fb_data_type *mfd)
|
|
|
|
{
|
|
|
|
char *envp[2] = {"PANEL_ALIVE=0", NULL};
|
|
|
|
struct mdss_panel_data *pdata =
|
|
|
|
dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
if (!pdata) {
|
|
|
|
pr_err("Panel data not available\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pdata->panel_info.panel_dead = true;
|
|
|
|
kobject_uevent_env(&mfd->fbi->dev->kobj,
|
|
|
|
KOBJ_CHANGE, envp);
|
|
|
|
pr_err("Panel has gone bad, sending uevent - %s\n", envp[0]);
|
|
|
|
}
|