blob: edfedb3935ba5920e254e15920129d8355a2bdb9 [file] [log] [blame]
/*
* Core MDSS framebuffer driver.
*
* Copyright (C) 2007 Google Incorporated
* Copyright (c) 2008-2013, The Linux Foundation. All rights reserved.
*
* 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__
#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>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/memory.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/msm_mdp.h>
#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>
#include <linux/memory_alloc.h>
#include <linux/kthread.h>
#include <mach/board.h>
#include <mach/memory.h>
#include <mach/iommu.h>
#include <mach/iommu_domains.h>
#include <mach/msm_memtypes.h>
/* HACK: preventing displaying garbage in off-mode charge */
#ifdef CONFIG_MACH_LGE
#include <mach/board_lge.h>
#endif
#include "mdss_fb.h"
#ifdef CONFIG_LGE_HANDLE_PANIC
#include <mach/lge_handle_panic.h>
#endif
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
#define MDSS_FB_NUM 3
#else
#define MDSS_FB_NUM 2
#endif
#define MAX_FBI_LIST 32
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);
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,
unsigned long arg);
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma);
static void mdss_fb_release_fences(struct msm_fb_data_type *mfd);
static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p,
unsigned long val, void *data);
static int __mdss_fb_display_thread(void *data);
static void mdss_fb_pan_idle(struct msm_fb_data_type *mfd);
static int mdss_fb_send_panel_event(struct msm_fb_data_type *mfd,
int event, void *arg);
void mdss_fb_no_update_notify_timer_cb(unsigned long data)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data;
if (!mfd)
pr_err("%s mfd NULL\n", __func__);
mfd->no_update.value = NOTIFY_TYPE_NO_UPDATE;
complete(&mfd->no_update.comp);
}
static int mdss_fb_notify_update(struct msm_fb_data_type *mfd,
unsigned long *argp)
{
int ret, notify, to_user;
ret = copy_from_user(&notify, argp, sizeof(int));
if (ret) {
pr_err("%s:ioctl failed\n", __func__);
return ret;
}
if (notify > NOTIFY_UPDATE_POWER_OFF)
return -EINVAL;
if (notify == NOTIFY_UPDATE_START) {
INIT_COMPLETION(mfd->update.comp);
ret = wait_for_completion_interruptible_timeout(
&mfd->update.comp, 4 * HZ);
to_user = mfd->update.value;
} else if (notify == NOTIFY_UPDATE_STOP) {
INIT_COMPLETION(mfd->no_update.comp);
ret = wait_for_completion_interruptible_timeout(
&mfd->no_update.comp, 4 * HZ);
to_user = mfd->no_update.value;
} else {
if (mfd->panel_power_on) {
INIT_COMPLETION(mfd->power_off_comp);
ret = wait_for_completion_interruptible_timeout(
&mfd->power_off_comp, 1 * HZ);
}
}
if (ret == 0)
ret = -ETIMEDOUT;
else if (ret > 0)
ret = copy_to_user(argp, &to_user, sizeof(int));
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;
if (value > MDSS_MAX_BL_BRIGHTNESS)
value = MDSS_MAX_BL_BRIGHTNESS;
/* 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,
MDSS_MAX_BL_BRIGHTNESS);
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",
.brightness = MDSS_MAX_BL_BRIGHTNESS,
.brightness_set = mdss_fb_set_bl_brightness,
};
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;
}
static void mdss_fb_parse_dt_split(struct msm_fb_data_type *mfd)
{
u32 data[2];
struct platform_device *pdev = mfd->pdev;
if (of_property_read_u32_array(pdev->dev.of_node, "qcom,mdss-fb-split",
data, 2))
return;
if (data[0] && data[1] &&
(mfd->panel_info->xres == (data[0] + data[1]))) {
mfd->split_fb_left = data[0];
mfd->split_fb_right = data[1];
pr_info("split framebuffer left=%d right=%d\n",
mfd->split_fb_left, mfd->split_fb_right);
} else {
mfd->split_fb_left = 0;
mfd->split_fb_right = 0;
}
}
static ssize_t mdss_fb_get_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 DEVICE_ATTR(msm_fb_type, S_IRUGO, mdss_fb_get_type, NULL);
static DEVICE_ATTR(msm_fb_split, S_IRUGO, mdss_fb_get_split, NULL);
static struct attribute *mdss_fb_attrs[] = {
&dev_attr_msm_fb_type.attr,
&dev_attr_msm_fb_split.attr,
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);
}
static void mdss_fb_shutdown(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
for (; mfd->ref_cnt > 1; mfd->ref_cnt--)
pm_runtime_put(mfd->fbi->dev);
mdss_fb_release(mfd->fbi, 0);
}
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;
/*
* 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;
mfd->bl_level = 0;
mfd->bl_scale = 1024;
mfd->bl_min_lvl = 30;
mfd->fb_imgType = MDP_RGBA_8888;
mfd->pdev = pdev;
if (pdata->next)
mfd->split_display = true;
mfd->mdp = *mdp_instance;
INIT_LIST_HEAD(&mfd->proc_list);
mutex_init(&mfd->lock);
mutex_init(&mfd->bl_lock);
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) {
if (led_classdev_register(&pdev->dev, &backlight_led))
pr_err("led_classdev_register failed\n");
else
lcd_backlight_registered = 1;
}
mdss_fb_create_sysfs(mfd);
mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi);
mfd->mdp_sync_pt_data.fence_name = "mdp-fence";
if (mfd->mdp_sync_pt_data.timeline == NULL) {
char timeline_name[16];
snprintf(timeline_name, sizeof(timeline_name),
"mdss_fb_%d", mfd->index);
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");
return -ENOMEM;
}
mfd->mdp_sync_pt_data.notifier.notifier_call =
__mdss_fb_sync_buf_done_callback;
}
switch (mfd->panel.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;
}
return rc;
}
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);
mdss_fb_remove_sysfs(mfd);
pm_runtime_disable(mfd->fbi->dev);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
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)
{
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);
if (pdata->event_handler)
return pdata->event_handler(pdata, event, arg);
return 0;
}
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);
mdss_fb_pan_idle(mfd);
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);
return ret;
}
mfd->suspend.op_enable = mfd->op_enable;
mfd->suspend.panel_power_on = mfd->panel_power_on;
if (mfd->op_enable) {
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi,
mfd->suspend.op_enable);
if (ret) {
pr_warn("can't turn off display!\n");
return ret;
}
mfd->op_enable = false;
fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED);
}
return 0;
}
static int mdss_fb_resume_sub(struct msm_fb_data_type *mfd)
{
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
INIT_COMPLETION(mfd->power_set_comp);
mfd->is_power_setting = true;
pr_debug("mdss_fb resume index=%d\n", mfd->index);
mdss_fb_pan_idle(mfd);
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;
if (mfd->suspend.panel_power_on) {
ret = mdss_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi,
mfd->op_enable);
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");
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,
.shutdown = mdss_fb_shutdown,
.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;
pr_debug("input = %d, scale = %d", temp, mfd->bl_scale);
if (temp >= mfd->bl_min_lvl) {
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)
*/
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;
}
pr_debug("output = %d", temp);
(*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;
if ((!mfd->panel_power_on || !mfd->bl_updated) &&
!IS_CALIB_MODE_BL(mfd)) {
mfd->unset_bl_level = bkl_lvl;
return;
} else {
mfd->unset_bl_level = 0;
}
pdata = dev_get_platdata(&mfd->pdev->dev);
if ((pdata) && (pdata->set_backlight)) {
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.
*/
if (mfd->bl_level_old == temp) {
mfd->bl_level = bkl_lvl;
return;
}
pdata->set_backlight(pdata, temp);
mfd->bl_level = bkl_lvl;
mfd->bl_level_old = temp;
if (mfd->mdp.update_ad_input) {
mutex_unlock(&mfd->bl_lock);
/* Will trigger ad_setup which will grab bl_lock */
mfd->mdp.update_ad_input(mfd);
mutex_lock(&mfd->bl_lock);
}
}
}
void mdss_fb_update_backlight(struct msm_fb_data_type *mfd)
{
struct mdss_panel_data *pdata;
if (mfd->unset_bl_level && !mfd->bl_updated) {
pdata = dev_get_platdata(&mfd->pdev->dev);
if ((pdata) && (pdata->set_backlight)) {
mutex_lock(&mfd->bl_lock);
mfd->bl_level = mfd->unset_bl_level;
pdata->set_backlight(pdata, mfd->bl_level);
mfd->bl_level_old = mfd->unset_bl_level;
mutex_unlock(&mfd->bl_lock);
mfd->bl_updated = 1;
}
}
}
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;
if (!op_enable)
return -EPERM;
if (mfd->dcm_state == DCM_ENTER)
return -EPERM;
switch (blank_mode) {
case FB_BLANK_UNBLANK:
if (!mfd->panel_power_on && mfd->mdp.on_fnc) {
ret = mfd->mdp.on_fnc(mfd);
if (ret == 0)
mfd->panel_power_on = true;
mutex_lock(&mfd->update.lock);
mfd->update.type = NOTIFY_TYPE_UPDATE;
mutex_unlock(&mfd->update.lock);
}
break;
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
case FB_BLANK_POWERDOWN:
default:
if (mfd->panel_power_on && mfd->mdp.off_fnc) {
int curr_pwr_state;
mutex_lock(&mfd->update.lock);
mfd->update.type = NOTIFY_TYPE_SUSPEND;
mutex_unlock(&mfd->update.lock);
del_timer(&mfd->no_update.timer);
mfd->no_update.value = NOTIFY_TYPE_SUSPEND;
complete(&mfd->no_update.comp);
mfd->op_enable = false;
curr_pwr_state = mfd->panel_power_on;
mfd->panel_power_on = false;
mfd->bl_updated = 0;
ret = mfd->mdp.off_fnc(mfd);
if (ret)
mfd->panel_power_on = curr_pwr_state;
else
mdss_fb_release_fences(mfd);
mfd->op_enable = true;
complete(&mfd->power_off_comp);
}
break;
}
return ret;
}
static int mdss_fb_blank(int blank_mode, struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int ret;
mdss_fb_pan_idle(mfd);
if (mfd->op_enable == 0) {
if (blank_mode == FB_BLANK_UNBLANK)
mfd->suspend.panel_power_on = true;
else
mfd->suspend.panel_power_on = false;
return 0;
}
ret = mdss_fb_blank_sub(blank_mode, info, mfd->op_enable);
#ifdef CONFIG_MACH_LGE
if (lge_get_boot_mode() == LGE_BOOT_MODE_CHARGER) {
/* HACK: preventing displaying garbage in off-mode charge */
if (blank_mode == FB_BLANK_UNBLANK &&
mfd->panel_info->type == MIPI_CMD_PANEL) {
msleep(33);
}
}
#endif
return ret;
}
/*
* Custom Framebuffer mmap() function for MSM driver.
* Differs from standard mmap() function by allowing for customized
* page-protection.
*/
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
/* 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) {
pr_warn("No framebuffer memory is allocated.\n");
return -ENOMEM;
}
mdss_fb_pan_idle(mfd);
/* Set VM flags. */
start &= PAGE_MASK;
if ((vma->vm_end <= vma->vm_start) ||
(off >= len) ||
((vma->vm_end - vma->vm_start) > (len - off)))
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 */
vma->vm_flags |= VM_IO | VM_RESERVED;
/* Set VM page protection */
if (mfd->mdp_fb_page_protection == MDP_FB_PAGE_PROTECTION_WRITECOMBINE)
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
else if (mfd->mdp_fb_page_protection ==
MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE)
vma->vm_page_prot = pgprot_writethroughcache(vma->vm_page_prot);
else if (mfd->mdp_fb_page_protection ==
MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE)
vma->vm_page_prot = pgprot_writebackcache(vma->vm_page_prot);
else if (mfd->mdp_fb_page_protection ==
MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE)
vma->vm_page_prot = pgprot_writebackwacache(vma->vm_page_prot);
else
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* Remap the frame buffer I/O range */
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
return -EAGAIN;
return 0;
}
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 */
.fb_ioctl = mdss_fb_ioctl, /* perform fb specific ioctl */
.fb_mmap = mdss_fb_mmap,
};
static int mdss_fb_alloc_fbmem_iommu(struct msm_fb_data_type *mfd, int dom)
{
void *virt = NULL;
unsigned long phys = 0;
size_t size = 0;
struct platform_device *pdev = mfd->pdev;
if (!pdev || !pdev->dev.of_node) {
pr_err("Invalid device node\n");
return -ENODEV;
}
if (of_property_read_u32(pdev->dev.of_node,
"qcom,memory-reservation-size",
&size) || !size) {
mfd->fbi->screen_base = NULL;
mfd->fbi->fix.smem_start = 0;
mfd->fbi->fix.smem_len = 0;
return 0;
}
pr_info("%s frame buffer reserve_size=0x%x\n", __func__, size);
if (size < PAGE_ALIGN(mfd->fbi->fix.line_length *
mfd->fbi->var.yres_virtual))
pr_warn("reserve size is smaller than framebuffer size\n");
virt = allocate_contiguous_memory(size, MEMTYPE_EBI1, SZ_1M, 0);
if (!virt) {
pr_err("unable to alloc fbmem size=%u\n", size);
return -ENOMEM;
}
phys = memory_pool_node_paddr(virt);
msm_iommu_map_contig_buffer(phys, dom, 0, size, SZ_4K, 0,
&mfd->iova);
pr_info("allocating %u bytes at %p (%lx phys) for fb %d\n",
size, virt, phys, mfd->index);
#ifdef CONFIG_LGE_HANDLE_PANIC
/* save fb1 address for lge crash handler display buffer */
lge_set_fb1_addr((unsigned int)(phys +
(mfd->fbi->fix.line_length *
mfd->fbi->var.yres)));
#endif
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)
{
if (mfd->mdp.fb_mem_alloc_fnc)
return mfd->mdp.fb_mem_alloc_fnc(mfd);
else if (mfd->mdp.fb_mem_get_iommu_domain) {
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;
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;
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 = 24;
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;
var->blue.offset = 8;
var->green.offset = 16;
var->red.offset = 24;
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 = 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;
}
var->xres = panel_info->xres;
if (mfd->split_display)
var->xres *= 2;
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->yres = panel_info->yres;
var->width = panel_info->width;
var->height = panel_info->height;
var->xres_virtual = var->xres;
var->yres_virtual = panel_info->yres * mfd->fb_page;
var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */
var->upper_margin = panel_info->lcdc.v_back_porch;
var->lower_margin = panel_info->lcdc.v_front_porch;
var->vsync_len = panel_info->lcdc.v_pulse_width;
var->left_margin = panel_info->lcdc.h_back_porch;
var->right_margin = panel_info->lcdc.h_front_porch;
var->hsync_len = panel_info->lcdc.h_pulse_width;
var->pixclock = panel_info->clk_rate / 1000;
/* id field for fb app */
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;
mfd->panel_power_on = false;
mfd->dcm_state = DCM_UNINIT;
mdss_fb_parse_dt_split(mfd);
if (mdss_fb_alloc_fbmem(mfd)) {
pr_err("unable to allocate framebuffer memory\n");
return -ENOMEM;
}
mfd->op_enable = true;
mutex_init(&mfd->update.lock);
mutex_init(&mfd->no_update.lock);
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);
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;
init_completion(&mfd->update.comp);
init_completion(&mfd->no_update.comp);
init_completion(&mfd->power_off_comp);
init_completion(&mfd->power_set_comp);
init_waitqueue_head(&mfd->commit_wait_q);
init_waitqueue_head(&mfd->idle_wait_q);
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;
}
pr_info("FrameBuffer[%d] %dx%d size=%d registered successfully!\n",
mfd->index, fbi->var.xres, fbi->var.yres,
fbi->fix.smem_len);
kthread_run(__mdss_fb_display_thread, mfd, "mdss_fb%d", mfd->index);
return 0;
}
static int mdss_fb_open(struct fb_info *info, int user)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct mdss_fb_proc_info *pinfo = NULL;
int result;
int pid = current->tgid;
list_for_each_entry(pinfo, &mfd->proc_list, list) {
if (pinfo->pid == pid)
break;
}
if ((pinfo == NULL) || (pinfo->pid != pid)) {
pinfo = kmalloc(sizeof(*pinfo), GFP_KERNEL);
if (!pinfo) {
pr_err("unable to alloc process info\n");
return -ENOMEM;
}
pinfo->pid = pid;
pinfo->ref_cnt = 0;
list_add(&pinfo->list, &mfd->proc_list);
pr_debug("new process entry pid=%d\n", pinfo->pid);
}
result = pm_runtime_get_sync(info->dev);
if (result < 0)
pr_err("pm_runtime: fail to wake up\n");
if (!mfd->ref_cnt) {
result = mdss_fb_blank_sub(FB_BLANK_UNBLANK, info,
mfd->op_enable);
if (result) {
pm_runtime_put(info->dev);
pr_err("can't turn on fb%d! rc=%d\n", mfd->index,
result);
return result;
}
}
pinfo->ref_cnt++;
mfd->ref_cnt++;
return 0;
}
static int mdss_fb_release(struct fb_info *info, int user)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct mdss_fb_proc_info *pinfo = NULL;
int ret = 0;
int pid = current->tgid;
if (!mfd->ref_cnt) {
pr_info("try to close unopened fb %d!\n", mfd->index);
return -EINVAL;
}
mdss_fb_pan_idle(mfd);
mfd->ref_cnt--;
list_for_each_entry(pinfo, &mfd->proc_list, list) {
if (pinfo->pid == pid)
break;
}
if (!pinfo || (pinfo->pid != pid)) {
pr_warn("unable to find process info for fb%d pid=%d\n",
mfd->index, pid);
if (mfd->mdp.release_fnc) {
ret = mfd->mdp.release_fnc(mfd);
if (ret)
pr_err("error releasing fb%d resources\n",
mfd->index);
}
} else {
pr_debug("found process entry pid=%d ref=%d\n",
pinfo->pid, pinfo->ref_cnt);
pinfo->ref_cnt--;
if (pinfo->ref_cnt == 0) {
if (mfd->mdp.release_fnc) {
ret = mfd->mdp.release_fnc(mfd);
if (ret)
pr_err("error releasing fb%d pid=%d\n",
mfd->index, pinfo->pid);
}
list_del(&pinfo->list);
kfree(pinfo);
}
}
if (!mfd->ref_cnt) {
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info,
mfd->op_enable);
if (ret) {
pr_err("can't turn off fb%d! rc=%d\n", mfd->index, ret);
return ret;
}
}
pm_runtime_put(info->dev);
return ret;
}
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);
}
}
}
void mdss_fb_wait_for_fence(struct msm_sync_pt_data *sync_pt_data)
{
struct sync_fence *fences[MDP_MAX_FENCE_FD];
int fence_cnt;
int i, ret = 0;
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);
/* buf sync */
for (i = 0; i < fence_cnt && !ret; i++) {
ret = sync_fence_wait(fences[i],
WAIT_FENCE_FIRST_TIMEOUT);
if (ret == -ETIME) {
pr_warn("%s: sync_fence_wait timed out! ",
sync_pt_data->fence_name);
pr_cont("Waiting %ld more seconds\n",
WAIT_FENCE_FINAL_TIMEOUT/MSEC_PER_SEC);
ret = sync_fence_wait(fences[i],
WAIT_FENCE_FINAL_TIMEOUT);
}
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]);
}
}
void mdss_fb_signal_timeline(struct msm_sync_pt_data *sync_pt_data)
{
mutex_lock(&sync_pt_data->sync_mutex);
if (atomic_add_unless(&sync_pt_data->commit_cnt, -1, 0) &&
sync_pt_data->timeline &&
!list_empty((const struct list_head *)
(&(sync_pt_data->timeline->obj.active_list_head)))) {
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);
}
mutex_unlock(&sync_pt_data->sync_mutex);
}
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);
}
mutex_unlock(&sync_pt_data->sync_mutex);
}
static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p,
unsigned long event, void *data)
{
struct msm_sync_pt_data *sync_pt_data;
sync_pt_data = container_of(p, struct msm_sync_pt_data, notifier);
switch (event) {
case MDP_NOTIFY_FRAME_READY:
if (sync_pt_data->async_wait_fences)
mdss_fb_wait_for_fence(sync_pt_data);
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;
}
return NOTIFY_OK;
}
static void mdss_fb_pan_idle(struct msm_fb_data_type *mfd)
{
int ret;
ret = wait_event_timeout(mfd->idle_wait_q,
!atomic_read(&mfd->commits_pending),
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT));
if (!ret) {
pr_err("wait for idle timeout %d pending=%d\n",
ret, atomic_read(&mfd->commits_pending));
mdss_fb_signal_timeline(&mfd->mdp_sync_pt_data);
}
}
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;
if ((!mfd->op_enable) || (!mfd->panel_power_on))
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;
mdss_fb_pan_idle(mfd);
mutex_lock(&mfd->mdp_sync_pt_data.sync_mutex);
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;
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);
wake_up_all(&mfd->commit_wait_q);
mutex_unlock(&mfd->mdp_sync_pt_data.sync_mutex);
if (wait_for_finish)
mdss_fb_pan_idle(mfd);
return ret;
}
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct mdp_display_commit disp_commit;
memset(&disp_commit, 0, sizeof(disp_commit));
disp_commit.var = *var;
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;
if ((!mfd->op_enable) || (!mfd->panel_power_on))
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);
return 0;
}
static void mdss_fb_var_to_panelinfo(struct fb_var_screeninfo *var,
struct mdss_panel_info *pinfo)
{
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;
pinfo->clk_rate = var->pixclock;
}
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;
if (!sync_pt_data->async_wait_fences)
mdss_fb_wait_for_fence(sync_pt_data);
sync_pt_data->flushed = false;
if (fb_backup->disp_commit.flags & MDP_DISPLAY_COMMIT_OVERLAY) {
if (mfd->mdp.kickoff_fnc)
ret = mfd->mdp.kickoff_fnc(mfd);
else
pr_warn("no kickoff function setup for fb%d\n",
mfd->index);
} else {
ret = mdss_fb_pan_display_sub(&fb_backup->disp_commit.var,
&fb_backup->info);
if (ret)
pr_err("pan display failed %x on fb%d\n", ret,
mfd->index);
}
mdss_fb_update_backlight(mfd);
if (IS_ERR_VALUE(ret) || !sync_pt_data->flushed)
mdss_fb_signal_timeline(sync_pt_data);
return ret;
}
static int __mdss_fb_display_thread(void *data)
{
struct msm_fb_data_type *mfd = data;
int ret;
struct sched_param param;
param.sched_priority = 16;
ret = sched_setscheduler(current, SCHED_FIFO, &param);
if (ret)
pr_info("%s: set priority failed\n", __func__);
while (1) {
wait_event(mfd->commit_wait_q,
atomic_read(&mfd->commits_pending));
ret = __mdss_fb_perform_commit(mfd);
atomic_dec(&mfd->commits_pending);
wake_up_all(&mfd->idle_wait_q);
}
return ret;
}
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;
if (var->rotate != FB_ROTATE_UR)
return -EINVAL;
if (var->grayscale != info->var.grayscale)
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:
/* Figure out if the user meant RGBA or ARGB
and verify the position of the RGB components */
if (var->transp.offset == 24) {
if ((var->blue.offset != 0) ||
(var->green.offset != 8) ||
(var->red.offset != 16))
return -EINVAL;
} else if (var->transp.offset == 0) {
if ((var->blue.offset != 8) ||
(var->green.offset != 16) ||
(var->red.offset != 24))
return -EINVAL;
} else
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 (info->fix.smem_start) {
u32 len = var->xres_virtual * var->yres_virtual *
(var->bits_per_pixel / 8);
if (len > info->fix.smem_len)
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;
if (mfd->panel_info) {
struct mdss_panel_info panel_info;
int rc;
memcpy(&panel_info, mfd->panel_info, sizeof(panel_info));
mdss_fb_var_to_panelinfo(var, &panel_info);
rc = mdss_fb_send_panel_event(mfd, MDSS_EVENT_CHECK_PARAMS,
&panel_info);
if (IS_ERR_VALUE(rc))
return rc;
mfd->panel_reconfig = rc;
}
return 0;
}
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;
mdss_fb_pan_idle(mfd);
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:
if (var->transp.offset == 24)
mfd->fb_imgType = MDP_ARGB_8888;
else
mfd->fb_imgType = MDP_RGBA_8888;
break;
default:
return -EINVAL;
}
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;
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 0;
}
int mdss_fb_dcm(struct msm_fb_data_type *mfd, int req_state)
{
int ret = -EINVAL;
if (req_state == mfd->dcm_state) {
pr_warn("Already in correct DCM state");
ret = 0;
}
switch (req_state) {
case DCM_UNBLANK:
if (mfd->dcm_state == DCM_UNINIT &&
!mfd->panel_power_on && mfd->mdp.on_fnc) {
ret = mfd->mdp.on_fnc(mfd);
if (ret == 0) {
mfd->panel_power_on = true;
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_on = false;
mfd->dcm_state = DCM_ENTER;
ret = 0;
}
break;
case DCM_EXIT:
if (mfd->dcm_state == DCM_ENTER) {
/* Release the unblank path for exit */
mfd->panel_power_on = true;
mfd->dcm_state = DCM_EXIT;
ret = 0;
}
break;
case DCM_BLANK:
if ((mfd->dcm_state == DCM_EXIT ||
mfd->dcm_state == DCM_UNBLANK) &&
mfd->panel_power_on && mfd->mdp.off_fnc) {
ret = mfd->mdp.off_fnc(mfd);
if (ret == 0) {
mfd->panel_power_on = false;
mfd->dcm_state = DCM_UNINIT;
}
}
break;
}
return ret;
}
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);
}
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;
}
struct sync_fence *mdss_fb_sync_get_fence(struct sw_sync_timeline *timeline,
const char *fence_name, int val)
{
struct sync_pt *sync_pt;
struct sync_fence *fence;
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, fence_cnt = 0, 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;
if ((buf_sync->acq_fen_fd_cnt > MDP_MAX_FENCE_FD) ||
(sync_pt_data->timeline == NULL))
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) {
pr_err("%s: copy_from_user failed", sync_pt_data->fence_name);
return ret;
}
if (sync_pt_data->acq_fen_cnt) {
pr_warn("%s: currently %d fences active. waiting...\n",
sync_pt_data->fence_name,
sync_pt_data->acq_fen_cnt);
mdss_fb_wait_for_fence(sync_pt_data);
}
mutex_lock(&sync_pt_data->sync_mutex);
for (i = 0; i < buf_sync->acq_fen_fd_cnt; i++) {
fence = sync_fence_fdget(acq_fen_fd[i]);
if (fence == NULL) {
pr_err("%s: null fence! i=%d fd=%d\n",
sync_pt_data->fence_name, i,
acq_fen_fd[i]);
ret = -EINVAL;
break;
}
sync_pt_data->acq_fen[i] = fence;
}
fence_cnt = i;
if (ret)
goto buf_sync_err_1;
sync_pt_data->acq_fen_cnt = fence_cnt;
val = sync_pt_data->timeline_value + sync_pt_data->threshold +
atomic_read(&sync_pt_data->commit_cnt);
/* 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;
goto buf_sync_err_1;
}
/* create fd */
rel_fen_fd = get_unused_fd_flags(0);
if (rel_fen_fd < 0) {
pr_err("%s: get_unused_fd_flags failed\n",
sync_pt_data->fence_name);
ret = -EIO;
goto buf_sync_err_2;
}
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 (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\n",
sync_pt_data->fence_name);
ret = -EIO;
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));
if (ret) {
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);
goto buf_sync_err_3;
}
mutex_unlock(&sync_pt_data->sync_mutex);
if (buf_sync->flags & MDP_BUF_SYNC_FLAG_WAIT)
mdss_fb_wait_for_fence(sync_pt_data);
return ret;
buf_sync_err_3:
put_unused_fd(rel_fen_fd);
buf_sync_err_2:
sync_fence_put(rel_fence);
buf_sync_err_1:
for (i = 0; i < fence_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);
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) {
pr_err("%s:copy_from_user failed", __func__);
return ret;
}
ret = mdss_fb_pan_display_ex(info, &disp_commit);
return ret;
}
static int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct msm_fb_data_type *mfd;
void __user *argp = (void __user *)arg;
struct mdp_page_protection fb_page_protection;
int ret = -ENOSYS;
struct mdp_buf_sync buf_sync;
struct msm_sync_pt_data *sync_pt_data = NULL;
if (!info || !info->par)
return -EINVAL;
mfd = (struct msm_fb_data_type *)info->par;
mdss_fb_power_setting_idle(mfd);
if (cmd != MSMFB_VSYNC_CTRL && cmd != MSMFB_OVERLAY_VSYNC_CTRL)
mdss_fb_pan_idle(mfd);
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_GET_PAGE_PROTECTION:
fb_page_protection.page_protection =
mfd->mdp_fb_page_protection;
ret = copy_to_user(argp, &fb_page_protection,
sizeof(fb_page_protection));
if (ret)
return ret;
break;
case MSMFB_BUFFER_SYNC:
ret = copy_from_user(&buf_sync, argp, sizeof(buf_sync));
if (ret)
return ret;
if ((!mfd->op_enable) || (!mfd->panel_power_on))
return -EPERM;
if (mfd->mdp.get_sync_fnc)
sync_pt_data = mfd->mdp.get_sync_fnc(mfd, &buf_sync);
if (!sync_pt_data)
sync_pt_data = &mfd->mdp_sync_pt_data;
ret = mdss_fb_handle_buf_sync_ioctl(sync_pt_data, &buf_sync);
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;
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);
return ret;
}
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;
}
if ((fb_pdata->panel_info.type != MIPI_VIDEO_PANEL) ||
(pdata->panel_info.type != MIPI_VIDEO_PANEL)) {
pr_err("Split panel not supported for panel type %d\n",
pdata->panel_info.type);
return -EINVAL;
}
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;
struct device_node *node;
int rc = 0;
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;
}
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;
}
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;
}
fb_pdev = of_find_device_by_node(node);
if (fb_pdev) {
rc = mdss_fb_register_extra_panel(fb_pdev, pdata);
} else {
pr_info("adding framebuffer device %s\n", dev_name(&pdev->dev));
fb_pdev = of_platform_device_create(node, NULL,
&mdss_pdev->dev);
fb_pdev->dev.platform_data = pdata;
}
if (mdp_instance->panel_register_done)
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) {
pr_err("multiple MDP instance registration");
return -EINVAL;
}
mdp_instance = mdp;
return 0;
}
EXPORT_SYMBOL(mdss_fb_register_mdp_instance);
int mdss_fb_get_phys_info(unsigned long *start, unsigned long *len, int fb_num)
{
struct fb_info *info;
struct msm_fb_data_type *mfd;
if (fb_num > MAX_FBI_LIST)
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);