Skip to content

Instantly share code, notes, and snippets.

@ShadowEO
Created October 2, 2017 18:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ShadowEO/a93c410af34678955e6fd3309331a531 to your computer and use it in GitHub Desktop.
Save ShadowEO/a93c410af34678955e6fd3309331a531 to your computer and use it in GitHub Desktop.
kernel/drivers/video/msm/msm_fb.c - corrected for Weston
/*
* drivers/video/msm/msm_fb.c
*
* Core MSM 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.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/msm_mdp.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <mach/board.h>
#include <linux/uaccess.h>
#include <mach/iommu_domains.h>
#include <linux/workqueue.h>
#include <linux/string.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <linux/debugfs.h>
#include <linux/console.h>
#include <linux/android_pmem.h>
#include <linux/leds.h>
#include <linux/pm_runtime.h>
#include <linux/sync.h>
#include <linux/sw_sync.h>
#include <linux/file.h>
#define MSM_FB_C
#include "msm_fb.h"
#include "mddihosti.h"
#include "tvenc.h"
#include "mdp.h"
#include "mdp4.h"
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
#define MSM_FB_NUM 3
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
#undef CONFIG_HAS_EARLYSUSPEND
#endif
static unsigned char *fbram;
static unsigned char *fbram_phys;
static int fbram_size;
static boolean bf_supported;
/*
* Set backlight on resume after 50 ms after first
* pan display on the panel. This is to avoid panel specific
* transients during resume.
*/
unsigned long backlight_duration = (HZ/20);
static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST];
static int pdev_list_cnt;
int vsync_mode = 1;
#define MAX_BLIT_REQ 256
#define MAX_FBI_LIST 32
static struct fb_info *fbi_list[MAX_FBI_LIST];
static int fbi_list_index;
static struct msm_fb_data_type *mfd_list[MAX_FBI_LIST];
static int mfd_list_index;
static u32 msm_fb_pseudo_palette[16] = {
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
};
static struct ion_client *iclient;
u32 msm_fb_debug_enabled;
/* Setting msm_fb_msg_level to 8 prints out ALL messages */
u32 msm_fb_msg_level = 7;
/* Setting mddi_msg_level to 8 prints out ALL messages */
u32 mddi_msg_level = 5;
extern int32 mdp_block_power_cnt[MDP_MAX_BLOCK];
extern unsigned long mdp_timer_duration;
static int msm_fb_register(struct msm_fb_data_type *mfd);
static int msm_fb_open(struct fb_info *info, int user);
static int msm_fb_release(struct fb_info *info, int user);
static int msm_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info);
static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd);
int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd);
static int msm_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info);
static int msm_fb_set_par(struct fb_info *info);
static int msm_fb_blank_sub(int blank_mode, struct fb_info *info,
boolean op_enable);
static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd);
static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg);
static int msm_fb_mmap(struct fb_info *info, struct vm_area_struct * vma);
static int mdp_bl_scale_config(struct msm_fb_data_type *mfd,
struct mdp_bl_scale_data *data);
static void msm_fb_scale_bl(__u32 bl_max, __u32 *bl_lvl);
static int msm_fb_commit_thread(void *data);
static int msm_fb_pan_idle(struct msm_fb_data_type *mfd);
#ifdef MSM_FB_ENABLE_DBGFS
#define MSM_FB_MAX_DBGFS 1024
#define MAX_BACKLIGHT_BRIGHTNESS 255
#define WAIT_FENCE_FIRST_TIMEOUT 3 * MSEC_PER_SEC
#define WAIT_FENCE_FINAL_TIMEOUT 10 * MSEC_PER_SEC
/* Display op timeout should be greater than total timeout */
#define WAIT_DISP_OP_TIMEOUT (WAIT_FENCE_FIRST_TIMEOUT +\
WAIT_FENCE_FINAL_TIMEOUT) * MDP_MAX_FENCE_FD
#define MAX_TIMELINE_NAME_LEN 16
int msm_fb_debugfs_file_index;
struct dentry *msm_fb_debugfs_root;
struct dentry *msm_fb_debugfs_file[MSM_FB_MAX_DBGFS];
static int bl_scale, bl_min_lvl;
DEFINE_MUTEX(msm_fb_notify_update_sem);
void msmfb_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__);
complete(&mfd->msmfb_no_update_notify);
}
struct dentry *msm_fb_get_debugfs_root(void)
{
if (msm_fb_debugfs_root == NULL)
msm_fb_debugfs_root = debugfs_create_dir("msm_fb", NULL);
return msm_fb_debugfs_root;
}
void msm_fb_debugfs_file_create(struct dentry *root, const char *name,
u32 *var)
{
if (msm_fb_debugfs_file_index >= MSM_FB_MAX_DBGFS)
return;
msm_fb_debugfs_file[msm_fb_debugfs_file_index++] =
debugfs_create_u32(name, S_IRUGO | S_IWUSR, root, var);
}
#endif
int msm_fb_cursor(struct fb_info *info, struct fb_cursor *cursor)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (!mfd->cursor_update)
return -ENODEV;
return mfd->cursor_update(info, cursor);
}
static int msm_fb_resource_initialized;
#ifndef CONFIG_FB_BACKLIGHT
static int lcd_backlight_registered;
static void msm_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;
/* This maps android backlight level 1 to 255 into
driver backlight level bl_min to bl_max with rounding
and maps backlight level 0 to 0. */
if (value <= 0)
bl_lvl = 0;
else if (value >= MAX_BACKLIGHT_BRIGHTNESS)
bl_lvl = mfd->panel_info.bl_max;
else
bl_lvl = mfd->panel_info.bl_min + ((value - 1) * 2 *
(mfd->panel_info.bl_max - mfd->panel_info.bl_min) +
MAX_BACKLIGHT_BRIGHTNESS - 1) /
(MAX_BACKLIGHT_BRIGHTNESS - 1) / 2;
down(&mfd->sem);
msm_fb_set_backlight(mfd, bl_lvl);
up(&mfd->sem);
}
static struct led_classdev backlight_led = {
.name = "lcd-backlight",
.brightness = MAX_BACKLIGHT_BRIGHTNESS,
.brightness_set = msm_fb_set_bl_brightness,
};
#endif
static struct msm_fb_platform_data *msm_fb_pdata;
unsigned char hdmi_prim_display;
unsigned char hdmi_prim_resolution;
int msm_fb_detect_client(const char *name)
{
int ret = 0;
u32 len;
#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT
u32 id;
#endif
if (!msm_fb_pdata)
return -EPERM;
len = strnlen(name, PANEL_NAME_MAX_LEN);
if (strnlen(msm_fb_pdata->prim_panel_name, PANEL_NAME_MAX_LEN)) {
pr_err("\n name = %s, prim_display = %s",
name, msm_fb_pdata->prim_panel_name);
if (!strncmp((char *)msm_fb_pdata->prim_panel_name,
name, len)) {
if (!strncmp((char *)msm_fb_pdata->prim_panel_name,
"hdmi_msm", len))
hdmi_prim_display = 1;
hdmi_prim_resolution =
msm_fb_pdata->ext_resolution;
return 0;
} else {
ret = -EPERM;
}
}
if (strnlen(msm_fb_pdata->ext_panel_name, PANEL_NAME_MAX_LEN)) {
pr_err("\n name = %s, ext_display = %s",
name, msm_fb_pdata->ext_panel_name);
if (!strncmp((char *)msm_fb_pdata->ext_panel_name, name, len))
return 0;
else
ret = -EPERM;
}
if (ret)
return ret;
ret = -EPERM;
if (msm_fb_pdata && msm_fb_pdata->detect_client) {
ret = msm_fb_pdata->detect_client(name);
/* if it's non mddi panel, we need to pre-scan
mddi client to see if we can disable mddi host */
#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT
if (!ret && msm_fb_pdata->mddi_prescan)
id = mddi_get_client_id();
#endif
}
return ret;
}
static ssize_t msm_fb_set_cabc(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 = (struct msm_fb_data_type *)fbi->par;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if (val < 0 || val > 3)
return -EINVAL;
if (pdata->set_cabc)
pdata->set_cabc(mfd->pdev, val);
return count;
}
static ssize_t msm_fb_get_cabc(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;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (pdata->get_cabc)
ret = snprintf(buf, PAGE_SIZE, "%d\n", pdata->get_cabc(mfd->pdev));
return ret;
}
static ssize_t msm_fb_set_sre(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 = (struct msm_fb_data_type *)fbi->par;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if (val < 0 || val > 3)
return -EINVAL;
if (pdata->set_sre)
pdata->set_sre(mfd->pdev, val);
return count;
}
static ssize_t msm_fb_get_sre(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;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (pdata->get_sre)
ret = snprintf(buf, PAGE_SIZE, "%d\n", pdata->get_sre(mfd->pdev));
return ret;
}
static ssize_t msm_fb_set_aco(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 = (struct msm_fb_data_type *)fbi->par;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
unsigned long val;
int ret;
ret = kstrtoul(buf, 2, &val);
if (ret)
return ret;
if (val < 0 || val > 1)
return -EINVAL;
if (pdata->set_aco)
pdata->set_aco(mfd->pdev, val);
return count;
}
static ssize_t msm_fb_get_aco(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;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (pdata->get_aco)
ret = snprintf(buf, PAGE_SIZE, "%d\n", pdata->get_aco(mfd->pdev));
return ret;
}
static ssize_t msm_fb_fps_level_change(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 = (struct msm_fb_data_type *)fbi->par;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
unsigned long val;
int ret;
if (mfd->panel.type != MIPI_VIDEO_PANEL)
return -EINVAL;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if ((val <= 0) || (val > 100))
return -EINVAL;
if (pdata->fps_level_change)
pdata->fps_level_change(mfd->pdev, (u32)val);
return count;
}
static ssize_t msm_fb_msm_fb_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;
struct msm_fb_panel_data *pdata =
(struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
switch (pdata->panel_info.type) {
case NO_PANEL:
ret = snprintf(buf, PAGE_SIZE, "no panel\n");
break;
case MDDI_PANEL:
ret = snprintf(buf, PAGE_SIZE, "mddi panel\n");
break;
case EBI2_PANEL:
ret = snprintf(buf, PAGE_SIZE, "ebi2 panel\n");
break;
case LCDC_PANEL:
ret = snprintf(buf, PAGE_SIZE, "lcdc panel\n");
break;
case EXT_MDDI_PANEL:
ret = snprintf(buf, PAGE_SIZE, "ext mddi panel\n");
break;
case TV_PANEL:
ret = snprintf(buf, PAGE_SIZE, "tv 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;
default:
ret = snprintf(buf, PAGE_SIZE, "unknown panel\n");
break;
}
return ret;
}
static int pcc_r = 32768, pcc_g = 32768, pcc_b = 32768;
static ssize_t mdp_get_rgb(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d %d %d\n", pcc_r, pcc_g, pcc_b);
}
/**
* simple color temperature interface using polynomial color correction
*
* input values are r/g/b adjustments from 0-32768 representing 0 -> 1
*
* example adjustment @ 3500K:
* 1.0000 / 0.5515 / 0.2520 = 32768 / 25828 / 17347
*
* reference chart:
* http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html
*/
static ssize_t mdp_set_rgb(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
uint32_t r = 0, g = 0, b = 0;
struct mdp_pcc_cfg_data pcc_cfg;
if (count > 19)
return -EINVAL;
sscanf(buf, "%d %d %d", &r, &g, &b);
if (r < 0 || r > 32768)
return -EINVAL;
if (g < 0 || g > 32768)
return -EINVAL;
if (b < 0 || b > 32768)
return -EINVAL;
pr_info("%s: r=%d g=%d b=%d", __func__, r, g, b);
memset(&pcc_cfg, 0, sizeof(struct mdp_pcc_cfg_data));
pcc_cfg.block = MDP_BLOCK_DMA_P;
pcc_cfg.ops = MDP_PP_OPS_ENABLE | MDP_PP_OPS_WRITE;
pcc_cfg.r.r = r;
pcc_cfg.g.g = g;
pcc_cfg.b.b = b;
if (mdp4_pcc_cfg(&pcc_cfg) == 0) {
pcc_r = r;
pcc_g = g;
pcc_b = b;
return count;
}
return -EINVAL;
}
static DEVICE_ATTR(msm_fb_type, S_IRUGO, msm_fb_msm_fb_type, NULL);
static DEVICE_ATTR(msm_fb_fps_level, S_IRUGO | S_IWUSR | S_IWGRP, NULL, \
msm_fb_fps_level_change);
static DEVICE_ATTR(rgb, S_IRUGO | S_IWUSR | S_IWGRP, mdp_get_rgb, \
mdp_set_rgb);
static DEVICE_ATTR(cabc, S_IRUGO | S_IWUSR | S_IWGRP, msm_fb_get_cabc, msm_fb_set_cabc);
static DEVICE_ATTR(sre, S_IRUGO | S_IWUSR | S_IWGRP, msm_fb_get_sre, msm_fb_set_sre);
static DEVICE_ATTR(aco, S_IRUGO | S_IWUSR | S_IWGRP, msm_fb_get_aco, msm_fb_set_aco);
static struct attribute *msm_fb_attrs[] = {
&dev_attr_msm_fb_type.attr,
&dev_attr_msm_fb_fps_level.attr,
&dev_attr_rgb.attr,
&dev_attr_cabc.attr,
&dev_attr_sre.attr,
&dev_attr_aco.attr,
NULL,
};
static struct attribute_group msm_fb_attr_group = {
.attrs = msm_fb_attrs,
};
static int msm_fb_create_sysfs(struct platform_device *pdev)
{
int rc;
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
rc = sysfs_create_group(&mfd->fbi->dev->kobj, &msm_fb_attr_group);
if (rc)
MSM_FB_ERR("%s: sysfs group creation failed, rc=%d\n", __func__,
rc);
return rc;
}
static void msm_fb_remove_sysfs(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
sysfs_remove_group(&mfd->fbi->dev->kobj, &msm_fb_attr_group);
}
static void bl_workqueue_handler(struct work_struct *work);
static int msm_fb_probe(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd;
int rc;
int err = 0;
MSM_FB_DEBUG("msm_fb_probe\n");
if ((pdev->id == 0) && (pdev->num_resources > 0)) {
msm_fb_pdata = pdev->dev.platform_data;
fbram_size =
pdev->resource[0].end - pdev->resource[0].start + 1;
fbram_phys = (char *)pdev->resource[0].start;
fbram = __va(fbram_phys);
if (!fbram) {
printk(KERN_ERR "fbram ioremap failed!\n");
return -ENOMEM;
}
MSM_FB_DEBUG("msm_fb_probe: phy_Addr = 0x%x virt = 0x%x\n",
(int)fbram_phys, (int)fbram);
iclient = msm_ion_client_create(-1, pdev->name);
if (IS_ERR_OR_NULL(iclient)) {
pr_err("msm_ion_client_create() return"
" error, val %p\n", iclient);
iclient = NULL;
}
msm_fb_resource_initialized = 1;
return 0;
}
if (!msm_fb_resource_initialized)
return -EPERM;
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
INIT_DELAYED_WORK(&mfd->backlight_worker, bl_workqueue_handler);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST)
return -ENOMEM;
vsync_cntrl.dev = mfd->fbi->dev;
mfd->panel_info.frame_count = 0;
mfd->bl_level = 0;
bl_scale = 1024;
bl_min_lvl = 255;
#ifdef CONFIG_FB_MSM_OVERLAY
mfd->overlay_play_enable = 1;
#endif
bf_supported = mdp4_overlay_borderfill_supported();
rc = msm_fb_register(mfd);
if (rc)
return rc;
err = pm_runtime_set_active(mfd->fbi->dev);
if (err < 0)
printk(KERN_ERR "pm_runtime: fail to set active.\n");
pm_runtime_enable(mfd->fbi->dev);
#ifdef CONFIG_FB_BACKLIGHT
msm_fb_config_backlight(mfd);
#else
/* android supports only one lcd-backlight/lcd for now */
if (!lcd_backlight_registered) {
if (led_classdev_register(&pdev->dev, &backlight_led))
printk(KERN_ERR "led_classdev_register failed\n");
else
lcd_backlight_registered = 1;
}
#endif
pdev_list[pdev_list_cnt++] = pdev;
msm_fb_create_sysfs(pdev);
if (mfd->timeline == NULL) {
char timeline_name[MAX_TIMELINE_NAME_LEN];
snprintf(timeline_name, sizeof(timeline_name),
"mdp_fb_%d", mfd->index);
mfd->timeline = sw_sync_timeline_create(timeline_name);
if (mfd->timeline == NULL) {
pr_err("%s: cannot create time line", __func__);
return -ENOMEM;
} else {
mfd->timeline_value = 0;
}
}
return 0;
}
static int msm_fb_remove(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd;
MSM_FB_DEBUG("msm_fb_remove\n");
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
msm_fb_pan_idle(mfd);
msm_fb_remove_sysfs(pdev);
pm_runtime_disable(mfd->fbi->dev);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
if (msm_fb_suspend_sub(mfd))
printk(KERN_ERR "msm_fb_remove: can't stop the device %d\n", mfd->index);
if (mfd->channel_irq != 0)
free_irq(mfd->channel_irq, (void *)mfd);
if (mfd->vsync_width_boundary)
vfree(mfd->vsync_width_boundary);
if (mfd->vsync_resync_timer.function)
del_timer(&mfd->vsync_resync_timer);
if (mfd->refresh_timer.function)
del_timer(&mfd->refresh_timer);
if (mfd->dma_hrtimer.function)
hrtimer_cancel(&mfd->dma_hrtimer);
if (mfd->msmfb_no_update_notify_timer.function)
del_timer(&mfd->msmfb_no_update_notify_timer);
complete(&mfd->msmfb_no_update_notify);
complete(&mfd->msmfb_update_notify);
kthread_stop(mfd->commit_thread);
/* remove /dev/fb* */
unregister_framebuffer(mfd->fbi);
#ifdef CONFIG_FB_BACKLIGHT
/* remove /sys/class/backlight */
backlight_device_unregister(mfd->fbi->bl_dev);
#else
if (lcd_backlight_registered) {
lcd_backlight_registered = 0;
led_classdev_unregister(&backlight_led);
}
#endif
#ifdef MSM_FB_ENABLE_DBGFS
if (mfd->sub_dir)
debugfs_remove(mfd->sub_dir);
#endif
return 0;
}
#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND)
static int msm_fb_suspend(struct platform_device *pdev, pm_message_t state)
{
struct msm_fb_data_type *mfd;
int ret = 0;
MSM_FB_DEBUG("msm_fb_suspend\n");
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
msm_fb_pan_idle(mfd);
console_lock();
fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED);
ret = msm_fb_suspend_sub(mfd);
if (ret != 0) {
printk(KERN_ERR "msm_fb: failed to suspend! %d\n", ret);
fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING);
} else {
pdev->dev.power.power_state = state;
}
console_unlock();
return ret;
}
#else
#define msm_fb_suspend NULL
#endif
static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd)
{
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
/*
* suspend this channel
*/
mfd->suspend.sw_refreshing_enable = mfd->sw_refreshing_enable;
mfd->suspend.op_enable = mfd->op_enable;
/*
* For HDMI/DTV, panel needs not to be turned ON during resume
* as power_ctrl will turn ON the HPD at resume which will turn
* ON the panel in case the HDMI cable is still connected.
*/
if (mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL)
mfd->suspend.panel_power_state = MDP_PANEL_POWER_OFF;
else
mfd->suspend.panel_power_state = mfd->panel_power_state;
mfd->suspend.op_suspend = true;
if (mfd->op_enable) {
ret =
msm_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi,
mfd->suspend.op_enable);
if (ret) {
MSM_FB_INFO
("msm_fb_suspend: can't turn off display!\n");
return ret;
}
mfd->op_enable = FALSE;
}
/*
* try to power down
*/
mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
/*
* detach display channel irq if there's any
* or wait until vsync-resync completes
*/
if ((mfd->dest == DISPLAY_LCD)) {
if (mfd->panel_info.lcd.vsync_enable) {
if (mfd->panel_info.lcd.hw_vsync_mode) {
if (mfd->channel_irq != 0)
disable_irq(mfd->channel_irq);
} else {
volatile boolean vh_pending;
do {
vh_pending = mfd->vsync_handler_pending;
} while (vh_pending);
}
}
}
return 0;
}
#ifdef CONFIG_PM
static int msm_fb_resume_sub(struct msm_fb_data_type *mfd)
{
int ret = 0;
struct msm_fb_panel_data *pdata = NULL;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
/* attach display channel irq if there's any */
if (mfd->channel_irq != 0)
enable_irq(mfd->channel_irq);
/* resume state var recover */
mfd->sw_refreshing_enable = mfd->suspend.sw_refreshing_enable;
mfd->op_enable = mfd->suspend.op_enable;
if (mdp_panel_is_power_on(mfd->suspend.panel_power_state)) {
if (mfd->panel_driver_on == FALSE)
msm_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi,
mfd->op_enable);
ret =
msm_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi,
mfd->op_enable);
if (ret)
MSM_FB_INFO("msm_fb_resume: can't turn on display!\n");
}
mfd->suspend.op_suspend = false;
return ret;
}
#endif
#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND)
static int msm_fb_resume(struct platform_device *pdev)
{
/* This resume function is called when interrupt is enabled.
*/
int ret = 0;
struct msm_fb_data_type *mfd;
MSM_FB_DEBUG("msm_fb_resume\n");
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
msm_fb_pan_idle(mfd);
console_lock();
ret = msm_fb_resume_sub(mfd);
pdev->dev.power.power_state = PMSG_ON;
fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING);
console_unlock();
return ret;
}
#else
#define msm_fb_resume NULL
#endif
static int msm_fb_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "pm_runtime: suspending...\n");
return 0;
}
static int msm_fb_runtime_resume(struct device *dev)
{
dev_dbg(dev, "pm_runtime: resuming...\n");
return 0;
}
static int msm_fb_runtime_idle(struct device *dev)
{
dev_dbg(dev, "pm_runtime: idling...\n");
return 0;
}
#if (defined(CONFIG_SUSPEND) && defined(CONFIG_FB_MSM_HDMI_MSM_PANEL))
static int msm_fb_ext_suspend(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
struct msm_fb_panel_data *pdata = NULL;
int ret = 0;
if (hdmi_prim_display) {
MSM_FB_INFO("%s: hdmi primary handles early suspend only\n",
__func__);
return 0;
}
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
msm_fb_pan_idle(mfd);
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL) {
ret = msm_fb_suspend_sub(mfd);
/* Turn off the HPD circuitry */
if (pdata->power_ctrl) {
MSM_FB_INFO("%s: Turning off HPD circuitry\n",
__func__);
pdata->power_ctrl(FALSE);
}
}
return ret;
}
static int msm_fb_ext_resume(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
struct msm_fb_panel_data *pdata = NULL;
int ret = 0;
if (hdmi_prim_display) {
MSM_FB_INFO("%s: hdmi primary handles early resume only\n",
__func__);
return 0;
}
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
msm_fb_pan_idle(mfd);
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL) {
/* Turn on the HPD circuitry */
if (pdata->power_ctrl) {
pdata->power_ctrl(TRUE);
MSM_FB_INFO("%s: Turning on HPD circuitry\n",
__func__);
}
ret = msm_fb_resume_sub(mfd);
}
return ret;
}
#endif
static struct dev_pm_ops msm_fb_dev_pm_ops = {
.runtime_suspend = msm_fb_runtime_suspend,
.runtime_resume = msm_fb_runtime_resume,
.runtime_idle = msm_fb_runtime_idle,
#if (defined(CONFIG_SUSPEND) && defined(CONFIG_FB_MSM_HDMI_MSM_PANEL) && \
!defined(CONFIG_FB_MSM_HDMI_AS_PRIMARY))
.suspend = msm_fb_ext_suspend,
.resume = msm_fb_ext_resume,
#endif
};
static struct platform_driver msm_fb_driver = {
.probe = msm_fb_probe,
.remove = msm_fb_remove,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = msm_fb_suspend,
.resume = msm_fb_resume,
#endif
.shutdown = NULL,
.driver = {
/* Driver name must match the device name added in platform.c. */
.name = "msm_fb",
.pm = &msm_fb_dev_pm_ops,
},
};
#if defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_FB_MSM_MDP303)
static void memset32_io(u32 __iomem *_ptr, u32 val, size_t count)
{
count >>= 2;
while (count--)
writel(val, _ptr++);
}
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
static void msmfb_early_suspend(struct early_suspend *h)
{
struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type,
early_suspend);
struct msm_fb_panel_data *pdata = NULL;
msm_fb_pan_idle(mfd);
#if defined(CONFIG_FB_MSM_MDP303)
/*
* For MDP with overlay, set framebuffer with black pixels
* to show black screen on HDMI.
*/
struct fb_info *fbi = mfd->fbi;
switch (mfd->fbi->var.bits_per_pixel) {
case 32:
memset32_io((void *)fbi->screen_base, 0xFF000000,
fbi->fix.smem_len);
break;
default:
memset32_io((void *)fbi->screen_base, 0x00, fbi->fix.smem_len);
break;
}
#endif
msm_fb_suspend_sub(mfd);
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (hdmi_prim_display &&
(mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL)) {
/* Turn off the HPD circuitry */
if (pdata->power_ctrl) {
MSM_FB_INFO("%s: Turning off HPD circuitry\n",
__func__);
pdata->power_ctrl(FALSE);
}
}
}
static void msmfb_early_resume(struct early_suspend *h)
{
struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type,
early_suspend);
struct msm_fb_panel_data *pdata = NULL;
msm_fb_pan_idle(mfd);
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if (hdmi_prim_display &&
(mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL)) {
/* Turn on the HPD circuitry */
if (pdata->power_ctrl) {
MSM_FB_INFO("%s: Turning on HPD circuitry\n", __func__);
pdata->power_ctrl(TRUE);
}
}
msm_fb_resume_sub(mfd);
}
#endif
static int unset_bl_level, bl_updated;
#if defined(CONFIG_BACKLIGHT_LM3530)
static int bl_level_old = 0x2A;
#else
static int bl_level_old;
#endif
static int mdp_bl_scale_config(struct msm_fb_data_type *mfd,
struct mdp_bl_scale_data *data)
{
int ret = 0;
int curr_bl;
down(&mfd->sem);
curr_bl = mfd->bl_level;
bl_scale = data->scale;
bl_min_lvl = data->min_lvl;
pr_debug("%s: update scale = %d, min_lvl = %d\n", __func__, bl_scale,
bl_min_lvl);
/* update current backlight to use new scaling*/
if (!mdp_fb_is_power_off(mfd) && bl_updated)
msm_fb_set_backlight(mfd, curr_bl);
up(&mfd->sem);
return ret;
}
static void msm_fb_scale_bl(__u32 bl_max, __u32 *bl_lvl)
{
__u32 temp = *bl_lvl;
pr_debug("%s: input = %d, scale = %d", __func__, temp, bl_scale);
if (temp >= bl_min_lvl) {
/* checking if temp is below bl_max else capping */
if (temp > bl_max) {
pr_warn("%s: invalid bl level\n", __func__);
temp = bl_max;
}
/* checking if bl_scale is below 1024 else capping */
if (bl_scale > 1024) {
pr_warn("%s: invalid bl scale\n", __func__);
bl_scale = 1024;
}
/* bl_scale is the numerator of scaling fraction (x/1024)*/
temp = (temp * bl_scale) / 1024;
/*if less than minimum level, use min level*/
if (temp < bl_min_lvl)
temp = bl_min_lvl;
}
pr_debug("%s: output = %d", __func__, temp);
(*bl_lvl) = temp;
}
/*must call this function from within mfd->sem*/
void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl)
{
struct msm_fb_panel_data *pdata;
__u32 temp = bkl_lvl;
unset_bl_level = bkl_lvl;
if (mdp_fb_is_power_off(mfd) || !bl_updated) {
return;
}
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if ((pdata) && (pdata->set_backlight)) {
msm_fb_scale_bl(mfd->panel_info.bl_max, &temp);
if (bl_level_old == temp) {
return;
}
mfd->bl_level = temp;
pdata->set_backlight(mfd);
mfd->bl_level = bkl_lvl;
bl_level_old = temp;
}
}
static int msm_fb_blank_sub(int blank_mode, struct fb_info *info,
boolean op_enable)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct msm_fb_panel_data *pdata = NULL;
int ret = 0;
int cur_power_state, req_power_state = MDP_PANEL_POWER_OFF;
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if ((!pdata) || (!pdata->on) || (!pdata->off)) {
printk(KERN_ERR "msm_fb_blank_sub: no panel operation detected!\n");
return -ENODEV;
}
cur_power_state = mfd->panel_power_state;
switch (blank_mode) {
case FB_BLANK_UNBLANK:
if (mdp_fb_is_power_off(mfd)) {
ret = pdata->on(mfd->pdev);
if (ret == 0) {
down(&mfd->sem);
mfd->panel_power_state = MDP_PANEL_POWER_ON;
up(&mfd->sem);
mfd->panel_driver_on = mfd->op_enable;
}
}
break;
case FB_BLANK_VSYNC_SUSPEND:
req_power_state = MDP_PANEL_POWER_DOZE;
if (mdp_fb_is_power_off(mfd)) {
ret = pdata->on(mfd->pdev);
if (ret == 0) {
down(&mfd->sem);
mfd->panel_power_state = MDP_PANEL_POWER_ON;
up(&mfd->sem);
mfd->panel_driver_on = mfd->op_enable;
}
}
/* Continue to POWERDOWN to doze if no errors */
if (ret)
break;
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
case FB_BLANK_POWERDOWN:
default:
if (mdp_fb_is_power_on(mfd)) {
cur_power_state = mfd->panel_power_state;
mfd->op_enable = FALSE;
down(&mfd->sem);
mfd->panel_power_state = req_power_state;
bl_updated = 0;
up(&mfd->sem);
cancel_delayed_work_sync(&mfd->backlight_worker);
if (mfd->msmfb_no_update_notify_timer.function)
del_timer(&mfd->msmfb_no_update_notify_timer);
complete(&mfd->msmfb_no_update_notify);
/* clean fb to prevent displaying old fb */
memset((void *)info->screen_base, 0,
info->fix.smem_len);
ret = pdata->off(mfd->pdev);
if (ret)
mfd->panel_power_state = cur_power_state;
msm_fb_release_timeline(mfd);
mfd->op_enable = TRUE;
}
break;
}
return ret;
}
int calc_fb_offset(struct msm_fb_data_type *mfd, struct fb_info *fbi, int bpp)
{
struct msm_panel_info *panel_info = &mfd->panel_info;
int remainder, yres, offset;
if (panel_info->mode2_yres != 0) {
yres = panel_info->mode2_yres;
remainder = (fbi->fix.line_length*yres) & (PAGE_SIZE - 1);
} else {
yres = panel_info->yres;
remainder = (fbi->fix.line_length*yres) & (PAGE_SIZE - 1);
}
if (!remainder)
remainder = PAGE_SIZE;
if (fbi->var.yoffset < yres) {
offset = (fbi->var.xoffset * bpp);
/* iBuf->buf += fbi->var.xoffset * bpp + 0 *
yres * fbi->fix.line_length; */
} else if (fbi->var.yoffset >= yres && fbi->var.yoffset < 2 * yres) {
offset = (fbi->var.xoffset * bpp + yres *
fbi->fix.line_length + PAGE_SIZE - remainder);
} else {
offset = (fbi->var.xoffset * bpp + 2 * yres *
fbi->fix.line_length + 2 * (PAGE_SIZE - remainder));
}
return offset;
}
static void msm_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
msm_fb_pan_idle(mfd);
cfb_fillrect(info, rect);
if (!mfd->hw_refresh && (info->var.yoffset == 0) &&
!mfd->sw_currently_refreshing) {
struct fb_var_screeninfo var;
var = info->var;
var.reserved[0] = 0x54445055;
var.reserved[1] = (rect->dy << 16) | (rect->dx);
var.reserved[2] = ((rect->dy + rect->height) << 16) |
(rect->dx + rect->width);
msm_fb_pan_display(&var, info);
}
}
static void msm_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
msm_fb_pan_idle(mfd);
cfb_copyarea(info, area);
if (!mfd->hw_refresh && (info->var.yoffset == 0) &&
!mfd->sw_currently_refreshing) {
struct fb_var_screeninfo var;
var = info->var;
var.reserved[0] = 0x54445055;
var.reserved[1] = (area->dy << 16) | (area->dx);
var.reserved[2] = ((area->dy + area->height) << 16) |
(area->dx + area->width);
msm_fb_pan_display(&var, info);
}
}
static void msm_fb_imageblit(struct fb_info *info, const struct fb_image *image)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
msm_fb_pan_idle(mfd);
cfb_imageblit(info, image);
if (!mfd->hw_refresh && (info->var.yoffset == 0) &&
!mfd->sw_currently_refreshing) {
struct fb_var_screeninfo var;
var = info->var;
var.reserved[0] = 0x54445055;
var.reserved[1] = (image->dy << 16) | (image->dx);
var.reserved[2] = ((image->dy + image->height) << 16) |
(image->dx + image->width);
msm_fb_pan_display(&var, info);
}
}
static int msm_fb_blank(int blank_mode, struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (blank_mode == FB_BLANK_POWERDOWN) {
struct fb_event event;
event.info = info;
event.data = &blank_mode;
fb_notifier_call_chain(FB_EVENT_BLANK, &event);
}
msm_fb_pan_idle(mfd);
if (mfd->op_enable == 0) {
if (blank_mode == FB_BLANK_UNBLANK) {
mfd->suspend.panel_power_state = MDP_PANEL_POWER_ON;
/* if unblank is called when system is in suspend,
wait for the system to resume */
while (mfd->suspend.op_suspend) {
pr_debug("waiting for system to resume\n");
msleep(20);
}
} else if (blank_mode == FB_BLANK_VSYNC_SUSPEND) {
mfd->suspend.panel_power_state = MDP_PANEL_POWER_DOZE;
/* if unblank is called when system is in suspend,
wait for the system to resume */
while (mfd->suspend.op_suspend) {
pr_debug("waiting for system to resume\n");
msleep(20);
}
} else
mfd->suspend.panel_power_state = MDP_PANEL_POWER_OFF;
}
return msm_fb_blank_sub(blank_mode, info, mfd->op_enable);
}
static int msm_fb_set_lut(struct fb_cmap *cmap, struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (!mfd->lut_update)
return -ENODEV;
mfd->lut_update(info, cmap);
return 0;
}
/*
* Custom Framebuffer mmap() function for MSM driver.
* Differs from standard mmap() function by allowing for customized
* page-protection.
*/
static int msm_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)
return -EINVAL;
if ((vma->vm_end <= vma->vm_start) ||
(off >= len) ||
((vma->vm_end - vma->vm_start) > (len - off)))
return -EINVAL;
msm_fb_pan_idle(mfd);
/* Set VM flags. */
start &= PAGE_MASK;
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 msm_fb_ops = {
.owner = THIS_MODULE,
.fb_open = msm_fb_open,
.fb_release = msm_fb_release,
.fb_read = NULL,
.fb_write = NULL,
.fb_cursor = NULL,
.fb_check_var = msm_fb_check_var, /* vinfo check */
.fb_set_par = msm_fb_set_par, /* set the video mode according to info->var */
.fb_setcolreg = NULL, /* set color register */
.fb_blank = msm_fb_blank, /* blank display */
.fb_pan_display = msm_fb_pan_display, /* pan display */
.fb_fillrect = msm_fb_fillrect, /* Draws a rectangle */
.fb_copyarea = msm_fb_copyarea, /* Copy data from area to another */
.fb_imageblit = msm_fb_imageblit, /* Draws a image to the display */
.fb_rotate = NULL,
.fb_sync = NULL, /* wait for blit idle, optional */
.fb_ioctl = msm_fb_ioctl, /* perform fb specific ioctl (optional) */
.fb_mmap = msm_fb_mmap,
};
static __u32 msm_fb_line_length(__u32 fb_index, __u32 xres, int bpp)
{
/* The adreno GPU hardware requires that the pitch be aligned to
32 pixels for color buffers, so for the cases where the GPU
is writing directly to fb0, the framebuffer pitch
also needs to be 32 pixel aligned */
if (fb_index == 0)
return ALIGN(xres, 32) * bpp;
else
return xres * bpp;
}
static int msm_fb_register(struct msm_fb_data_type *mfd)
{
int ret = -ENODEV;
int bpp;
struct msm_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;
int fbram_offset;
int remainder, remainder_mode2;
/*
* 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:
/* ToDo: need to check TV-Out YUV422i framebuffer format */
/* we might need to create new type define */
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:
MSM_FB_ERR("msm_fb_init: fb %d unkown image type!\n",
mfd->index);
return ret;
}
fix->type = panel_info->is_3d_panel;
fix->line_length = msm_fb_line_length(mfd->index, panel_info->xres,
bpp);
/* Make sure all buffers can be addressed on a page boundary by an x
* and y offset */
remainder = (fix->line_length * panel_info->yres) & (PAGE_SIZE - 1);
/* PAGE_SIZE is a power of 2 */
if (!remainder)
remainder = PAGE_SIZE;
remainder_mode2 = (fix->line_length *
panel_info->mode2_yres) & (PAGE_SIZE - 1);
if (!remainder_mode2)
remainder_mode2 = PAGE_SIZE;
/*
* calculate smem_len based on max size of two supplied modes.
* Only fb0 has mem. fb1 and fb2 don't have mem.
*/
if (!bf_supported || mfd->index == 0)
fix->smem_len = MAX((msm_fb_line_length(mfd->index,
panel_info->xres,
bpp) *
panel_info->yres + PAGE_SIZE -
remainder) * mfd->fb_page,
(msm_fb_line_length(mfd->index,
panel_info->mode2_xres,
bpp) *
panel_info->mode2_yres + PAGE_SIZE -
remainder_mode2) * mfd->fb_page);
else if (mfd->index == 1 || mfd->index == 2) {
pr_debug("%s:%d no memory is allocated for fb%d!\n",
__func__, __LINE__, mfd->index);
fix->smem_len = 0;
}
mfd->var_xres = panel_info->xres;
mfd->var_yres = panel_info->yres;
mfd->var_frame_rate = panel_info->frame_rate;
var->pixclock = 100000 / 60;
mfd->var_pixclock = var->pixclock;
var->xres = panel_info->xres;
var->yres = panel_info->yres;
var->width = panel_info->width;
var->height = panel_info->height;
var->xres_virtual = panel_info->xres;
var->yres_virtual = panel_info->yres * mfd->fb_page +
((PAGE_SIZE - remainder)/fix->line_length) * mfd->fb_page;
var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */
var->reserved[3] = mdp_get_panel_framerate(mfd);
/*
* id field for fb app
*/
id = (int *)&mfd->panel;
switch (mdp_rev) {
case MDP_REV_20:
snprintf(fix->id, sizeof(fix->id), "msmfb20_%x", (__u32) *id);
break;
case MDP_REV_22:
snprintf(fix->id, sizeof(fix->id), "msmfb22_%x", (__u32) *id);
break;
case MDP_REV_30:
snprintf(fix->id, sizeof(fix->id), "msmfb30_%x", (__u32) *id);
break;
case MDP_REV_303:
snprintf(fix->id, sizeof(fix->id), "msmfb303_%x", (__u32) *id);
break;
case MDP_REV_31:
snprintf(fix->id, sizeof(fix->id), "msmfb31_%x", (__u32) *id);
break;
case MDP_REV_40:
snprintf(fix->id, sizeof(fix->id), "msmfb40_%x", (__u32) *id);
break;
case MDP_REV_41:
snprintf(fix->id, sizeof(fix->id), "msmfb41_%x", (__u32) *id);
break;
case MDP_REV_42:
snprintf(fix->id, sizeof(fix->id), "msmfb42_%x", (__u32) *id);
break;
case MDP_REV_43:
snprintf(fix->id, sizeof(fix->id), "msmfb43_%x", (__u32) *id);
break;
case MDP_REV_44:
snprintf(fix->id, sizeof(fix->id), "msmfb44_%x", (__u32) *id);
break;
default:
snprintf(fix->id, sizeof(fix->id), "msmfb0_%x", (__u32) *id);
break;
}
fbi->fbops = &msm_fb_ops;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = msm_fb_pseudo_palette;
mfd->ref_cnt = 0;
mfd->sw_currently_refreshing = FALSE;
mfd->sw_refreshing_enable = TRUE;
mfd->panel_power_state = MDP_PANEL_POWER_OFF;
mfd->pan_waiting = FALSE;
init_completion(&mfd->pan_comp);
init_completion(&mfd->refresher_comp);
sema_init(&mfd->sem, 1);
init_timer(&mfd->msmfb_no_update_notify_timer);
mfd->msmfb_no_update_notify_timer.function =
msmfb_no_update_notify_timer_cb;
mfd->msmfb_no_update_notify_timer.data = (unsigned long)mfd;
init_completion(&mfd->msmfb_update_notify);
init_completion(&mfd->msmfb_no_update_notify);
init_completion(&mfd->commit_comp);
mutex_init(&mfd->sync_mutex);
mutex_init(&mfd->queue_mutex);
init_waitqueue_head(&mfd->commit_queue);
mfd->commit_thread = kthread_run(msm_fb_commit_thread, mfd,
"msmfb_commit_thread");
mfd->msm_fb_backup = kzalloc(sizeof(struct msm_fb_backup_type),
GFP_KERNEL);
if (mfd->msm_fb_backup == 0) {
pr_err("error: not enough memory!\n");
return -ENOMEM;
}
fbram_offset = PAGE_ALIGN((int)fbram)-(int)fbram;
fbram += fbram_offset;
fbram_phys += fbram_offset;
fbram_size -= fbram_offset;
if (!bf_supported || mfd->index == 0)
if (fbram_size < fix->smem_len) {
pr_err("error: no more framebuffer memory!\n");
return -ENOMEM;
}
fbi->screen_base = fbram;
fbi->fix.smem_start = (unsigned long)fbram_phys;
msm_iommu_map_contig_buffer(fbi->fix.smem_start,
DISPLAY_WRITE_DOMAIN,
GEN_POOL,
fbi->fix.smem_len,
SZ_4K,
0,
&(mfd->display_iova));
msm_iommu_map_contig_buffer(fbi->fix.smem_start,
DISPLAY_READ_DOMAIN,
GEN_POOL,
fbi->fix.smem_len,
SZ_4K,
0,
&(mfd->display_iova));
msm_iommu_map_contig_buffer(fbi->fix.smem_start,
ROTATOR_SRC_DOMAIN,
GEN_POOL,
fbi->fix.smem_len,
SZ_4K,
0,
&(mfd->rotator_iova));
if (!bf_supported || mfd->index == 0)
memset(fbi->screen_base, 0x0, fix->smem_len);
mfd->op_enable = TRUE;
mfd->panel_power_state = MDP_PANEL_POWER_OFF;
/* cursor memory allocation */
if (mfd->cursor_update) {
unsigned long cursor_buf_iommu = 0;
mfd->cursor_buf = dma_alloc_coherent(NULL,
MDP_CURSOR_SIZE,
(dma_addr_t *) &mfd->cursor_buf_phys,
GFP_KERNEL);
msm_iommu_map_contig_buffer((unsigned long)mfd->cursor_buf_phys,
DISPLAY_READ_DOMAIN,
GEN_POOL,
MDP_CURSOR_SIZE,
SZ_4K,
0,
&cursor_buf_iommu);
if (cursor_buf_iommu)
mfd->cursor_buf_phys = (void *)cursor_buf_iommu;
if (!mfd->cursor_buf)
mfd->cursor_update = 0;
}
if (mfd->lut_update) {
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
if (ret)
printk(KERN_ERR "%s: fb_alloc_cmap() failed!\n",
__func__);
}
if (register_framebuffer(fbi) < 0) {
if (mfd->lut_update)
fb_dealloc_cmap(&fbi->cmap);
if (mfd->cursor_buf)
dma_free_coherent(NULL,
MDP_CURSOR_SIZE,
mfd->cursor_buf,
(dma_addr_t) mfd->cursor_buf_phys);
mfd->op_enable = FALSE;
return -EPERM;
}
fbram += fix->smem_len;
fbram_phys += fix->smem_len;
fbram_size -= fix->smem_len;
MSM_FB_INFO
("FrameBuffer[%d] %dx%d size=%d bytes is registered successfully!\n",
mfd->index, fbi->var.xres, fbi->var.yres, fbi->fix.smem_len);
#ifdef CONFIG_FB_MSM_LOGO
/* Flip buffer */
if (!load_565rle_image(INIT_IMAGE_FILE, bf_supported))
;
#endif
ret = 0;
#ifdef CONFIG_HAS_EARLYSUSPEND
if (hdmi_prim_display ||
(mfd->panel_info.type != DTV_PANEL)) {
mfd->early_suspend.suspend = msmfb_early_suspend;
mfd->early_suspend.resume = msmfb_early_resume;
mfd->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 2;
register_early_suspend(&mfd->early_suspend);
}
#endif
#ifdef MSM_FB_ENABLE_DBGFS
{
struct dentry *root;
struct dentry *sub_dir;
char sub_name[2];
root = msm_fb_get_debugfs_root();
if (root != NULL) {
sub_name[0] = (char)(mfd->index + 0x30);
sub_name[1] = '\0';
sub_dir = debugfs_create_dir(sub_name, root);
} else {
sub_dir = NULL;
}
mfd->sub_dir = sub_dir;
if (sub_dir) {
msm_fb_debugfs_file_create(sub_dir, "op_enable",
(u32 *) &mfd->op_enable);
msm_fb_debugfs_file_create(sub_dir, "panel_power_state",
(u32 *) &mfd->
panel_power_state);
msm_fb_debugfs_file_create(sub_dir, "ref_cnt",
(u32 *) &mfd->ref_cnt);
msm_fb_debugfs_file_create(sub_dir, "fb_imgType",
(u32 *) &mfd->fb_imgType);
msm_fb_debugfs_file_create(sub_dir,
"sw_currently_refreshing",
(u32 *) &mfd->
sw_currently_refreshing);
msm_fb_debugfs_file_create(sub_dir,
"sw_refreshing_enable",
(u32 *) &mfd->
sw_refreshing_enable);
msm_fb_debugfs_file_create(sub_dir, "xres",
(u32 *) &mfd->panel_info.
xres);
msm_fb_debugfs_file_create(sub_dir, "yres",
(u32 *) &mfd->panel_info.
yres);
msm_fb_debugfs_file_create(sub_dir, "bpp",
(u32 *) &mfd->panel_info.
bpp);
msm_fb_debugfs_file_create(sub_dir, "type",
(u32 *) &mfd->panel_info.
type);
msm_fb_debugfs_file_create(sub_dir, "wait_cycle",
(u32 *) &mfd->panel_info.
wait_cycle);
msm_fb_debugfs_file_create(sub_dir, "pdest",
(u32 *) &mfd->panel_info.
pdest);
msm_fb_debugfs_file_create(sub_dir, "backbuff",
(u32 *) &mfd->panel_info.
fb_num);
msm_fb_debugfs_file_create(sub_dir, "clk_rate",
(u32 *) &mfd->panel_info.
clk_rate);
msm_fb_debugfs_file_create(sub_dir, "frame_count",
(u32 *) &mfd->panel_info.
frame_count);
switch (mfd->dest) {
case DISPLAY_LCD:
msm_fb_debugfs_file_create(sub_dir,
"vsync_enable",
(u32 *)&mfd->panel_info.lcd.vsync_enable);
msm_fb_debugfs_file_create(sub_dir,
"refx100",
(u32 *) &mfd->panel_info.lcd. refx100);
msm_fb_debugfs_file_create(sub_dir,
"v_back_porch",
(u32 *) &mfd->panel_info.lcd.v_back_porch);
msm_fb_debugfs_file_create(sub_dir,
"v_front_porch",
(u32 *) &mfd->panel_info.lcd.v_front_porch);
msm_fb_debugfs_file_create(sub_dir,
"v_pulse_width",
(u32 *) &mfd->panel_info.lcd.v_pulse_width);
msm_fb_debugfs_file_create(sub_dir,
"hw_vsync_mode",
(u32 *) &mfd->panel_info.lcd.hw_vsync_mode);
msm_fb_debugfs_file_create(sub_dir,
"vsync_notifier_period", (u32 *)
&mfd->panel_info.lcd.vsync_notifier_period);
break;
case DISPLAY_LCDC:
msm_fb_debugfs_file_create(sub_dir,
"h_back_porch",
(u32 *) &mfd->panel_info.lcdc.h_back_porch);
msm_fb_debugfs_file_create(sub_dir,
"h_front_porch",
(u32 *) &mfd->panel_info.lcdc.h_front_porch);
msm_fb_debugfs_file_create(sub_dir,
"h_pulse_width",
(u32 *) &mfd->panel_info.lcdc.h_pulse_width);
msm_fb_debugfs_file_create(sub_dir,
"v_back_porch",
(u32 *) &mfd->panel_info.lcdc.v_back_porch);
msm_fb_debugfs_file_create(sub_dir,
"v_front_porch",
(u32 *) &mfd->panel_info.lcdc.v_front_porch);
msm_fb_debugfs_file_create(sub_dir,
"v_pulse_width",
(u32 *) &mfd->panel_info.lcdc.v_pulse_width);
msm_fb_debugfs_file_create(sub_dir,
"border_clr",
(u32 *) &mfd->panel_info.lcdc.border_clr);
msm_fb_debugfs_file_create(sub_dir,
"underflow_clr",
(u32 *) &mfd->panel_info.lcdc.underflow_clr);
msm_fb_debugfs_file_create(sub_dir,
"hsync_skew",
(u32 *) &mfd->panel_info.lcdc.hsync_skew);
break;
default:
break;
}
}
}
#endif /* MSM_FB_ENABLE_DBGFS */
return ret;
}
static int msm_fb_open(struct fb_info *info, int user)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
bool unblank = true;
int result;
result = pm_runtime_get_sync(info->dev);
if (result < 0) {
printk(KERN_ERR "pm_runtime: fail to wake up\n");
}
if (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */
mfd->ref_cnt++;
return 0;
}
if (!mfd->ref_cnt) {
if (!bf_supported ||
(info->node != 1 && info->node != 2))
mdp_set_dma_pan_info(info, NULL, TRUE);
else
pr_debug("%s:%d no mdp_set_dma_pan_info %d\n",
__func__, __LINE__, info->node);
if (mfd->is_panel_ready && !mfd->is_panel_ready())
unblank = false;
if (unblank && (mfd->panel_info.type != DTV_PANEL)) {
if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, TRUE)) {
pr_err("msm_fb_open: can't turn on display\n");
return -EINVAL;
}
}
}
mfd->ref_cnt++;
return 0;
}
static void msm_fb_free_base_pipe(struct msm_fb_data_type *mfd)
{
return mdp4_overlay_free_base_pipe(mfd);
}
static int msm_fb_release(struct fb_info *info, int user)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int ret = 0;
if (!mfd->ref_cnt) {
MSM_FB_INFO("msm_fb_release: try to close unopened fb %d!\n",
mfd->index);
return -EINVAL;
}
msm_fb_pan_idle(mfd);
mfd->ref_cnt--;
if (!mfd->ref_cnt) {
if (mfd->op_enable) {
ret = msm_fb_blank_sub(FB_BLANK_POWERDOWN, info,
mfd->op_enable);
if (ret != 0) {
printk(KERN_ERR "msm_fb_release: can't turn off display!\n");
return ret;
}
} else {
msm_fb_free_base_pipe(mfd);
}
}
pm_runtime_put(info->dev);
return ret;
}
void msm_fb_wait_for_fence(struct msm_fb_data_type *mfd)
{
int i, ret = 0;
/* buf sync */
for (i = 0; i < mfd->acq_fen_cnt; i++) {
ret = sync_fence_wait(mfd->acq_fen[i],
WAIT_FENCE_FIRST_TIMEOUT);
if (ret == -ETIME) {
pr_warn("%s: sync_fence_wait timed out!"
"Waiting %ld more seconds\n",
__func__,WAIT_FENCE_FINAL_TIMEOUT/MSEC_PER_SEC);
ret = sync_fence_wait(mfd->acq_fen[i],
WAIT_FENCE_FINAL_TIMEOUT);
}
if (ret < 0) {
pr_err("%s: sync_fence_wait failed! ret = %x\n",
__func__, ret);
break;
}
sync_fence_put(mfd->acq_fen[i]);
}
if (ret < 0) {
while (i < mfd->acq_fen_cnt) {
sync_fence_put(mfd->acq_fen[i]);
i++;
}
}
mfd->acq_fen_cnt = 0;
}
int msm_fb_signal_timeline(struct msm_fb_data_type *mfd)
{
mutex_lock(&mfd->sync_mutex);
if (mfd->timeline && !list_empty((const struct list_head *)
(&(mfd->timeline->obj.active_list_head)))) {
sw_sync_timeline_inc(mfd->timeline, 1);
mfd->timeline_value++;
}
if (atomic_read(&mfd->commit_cnt) > 0)
atomic_dec(&mfd->commit_cnt);
mutex_unlock(&mfd->sync_mutex);
return 0;
}
void msm_fb_release_timeline(struct msm_fb_data_type *mfd)
{
u32 commit_cnt;
mutex_lock(&mfd->sync_mutex);
commit_cnt = atomic_read(&mfd->commit_cnt) + 3;
if (commit_cnt < 0)
commit_cnt = 0;
if (mfd->timeline) {
sw_sync_timeline_inc(mfd->timeline, 2 + commit_cnt);
mfd->timeline_value += 2 + commit_cnt;
}
atomic_set(&mfd->commit_cnt, 0);
mutex_unlock(&mfd->sync_mutex);
}
DEFINE_SEMAPHORE(msm_fb_pan_sem);
static int msm_fb_pan_idle(struct msm_fb_data_type *mfd)
{
int ret = 0;
mutex_lock(&mfd->sync_mutex);
if (mfd->is_committing) {
mutex_unlock(&mfd->sync_mutex);
ret = wait_for_completion_interruptible_timeout(
&mfd->commit_comp,
msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT));
if (ret < 0)
ret = -ERESTARTSYS;
else if (!ret)
pr_err("%s wait for commit_comp timeout %d %d",
__func__, ret, mfd->is_committing);
if (ret <= 0) {
mutex_lock(&mfd->sync_mutex);
mfd->is_committing = 0;
complete_all(&mfd->commit_comp);
mutex_unlock(&mfd->sync_mutex);
}
} else {
mutex_unlock(&mfd->sync_mutex);
}
return ret;
}
static int msm_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 msm_fb_backup_type *fb_backup;
struct fb_var_screeninfo *var = &disp_commit->var;
u32 wait_for_finish = disp_commit->wait_for_finish;
int ret = 0;
if (disp_commit->flags &
MDP_DISPLAY_COMMIT_OVERLAY) {
if (mdp_fb_is_power_off(mfd)) {
/* suspended */
pr_err("%s,%d fb_%d panel type %d is suspended\n",
__func__, __LINE__, mfd->index,
mfd->panel.type);
return -EPERM;
}
} else {
/*
* If framebuffer is 2, io pan display is not allowed.
*/
if (bf_supported && info->node == 2) {
pr_err("%s: no pan display for fb%d!",
__func__, info->node);
return -EPERM;
}
if (info->node != 0 || mfd->cont_splash_done) /* primary */
if ((!mfd->op_enable) || (mdp_fb_is_power_off(mfd)))
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;
}
msm_fb_pan_idle(mfd);
mutex_lock(&mfd->sync_mutex);
if (!(disp_commit->flags &
MDP_DISPLAY_COMMIT_OVERLAY)) {
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;
} else {
mdp4_overlay_mdp_perf_upd(mfd, 1);
}
fb_backup = (struct msm_fb_backup_type *)mfd->msm_fb_backup;
memcpy(&fb_backup->info, info, sizeof(struct fb_info));
memcpy(&fb_backup->disp_commit, disp_commit,
sizeof(struct mdp_display_commit));
mfd->is_committing = 1;
INIT_COMPLETION(mfd->commit_comp);
atomic_inc(&mfd->commit_cnt);
mfd->wake_commit_thread = 1;
wake_up_interruptible_all(&mfd->commit_queue);
mutex_unlock(&mfd->sync_mutex);
if (wait_for_finish)
msm_fb_pan_idle(mfd);
return ret;
}
static void bl_workqueue_handler(struct work_struct *work)
{
struct msm_fb_data_type *mfd = container_of(to_delayed_work(work),
struct msm_fb_data_type, backlight_worker);
struct msm_fb_panel_data *pdata = mfd->pdev->dev.platform_data;
__u32 temp = unset_bl_level;
down(&mfd->sem);
if ((pdata) && (pdata->set_backlight) && (!bl_updated)
&& (!mdp_fb_is_power_off(mfd))) {
msm_fb_scale_bl(mfd->panel_info.bl_max, &temp);
mfd->bl_level = temp;
pdata->set_backlight(mfd);
bl_level_old = unset_bl_level;
mfd->bl_level = unset_bl_level;
bl_updated = 1;
}
up(&mfd->sem);
}
static int msm_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;
return msm_fb_pan_display_ex(info, &disp_commit);
}
static int msm_fb_pan_display_sub(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct mdp_dirty_region dirty;
struct mdp_dirty_region *dirtyPtr = NULL;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct msm_fb_panel_data *pdata;
/*
* If framebuffer is 2, io pen display is not allowed.
*/
if (bf_supported && info->node == 2) {
pr_err("%s: no pan display for fb%d!",
__func__, info->node);
return -EPERM;
}
if (info->node != 0 || mfd->cont_splash_done) /* primary */
if ((!mfd->op_enable) || (mdp_fb_is_power_off(mfd)))
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;
/* "UPDT" */
if (var->reserved[0] == 0x54445055) {
dirty.xoffset = var->reserved[1] & 0xffff;
dirty.yoffset = (var->reserved[1] >> 16) & 0xffff;
if ((var->reserved[2] & 0xffff) <= dirty.xoffset)
return -EINVAL;
if (((var->reserved[2] >> 16) & 0xffff) <= dirty.yoffset)
return -EINVAL;
dirty.width = (var->reserved[2] & 0xffff) - dirty.xoffset;
dirty.height =
((var->reserved[2] >> 16) & 0xffff) - dirty.yoffset;
info->var.yoffset = var->yoffset;
if (dirty.xoffset < 0)
return -EINVAL;
if (dirty.yoffset < 0)
return -EINVAL;
if ((dirty.xoffset + dirty.width) > info->var.xres)
return -EINVAL;
if ((dirty.yoffset + dirty.height) > info->var.yres)
return -EINVAL;
if ((dirty.width <= 0) || (dirty.height <= 0))
return -EINVAL;
dirtyPtr = &dirty;
}
complete(&mfd->msmfb_update_notify);
mutex_lock(&msm_fb_notify_update_sem);
if (mfd->msmfb_no_update_notify_timer.function)
del_timer(&mfd->msmfb_no_update_notify_timer);
mfd->msmfb_no_update_notify_timer.expires = jiffies + (2 * HZ);
add_timer(&mfd->msmfb_no_update_notify_timer);
mutex_unlock(&msm_fb_notify_update_sem);
down(&msm_fb_pan_sem);
msm_fb_wait_for_fence(mfd);
if (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */
mdp_set_dma_pan_info(info, NULL, TRUE);
if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) {
pr_err("%s: can't turn on display!\n", __func__);
up(&msm_fb_pan_sem);
msm_fb_release_timeline(mfd);
return -EINVAL;
}
}
mdp_set_dma_pan_info(info, dirtyPtr,
(var->activate & FB_ACTIVATE_VBL));
/* async call */
mdp_dma_pan_update(info);
msm_fb_signal_timeline(mfd);
if (mdp4_unmap_sec_resource(mfd))
pr_err("%s: unmap secure res failed\n", __func__);
up(&msm_fb_pan_sem);
if (unset_bl_level && !bl_updated)
schedule_delayed_work(&mfd->backlight_worker,
backlight_duration);
/* set backlight in recovery mode */
pdata = (struct msm_fb_panel_data *)mfd->pdev->
dev.platform_data;
if ((pdata) && (pdata->set_recovery_backlight)) {
down(&mfd->sem);
pdata->set_recovery_backlight(mfd);
up(&mfd->sem);
}
++mfd->panel_info.frame_count;
return 0;
}
void msm_fb_release_busy(struct msm_fb_data_type *mfd)
{
mutex_lock(&mfd->sync_mutex);
mfd->is_committing = 0;
complete_all(&mfd->commit_comp);
mutex_unlock(&mfd->sync_mutex);
}
static int msm_fb_commit_thread(void *data)
{
int ret = 0;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) data;
struct fb_var_screeninfo *var;
struct fb_info *info;
struct msm_fb_backup_type *fb_backup;
u32 overlay_commit = false;
while (!kthread_should_stop()) {
ret = wait_event_interruptible(mfd->commit_queue,
mfd->wake_commit_thread);
if (ret >= 0) {
mfd->wake_commit_thread = 0;
mutex_lock(&mfd->queue_mutex);
while (atomic_read(&mfd->commit_cnt) > 0) {
fb_backup = (struct msm_fb_backup_type *)
mfd->msm_fb_backup;
info = &fb_backup->info;
if (fb_backup->disp_commit.flags &
MDP_DISPLAY_COMMIT_OVERLAY) {
overlay_commit = true;
mdp4_overlay_commit(info);
} else {
var = &fb_backup->disp_commit.var;
msm_fb_pan_display_sub(var, info);
msm_fb_release_busy(mfd);
}
if (unset_bl_level && !bl_updated)
schedule_delayed_work(
&mfd->backlight_worker,
backlight_duration);
}
if (overlay_commit)
mdp4_overlay_commit_finish(info);
mutex_unlock(&mfd->queue_mutex);
}
}
return 0;
}
static int msm_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;
msm_fb_pan_idle(mfd);
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) || (va
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment