blob: 527b02f10592ccc8248031c5a5f2cfd588fb098a [file] [log] [blame]
/* drivers/video/msm/msm_fb.c
*
* Core MSM framebuffer driver.
*
* Copyright (C) 2007 Google Incorporated
* Copyright (c) 2008-2012, Code Aurora Forum. 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/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>
#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
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_lvl);
#ifdef MSM_FB_ENABLE_DBGFS
#define MSM_FB_MAX_DBGFS 1024
#define MAX_BACKLIGHT_BRIGHTNESS 255
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;
if (value > MAX_BACKLIGHT_BRIGHTNESS)
value = MAX_BACKLIGHT_BRIGHTNESS;
/* This maps android backlight level 0 to 255 into
driver backlight level 0 to bl_max with rounding */
bl_lvl = (2 * value * mfd->panel_info.bl_max + MAX_BACKLIGHT_BRIGHTNESS)
/(2 * MAX_BACKLIGHT_BRIGHTNESS);
if (!bl_lvl && value)
bl_lvl = 1;
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_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 DEVICE_ATTR(msm_fb_type, S_IRUGO, msm_fb_msm_fb_type, NULL);
static struct attribute *msm_fb_attrs[] = {
&dev_attr_msm_fb_type.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);
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_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);
/* 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;
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;
if (mfd->msmfb_no_update_notify_timer.function)
del_timer(&mfd->msmfb_no_update_notify_timer);
complete(&mfd->msmfb_no_update_notify);
/*
* suspend this channel
*/
mfd->suspend.sw_refreshing_enable = mfd->sw_refreshing_enable;
mfd->suspend.op_enable = mfd->op_enable;
mfd->suspend.panel_power_on = mfd->panel_power_on;
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 (mfd->suspend.panel_power_on) {
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");
}
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;
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;
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;
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;
#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;
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;
static int bl_level_old;
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*/
msm_fb_set_backlight(mfd, curr_bl);
up(&mfd->sem);
return ret;
}
static void msm_fb_scale_bl(__u32 *bl_lvl)
{
__u32 temp = *bl_lvl;
pr_debug("%s: input = %d, scale = %d", __func__, temp, bl_scale);
if (temp >= bl_min_lvl) {
/* bl_scale is the numerator of scaling fraction (x/1024)*/
temp = ((*bl_lvl) * 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;
if (!mfd->panel_power_on || !bl_updated) {
unset_bl_level = bkl_lvl;
return;
} else {
unset_bl_level = 0;
}
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
if ((pdata) && (pdata->set_backlight)) {
msm_fb_scale_bl(&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;
if (!op_enable)
return -EPERM;
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;
}
switch (blank_mode) {
case FB_BLANK_UNBLANK:
if (!mfd->panel_power_on) {
msleep(16);
ret = pdata->on(mfd->pdev);
if (ret == 0) {
mfd->panel_power_on = TRUE;
/* ToDo: possible conflict with android which doesn't expect sw refresher */
/*
if (!mfd->hw_refresh)
{
if ((ret = msm_fb_resume_sw_refresher(mfd)) != 0)
{
MSM_FB_INFO("msm_fb_blank_sub: msm_fb_resume_sw_refresher failed = %d!\n",ret);
}
}
*/
}
}
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) {
int curr_pwr_state;
mfd->op_enable = FALSE;
curr_pwr_state = mfd->panel_power_on;
mfd->panel_power_on = FALSE;
cancel_delayed_work_sync(&mfd->backlight_worker);
bl_updated = 0;
msleep(16);
ret = pdata->off(mfd->pdev);
if (ret)
mfd->panel_power_on = curr_pwr_state;
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;
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;
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;
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;
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 (off >= len) {
/* memory mapped io */
off -= len;
if (info->var.accel_flags) {
mutex_unlock(&info->lock);
return -EINVAL;
}
start = info->fix.mmio_start;
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
/* Set VM flags. */
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
return -EINVAL;
off += start;
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 = mfd->panel_info.clk_rate;
mfd->var_pixclock = var->pixclock;
var->xres = panel_info->xres;
var->yres = panel_info->yres;
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 */
if (mfd->dest == DISPLAY_LCD) {
if (panel_info->type == MDDI_PANEL && panel_info->mddi.is_type1)
var->reserved[4] = panel_info->lcd.refx100 / (100 * 2);
else
var->reserved[4] = panel_info->lcd.refx100 / 100;
} else {
if (panel_info->type == MIPI_VIDEO_PANEL) {
var->reserved[4] = panel_info->mipi.frame_rate;
} else {
var->reserved[4] = panel_info->clk_rate /
((panel_info->lcdc.h_back_porch +
panel_info->lcdc.h_front_porch +
panel_info->lcdc.h_pulse_width +
panel_info->xres) *
(panel_info->lcdc.v_back_porch +
panel_info->lcdc.v_front_porch +
panel_info->lcdc.v_pulse_width +
panel_info->yres));
}
}
pr_debug("reserved[4] %u\n", var->reserved[4]);
/*
* 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_on = FALSE;
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);
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_on = FALSE;
/* 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->panel_info.type != WRITEBACK_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_on",
(u32 *) &mfd->
panel_power_on);
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;
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 (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) {
printk(KERN_ERR "msm_fb_open: can't turn on display!\n");
return -1;
}
}
mfd->ref_cnt++;
return 0;
}
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;
}
mfd->ref_cnt--;
if (!mfd->ref_cnt) {
if ((ret =
msm_fb_blank_sub(FB_BLANK_POWERDOWN, info,
mfd->op_enable)) != 0) {
printk(KERN_ERR "msm_fb_release: can't turn off display!\n");
return ret;
}
}
pm_runtime_put(info->dev);
return ret;
}
DEFINE_SEMAPHORE(msm_fb_pan_sem);
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;
if ((pdata) && (pdata->set_backlight) && (!bl_updated)) {
down(&mfd->sem);
mfd->bl_level = unset_bl_level;
pdata->set_backlight(mfd);
bl_level_old = 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_dirty_region dirty;
struct mdp_dirty_region *dirtyPtr = NULL;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
/*
* 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) || (!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;
/* "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);
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__);
return -EINVAL;
}
}
mdp_set_dma_pan_info(info, dirtyPtr,
(var->activate == FB_ACTIVATE_VBL));
mdp_dma_pan_update(info);
up(&msm_fb_pan_sem);
if (unset_bl_level && !bl_updated)
schedule_delayed_work(&mfd->backlight_worker,
backlight_duration);
if (info->node == 0 && (mfd->cont_splash_done)) /* primary */
mdp_free_splash_buffer(mfd);
++mfd->panel_info.frame_count;
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;
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 (!bf_supported ||
(info->node != 1 && info->node != 2))
if (info->fix.smem_len <
(var->xres_virtual*
var->yres_virtual*
(var->bits_per_pixel/8)))
return -EINVAL;
if ((var->xres == 0) || (var->yres == 0))
return -EINVAL;
if ((var->xres > MAX(mfd->panel_info.xres,
mfd->panel_info.mode2_xres)) ||
(var->yres > MAX(mfd->panel_info.yres,
mfd->panel_info.mode2_yres)))
return -EINVAL;
if (var->xoffset > (var->xres_virtual - var->xres))
return -EINVAL;
if (var->yoffset > (var->yres_virtual - var->yres))
return -EINVAL;
return 0;
}
int msm_fb_check_frame_rate(struct msm_fb_data_type *mfd
, struct fb_info *info)
{
int panel_height, panel_width, var_frame_rate, fps_mod;
struct fb_var_screeninfo *var = &info->var;
fps_mod = 0;
if ((mfd->panel_info.type == DTV_PANEL) ||
(mfd->panel_info.type == HDMI_PANEL)) {
panel_height = var->yres + var->upper_margin +
var->vsync_len + var->lower_margin;
panel_width = var->xres + var->right_margin +
var->hsync_len + var->left_margin;
var_frame_rate = ((var->pixclock)/(panel_height * panel_width));
if (mfd->var_frame_rate != var_frame_rate) {
fps_mod = 1;
mfd->var_frame_rate = var_frame_rate;
}
}
return fps_mod;
}
static int msm_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;
int blank = 0;
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->var_pixclock != var->pixclock) ||
(mfd->hw_refresh && ((mfd->fb_imgType != old_imgType) ||
(mfd->var_pixclock != var->pixclock) ||
(mfd->var_xres != var->xres) ||
(mfd->var_yres != var->yres) ||
(msm_fb_check_frame_rate(mfd, info))))) {
mfd->var_xres = var->xres;
mfd->var_yres = var->yres;
mfd->var_pixclock = var->pixclock;
blank = 1;
}
mfd->fbi->fix.line_length = msm_fb_line_length(mfd->index, var->xres,
var->bits_per_pixel/8);
if (blank) {
msm_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable);
msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable);
}
return 0;
}
static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd)
{
if (mfd->hw_refresh)
return -EPERM;
if (mfd->sw_currently_refreshing) {
down(&mfd->sem);
mfd->sw_currently_refreshing = FALSE;
up(&mfd->sem);
/* wait until the refresher finishes the last job */
wait_for_completion_killable(&mfd->refresher_comp);
}
return 0;
}
int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd)
{
boolean do_refresh;
if (mfd->hw_refresh)
return -EPERM;
down(&mfd->sem);
if ((!mfd->sw_currently_refreshing) && (mfd->sw_refreshing_enable)) {
do_refresh = TRUE;
mfd->sw_currently_refreshing = TRUE;
} else {
do_refresh = FALSE;
}
up(&mfd->sem);
if (do_refresh)
mdp_refresh_screen((unsigned long)mfd);
return 0;
}
#if defined CONFIG_FB_MSM_MDP31
static int mdp_blit_split_height(struct fb_info *info,
struct mdp_blit_req *req)
{
int ret;
struct mdp_blit_req splitreq;
int s_x_0, s_x_1, s_w_0, s_w_1, s_y_0, s_y_1, s_h_0, s_h_1;
int d_x_0, d_x_1, d_w_0, d_w_1, d_y_0, d_y_1, d_h_0, d_h_1;
splitreq = *req;
/* break dest roi at height*/
d_x_0 = d_x_1 = req->dst_rect.x;
d_w_0 = d_w_1 = req->dst_rect.w;
d_y_0 = req->dst_rect.y;
if (req->dst_rect.h % 32 == 3)
d_h_1 = (req->dst_rect.h - 3) / 2 - 1;
else if (req->dst_rect.h % 32 == 2)
d_h_1 = (req->dst_rect.h - 2) / 2 - 6;
else
d_h_1 = (req->dst_rect.h - 1) / 2 - 1;
d_h_0 = req->dst_rect.h - d_h_1;
d_y_1 = d_y_0 + d_h_0;
if (req->dst_rect.h == 3) {
d_h_1 = 2;
d_h_0 = 2;
d_y_1 = d_y_0 + 1;
}
/* blit first region */
if (((splitreq.flags & 0x07) == 0x04) ||
((splitreq.flags & 0x07) == 0x0)) {
if (splitreq.flags & MDP_ROT_90) {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_h_1) / req->dst_rect.h;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_h_1 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
} else {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_h_1) / req->dst_rect.h;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_h_1 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
} else {
if (splitreq.flags & MDP_ROT_90) {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_h_0) / req->dst_rect.h;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_h_0 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
} else {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_h_0) / req->dst_rect.h;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_h_0 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
}
ret = mdp_ppp_blit(info, &splitreq);
if (ret)
return ret;
/* blit second region */
if (((splitreq.flags & 0x07) == 0x04) ||
((splitreq.flags & 0x07) == 0x0)) {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
} else {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
}
ret = mdp_ppp_blit(info, &splitreq);
return ret;
}
#endif
int mdp_blit(struct fb_info *info, struct mdp_blit_req *req)
{
int ret;
#if defined CONFIG_FB_MSM_MDP31 || defined CONFIG_FB_MSM_MDP30
unsigned int remainder = 0, is_bpp_4 = 0;
struct mdp_blit_req splitreq;
int s_x_0, s_x_1, s_w_0, s_w_1, s_y_0, s_y_1, s_h_0, s_h_1;
int d_x_0, d_x_1, d_w_0, d_w_1, d_y_0, d_y_1, d_h_0, d_h_1;
if (req->flags & MDP_ROT_90) {
if (((req->dst_rect.h == 1) && ((req->src_rect.w != 1) ||
(req->dst_rect.w != req->src_rect.h))) ||
((req->dst_rect.w == 1) && ((req->src_rect.h != 1) ||
(req->dst_rect.h != req->src_rect.w)))) {
printk(KERN_ERR "mpd_ppp: error scaling when size is 1!\n");
return -EINVAL;
}
} else {
if (((req->dst_rect.w == 1) && ((req->src_rect.w != 1) ||
(req->dst_rect.h != req->src_rect.h))) ||
((req->dst_rect.h == 1) && ((req->src_rect.h != 1) ||
(req->dst_rect.w != req->src_rect.w)))) {
printk(KERN_ERR "mpd_ppp: error scaling when size is 1!\n");
return -EINVAL;
}
}
#endif
if (unlikely(req->src_rect.h == 0 || req->src_rect.w == 0)) {
printk(KERN_ERR "mpd_ppp: src img of zero size!\n");
return -EINVAL;
}
if (unlikely(req->dst_rect.h == 0 || req->dst_rect.w == 0))
return 0;
#if defined CONFIG_FB_MSM_MDP31
/* MDP width split workaround */
remainder = (req->dst_rect.w)%32;
ret = mdp_get_bytes_per_pixel(req->dst.format,
(struct msm_fb_data_type *)info->par);
if (ret <= 0) {
printk(KERN_ERR "mdp_ppp: incorrect bpp!\n");
return -EINVAL;
}
is_bpp_4 = (ret == 4) ? 1 : 0;
if ((is_bpp_4 && (remainder == 6 || remainder == 14 ||
remainder == 22 || remainder == 30)) || remainder == 3 ||
(remainder == 1 && req->dst_rect.w != 1) ||
(remainder == 2 && req->dst_rect.w != 2)) {
/* make new request as provide by user */
splitreq = *req;
/* break dest roi at width*/
d_y_0 = d_y_1 = req->dst_rect.y;
d_h_0 = d_h_1 = req->dst_rect.h;
d_x_0 = req->dst_rect.x;
if (remainder == 14)
d_w_1 = (req->dst_rect.w - 14) / 2 + 4;
else if (remainder == 22)
d_w_1 = (req->dst_rect.w - 22) / 2 + 10;
else if (remainder == 30)
d_w_1 = (req->dst_rect.w - 30) / 2 + 10;
else if (remainder == 6)
d_w_1 = req->dst_rect.w / 2 - 1;
else if (remainder == 3)
d_w_1 = (req->dst_rect.w - 3) / 2 - 1;
else if (remainder == 2)
d_w_1 = (req->dst_rect.w - 2) / 2 - 6;
else
d_w_1 = (req->dst_rect.w - 1) / 2 - 1;
d_w_0 = req->dst_rect.w - d_w_1;
d_x_1 = d_x_0 + d_w_0;
if (req->dst_rect.w == 3) {
d_w_1 = 2;
d_w_0 = 2;
d_x_1 = d_x_0 + 1;
}
/* blit first region */
if (((splitreq.flags & 0x07) == 0x07) ||
((splitreq.flags & 0x07) == 0x0)) {
if (splitreq.flags & MDP_ROT_90) {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_w_1) /
req->dst_rect.w;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_w_1 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
} else {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_w_1) /
req->dst_rect.w;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_w_1 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
} else {
if (splitreq.flags & MDP_ROT_90) {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_w_0) /
req->dst_rect.w;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_w_0 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
} else {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_w_0) /
req->dst_rect.w;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_w_0 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
}
if ((splitreq.dst_rect.h % 32 == 3) ||
((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) ||
((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2))
ret = mdp_blit_split_height(info, &splitreq);
else
ret = mdp_ppp_blit(info, &splitreq);
if (ret)
return ret;
/* blit second region */
if (((splitreq.flags & 0x07) == 0x07) ||
((splitreq.flags & 0x07) == 0x0)) {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
} else {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
}
if (((splitreq.dst_rect.h % 32) == 3) ||
((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) ||
((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2))
ret = mdp_blit_split_height(info, &splitreq);
else
ret = mdp_ppp_blit(info, &splitreq);
if (ret)
return ret;
} else if ((req->dst_rect.h % 32) == 3 ||
((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) ||
((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2))
ret = mdp_blit_split_height(info, req);
else
ret = mdp_ppp_blit(info, req);
return ret;
#elif defined CONFIG_FB_MSM_MDP30
/* MDP width split workaround */
remainder = (req->dst_rect.w)%16;
ret = mdp_get_bytes_per_pixel(req->dst.format,
(struct msm_fb_data_type *)info->par);
if (ret <= 0) {
printk(KERN_ERR "mdp_ppp: incorrect bpp!\n");
return -EINVAL;
}
is_bpp_4 = (ret == 4) ? 1 : 0;
if ((is_bpp_4 && (remainder == 6 || remainder == 14))) {
/* make new request as provide by user */
splitreq = *req;
/* break dest roi at width*/
d_y_0 = d_y_1 = req->dst_rect.y;
d_h_0 = d_h_1 = req->dst_rect.h;
d_x_0 = req->dst_rect.x;
if (remainder == 14 || remainder == 6)
d_w_1 = req->dst_rect.w / 2;
else
d_w_1 = (req->dst_rect.w - 1) / 2 - 1;
d_w_0 = req->dst_rect.w - d_w_1;
d_x_1 = d_x_0 + d_w_0;
/* blit first region */
if (((splitreq.flags & 0x07) == 0x07) ||
((splitreq.flags & 0x07) == 0x05) ||
((splitreq.flags & 0x07) == 0x02) ||
((splitreq.flags & 0x07) == 0x0)) {
if (splitreq.flags & MDP_ROT_90) {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_w_1) /
req->dst_rect.w;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_w_1 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
} else {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_w_1) /
req->dst_rect.w;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_w_1 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
} else {
if (splitreq.flags & MDP_ROT_90) {
s_x_0 = s_x_1 = req->src_rect.x;
s_w_0 = s_w_1 = req->src_rect.w;
s_y_0 = req->src_rect.y;
s_h_1 = (req->src_rect.h * d_w_0) /
req->dst_rect.w;
s_h_0 = req->src_rect.h - s_h_1;
s_y_1 = s_y_0 + s_h_0;
if (d_w_0 >= 8 * s_h_1) {
s_h_1++;
s_y_1--;
}
} else {
s_y_0 = s_y_1 = req->src_rect.y;
s_h_0 = s_h_1 = req->src_rect.h;
s_x_0 = req->src_rect.x;
s_w_1 = (req->src_rect.w * d_w_0) /
req->dst_rect.w;
s_w_0 = req->src_rect.w - s_w_1;
s_x_1 = s_x_0 + s_w_0;
if (d_w_0 >= 8 * s_w_1) {
s_w_1++;
s_x_1--;
}
}
splitreq.src_rect.h = s_h_0;
splitreq.src_rect.y = s_y_0;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_0;
splitreq.src_rect.w = s_w_0;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
}
/* No need to split in height */
ret = mdp_ppp_blit(info, &splitreq);
if (ret)
return ret;
/* blit second region */
if (((splitreq.flags & 0x07) == 0x07) ||
((splitreq.flags & 0x07) == 0x05) ||
((splitreq.flags & 0x07) == 0x02) ||
((splitreq.flags & 0x07) == 0x0)) {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_1;
splitreq.dst_rect.y = d_y_1;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_1;
splitreq.dst_rect.w = d_w_1;
} else {
splitreq.src_rect.h = s_h_1;
splitreq.src_rect.y = s_y_1;
splitreq.dst_rect.h = d_h_0;
splitreq.dst_rect.y = d_y_0;
splitreq.src_rect.x = s_x_1;
splitreq.src_rect.w = s_w_1;
splitreq.dst_rect.x = d_x_0;
splitreq.dst_rect.w = d_w_0;
}
/* No need to split in height ... just width */
ret = mdp_ppp_blit(info, &splitreq);
if (ret)
return ret;
} else
ret = mdp_ppp_blit(info, req);
return ret;
#else
ret = mdp_ppp_blit(info, req);
return ret;
#endif
}
typedef void (*msm_dma_barrier_function_pointer) (void *, size_t);
static inline void msm_fb_dma_barrier_for_rect(struct fb_info *info,
struct mdp_img *img, struct mdp_rect *rect,
msm_dma_barrier_function_pointer dma_barrier_fp
)
{
/*
* Compute the start and end addresses of the rectangles.
* NOTE: As currently implemented, the data between
* the end of one row and the start of the next is
* included in the address range rather than
* doing multiple calls for each row.
*/
unsigned long start;
size_t size;
char * const pmem_start = info->screen_base;
int bytes_per_pixel = mdp_get_bytes_per_pixel(img->format,
(struct msm_fb_data_type *)info->par);
if (bytes_per_pixel <= 0) {
printk(KERN_ERR "%s incorrect bpp!\n", __func__);
return;
}
start = (unsigned long)pmem_start + img->offset +
(img->width * rect->y + rect->x) * bytes_per_pixel;
size = (rect->h * img->width + rect->w) * bytes_per_pixel;
(*dma_barrier_fp) ((void *) start, size);
}
static inline void msm_dma_nc_pre(void)
{
dmb();
}
static inline void msm_dma_wt_pre(void)
{
dmb();
}
static inline void msm_dma_todevice_wb_pre(void *start, size_t size)
{
dma_cache_pre_ops(start, size, DMA_TO_DEVICE);
}
static inline void msm_dma_fromdevice_wb_pre(void *start, size_t size)
{
dma_cache_pre_ops(start, size, DMA_FROM_DEVICE);
}
static inline void msm_dma_nc_post(void)
{
dmb();
}
static inline void msm_dma_fromdevice_wt_post(void *start, size_t size)
{
dma_cache_post_ops(start, size, DMA_FROM_DEVICE);
}
static inline void msm_dma_todevice_wb_post(void *start, size_t size)
{
dma_cache_post_ops(start, size, DMA_TO_DEVICE);
}
static inline void msm_dma_fromdevice_wb_post(void *start, size_t size)
{
dma_cache_post_ops(start, size, DMA_FROM_DEVICE);
}
/*
* Do the write barriers required to guarantee data is committed to RAM
* (from CPU cache or internal buffers) before a DMA operation starts.
* NOTE: As currently implemented, the data between
* the end of one row and the start of the next is
* included in the address range rather than
* doing multiple calls for each row.
*/
static void msm_fb_ensure_memory_coherency_before_dma(struct fb_info *info,
struct mdp_blit_req *req_list,
int req_list_count)
{
#ifdef CONFIG_ARCH_QSD8X50
int i;
/*
* Normally, do the requested barriers for each address
* range that corresponds to a rectangle.
*
* But if at least one write barrier is requested for data
* going to or from the device but no address range is
* needed for that barrier, then do the barrier, but do it
* only once, no matter how many requests there are.
*/
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
switch (mfd->mdp_fb_page_protection) {
default:
case MDP_FB_PAGE_PROTECTION_NONCACHED:
case MDP_FB_PAGE_PROTECTION_WRITECOMBINE:
/*
* The following barrier is only done at most once,
* since further calls would be redundant.
*/
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags
& MDP_NO_DMA_BARRIER_START)) {
msm_dma_nc_pre();
break;
}
}
break;
case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE:
/*
* The following barrier is only done at most once,
* since further calls would be redundant.
*/
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags
& MDP_NO_DMA_BARRIER_START)) {
msm_dma_wt_pre();
break;
}
}
break;
case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE:
case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE:
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags &
MDP_NO_DMA_BARRIER_START)) {
msm_fb_dma_barrier_for_rect(info,
&(req_list[i].src),
&(req_list[i].src_rect),
msm_dma_todevice_wb_pre
);
msm_fb_dma_barrier_for_rect(info,
&(req_list[i].dst),
&(req_list[i].dst_rect),
msm_dma_todevice_wb_pre
);
}
}
break;
}
#else
dmb();
#endif
}
/*
* Do the write barriers required to guarantee data will be re-read from RAM by
* the CPU after a DMA operation ends.
* NOTE: As currently implemented, the data between
* the end of one row and the start of the next is
* included in the address range rather than
* doing multiple calls for each row.
*/
static void msm_fb_ensure_memory_coherency_after_dma(struct fb_info *info,
struct mdp_blit_req *req_list,
int req_list_count)
{
#ifdef CONFIG_ARCH_QSD8X50
int i;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
switch (mfd->mdp_fb_page_protection) {
default:
case MDP_FB_PAGE_PROTECTION_NONCACHED:
case MDP_FB_PAGE_PROTECTION_WRITECOMBINE:
/*
* The following barrier is only done at most once,
* since further calls would be redundant.
*/
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags
& MDP_NO_DMA_BARRIER_END)) {
msm_dma_nc_post();
break;
}
}
break;
case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE:
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags &
MDP_NO_DMA_BARRIER_END)) {
msm_fb_dma_barrier_for_rect(info,
&(req_list[i].dst),
&(req_list[i].dst_rect),
msm_dma_fromdevice_wt_post
);
}
}
break;
case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE:
case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE:
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags &
MDP_NO_DMA_BARRIER_END)) {
msm_fb_dma_barrier_for_rect(info,
&(req_list[i].dst),
&(req_list[i].dst_rect),
msm_dma_fromdevice_wb_post
);
}
}
break;
}
#else
dmb();
#endif
}
/*
* NOTE: The userspace issues blit operations in a sequence, the sequence
* start with a operation marked START and ends in an operation marked
* END. It is guranteed by the userspace that all the blit operations
* between START and END are only within the regions of areas designated
* by the START and END operations and that the userspace doesnt modify
* those areas. Hence it would be enough to perform barrier/cache operations
* only on the START and END operations.
*/
static int msmfb_blit(struct fb_info *info, void __user *p)
{
/*
* CAUTION: The names of the struct types intentionally *DON'T* match
* the names of the variables declared -- they appear to be swapped.
* Read the code carefully and you should see that the variable names
* make sense.
*/
const int MAX_LIST_WINDOW = 16;
struct mdp_blit_req req_list[MAX_LIST_WINDOW];
struct mdp_blit_req_list req_list_header;
int count, i, req_list_count;
if (bf_supported &&
(info->node == 1 || info->node == 2)) {
pr_err("%s: no pan display for fb%d.",
__func__, info->node);
return -EPERM;
}
/* Get the count size for the total BLIT request. */
if (copy_from_user(&req_list_header, p, sizeof(req_list_header)))
return -EFAULT;
p += sizeof(req_list_header);
count = req_list_header.count;
if (count < 0 || count >= MAX_BLIT_REQ)
return -EINVAL;
while (count > 0) {
/*
* Access the requests through a narrow window to decrease copy
* overhead and make larger requests accessible to the
* coherency management code.
* NOTE: The window size is intended to be larger than the
* typical request size, but not require more than 2
* kbytes of stack storage.
*/
req_list_count = count;
if (req_list_count > MAX_LIST_WINDOW)
req_list_count = MAX_LIST_WINDOW;
if (copy_from_user(&req_list, p,
sizeof(struct mdp_blit_req)*req_list_count))
return -EFAULT;
/*
* Ensure that any data CPU may have previously written to
* internal state (but not yet committed to memory) is
* guaranteed to be committed to memory now.
*/
msm_fb_ensure_memory_coherency_before_dma(info,
req_list, req_list_count);
/*
* Do the blit DMA, if required -- returning early only if
* there is a failure.
*/
for (i = 0; i < req_list_count; i++) {
if (!(req_list[i].flags & MDP_NO_BLIT)) {
/* Do the actual blit. */
int ret = mdp_blit(info, &(req_list[i]));
/*
* Note that early returns don't guarantee
* memory coherency.
*/
if (ret)
return ret;
}
}
/*
* Ensure that CPU cache and other internal CPU state is
* updated to reflect any change in memory modified by MDP blit
* DMA.
*/
msm_fb_ensure_memory_coherency_after_dma(info,
req_list,
req_list_count);
/* Go to next window of requests. */
count -= req_list_count;
p += sizeof(struct mdp_blit_req)*req_list_count;
}
return 0;
}
static int msmfb_vsync_ctrl(struct fb_info *info, void __user *argp)
{
int enable, ret;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
ret = copy_from_user(&enable, argp, sizeof(enable));
if (ret) {
pr_err("%s:msmfb_overlay_vsync ioctl failed", __func__);
return ret;
}
if (mfd->vsync_ctrl)
mfd->vsync_ctrl(enable);
else {
pr_err("%s: Vsync IOCTL not supported", __func__);
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_FB_MSM_OVERLAY
static int msmfb_overlay_get(struct fb_info *info, void __user *p)
{
struct mdp_overlay req;
int ret;
if (copy_from_user(&req, p, sizeof(req)))
return -EFAULT;
ret = mdp4_overlay_get(info, &req);
if (ret) {
printk(KERN_ERR "%s: ioctl failed \n",
__func__);
return ret;
}
if (copy_to_user(p, &req, sizeof(req))) {
printk(KERN_ERR "%s: copy2user failed \n",
__func__);
return -EFAULT;
}
return 0;
}
static int msmfb_overlay_set(struct fb_info *info, void __user *p)
{
struct mdp_overlay req;
int ret;
if (copy_from_user(&req, p, sizeof(req)))
return -EFAULT;
ret = mdp4_overlay_set(info, &req);
if (ret) {
printk(KERN_ERR "%s: ioctl failed, rc=%d\n",
__func__, ret);
return ret;
}
if (copy_to_user(p, &req, sizeof(req))) {
printk(KERN_ERR "%s: copy2user failed \n",
__func__);
return -EFAULT;
}
return 0;
}
static int msmfb_overlay_unset(struct fb_info *info, unsigned long *argp)
{
int ret, ndx;
ret = copy_from_user(&ndx, argp, sizeof(ndx));
if (ret) {
printk(KERN_ERR "%s:msmfb_overlay_unset ioctl failed \n",
__func__);
return ret;
}
return mdp4_overlay_unset(info, ndx);
}
static int msmfb_overlay_vsync_ctrl(struct fb_info *info, void __user *argp)
{
int ret;
int enable;
ret = copy_from_user(&enable, argp, sizeof(enable));
if (ret) {
pr_err("%s:msmfb_overlay_vsync ioctl failed", __func__);
return ret;
}
ret = mdp4_overlay_vsync_ctrl(info, enable);
return ret;
}
static int msmfb_overlay_play_wait(struct fb_info *info, unsigned long *argp)
{
int ret;
struct msmfb_overlay_data req;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (mfd->overlay_play_enable == 0) /* nothing to do */
return 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("%s:msmfb_overlay_wait ioctl failed", __func__);
return ret;
}
ret = mdp4_overlay_play_wait(info, &req);
return ret;
}
static int msmfb_overlay_commit(struct fb_info *info, unsigned long *argp)
{
int ret, ndx;
ret = copy_from_user(&ndx, argp, sizeof(ndx));
if (ret) {
pr_err("%s: ioctl failed\n", __func__);
return ret;
}
return mdp4_overlay_commit(info, ndx);
}
static int msmfb_overlay_play(struct fb_info *info, unsigned long *argp)
{
int ret;
struct msmfb_overlay_data req;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (mfd->overlay_play_enable == 0) /* nothing to do */
return 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
printk(KERN_ERR "%s:msmfb_overlay_play ioctl failed \n",
__func__);
return ret;
}
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);
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__);
return -EINVAL;
}
}
ret = mdp4_overlay_play(info, &req);
if (unset_bl_level && !bl_updated)
schedule_delayed_work(&mfd->backlight_worker,
backlight_duration);
if (info->node == 0 && (mfd->cont_splash_done)) /* primary */
mdp_free_splash_buffer(mfd);
return ret;
}
static int msmfb_overlay_play_enable(struct fb_info *info, unsigned long *argp)
{
int ret, enable;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
ret = copy_from_user(&enable, argp, sizeof(enable));
if (ret) {
printk(KERN_ERR "%s:msmfb_overlay_play_enable ioctl failed \n",
__func__);
return ret;
}
mfd->overlay_play_enable = enable;
return 0;
}
static int msmfb_overlay_blt(struct fb_info *info, unsigned long *argp)
{
int ret;
struct msmfb_overlay_blt req;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("%s: failed\n", __func__);
return ret;
}
ret = mdp4_overlay_blt(info, &req);
return ret;
}
#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL
static int msmfb_overlay_ioctl_writeback_init(struct fb_info *info)
{
return mdp4_writeback_init(info);
}
static int msmfb_overlay_ioctl_writeback_start(
struct fb_info *info)
{
int ret = 0;
ret = mdp4_writeback_start(info);
if (ret)
goto error;
error:
if (ret)
pr_err("%s:msmfb_writeback_start "
" ioctl failed\n", __func__);
return ret;
}
static int msmfb_overlay_ioctl_writeback_stop(
struct fb_info *info)
{
int ret = 0;
ret = mdp4_writeback_stop(info);
if (ret)
goto error;
error:
if (ret)
pr_err("%s:msmfb_writeback_stop ioctl failed\n",
__func__);
return ret;
}
static int msmfb_overlay_ioctl_writeback_queue_buffer(
struct fb_info *info, unsigned long *argp)
{
int ret = 0;
struct msmfb_data data;
ret = copy_from_user(&data, argp, sizeof(data));
if (ret)
goto error;
ret = mdp4_writeback_queue_buffer(info, &data);
if (ret)
goto error;
error:
if (ret)
pr_err("%s:msmfb_writeback_queue_buffer ioctl failed\n",
__func__);
return ret;
}
static int msmfb_overlay_ioctl_writeback_dequeue_buffer(
struct fb_info *info, unsigned long *argp)
{
int ret = 0;
struct msmfb_data data;
ret = copy_from_user(&data, argp, sizeof(data));
if (ret)
goto error;
ret = mdp4_writeback_dequeue_buffer(info, &data);
if (ret)
goto error;
ret = copy_to_user(argp, &data, sizeof(data));
if (ret)
goto error;
error:
if (ret)
pr_err("%s:msmfb_writeback_dequeue_buffer ioctl failed\n",
__func__);
return ret;
}
static int msmfb_overlay_ioctl_writeback_terminate(struct fb_info *info)
{
return mdp4_writeback_terminate(info);
}
#else
static int msmfb_overlay_ioctl_writeback_init(struct fb_info *info)
{
return -ENOTSUPP;
}
static int msmfb_overlay_ioctl_writeback_start(
struct fb_info *info)
{
return -ENOTSUPP;
}
static int msmfb_overlay_ioctl_writeback_stop(
struct fb_info *info)
{
return -ENOTSUPP;
}
static int msmfb_overlay_ioctl_writeback_queue_buffer(
struct fb_info *info, unsigned long *argp)
{
return -ENOTSUPP;
}
static int msmfb_overlay_ioctl_writeback_dequeue_buffer(
struct fb_info *info, unsigned long *argp)
{
return -ENOTSUPP;
}
static int msmfb_overlay_ioctl_writeback_terminate(struct fb_info *info)
{
return -ENOTSUPP;
}
#endif
static int msmfb_overlay_3d_sbys(struct fb_info *info, unsigned long *argp)
{
int ret;
struct msmfb_overlay_3d req;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("%s:msmfb_overlay_3d_ctrl ioctl failed\n",
__func__);
return ret;
}
ret = mdp4_overlay_3d_sbys(info, &req);
return ret;
}
static int msmfb_mixer_info(struct fb_info *info, unsigned long *argp)
{
int ret, cnt;
struct msmfb_mixer_info_req req;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("%s: failed\n", __func__);
return ret;
}
cnt = mdp4_mixer_info(req.mixer_num, req.info);
req.cnt = cnt;
ret = copy_to_user(argp, &req, sizeof(req));
if (ret)
pr_err("%s:msmfb_overlay_blt_off ioctl failed\n",
__func__);
return cnt;
}
#endif
DEFINE_SEMAPHORE(msm_fb_ioctl_ppp_sem);
DEFINE_MUTEX(msm_fb_ioctl_lut_sem);
/* Set color conversion matrix from user space */
#ifndef CONFIG_FB_MSM_MDP40
static void msmfb_set_color_conv(struct mdp_ccs *p)
{
int i;
if (p->direction == MDP_CCS_RGB2YUV) {
/* MDP cmd block enable */
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE);
/* RGB->YUV primary forward matrix */
for (i = 0; i < MDP_CCS_SIZE; i++)
writel(p->ccs[i], MDP_CSC_PFMVn(i));
#ifdef CONFIG_FB_MSM_MDP31
for (i = 0; i < MDP_BV_SIZE; i++)
writel(p->bv[i], MDP_CSC_POST_BV2n(i));
#endif
/* MDP cmd block disable */
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
} else {
/* MDP cmd block enable */
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE);
/* YUV->RGB primary reverse matrix */
for (i = 0; i < MDP_CCS_SIZE; i++)
writel(p->ccs[i], MDP_CSC_PRMVn(i));
for (i = 0; i < MDP_BV_SIZE; i++)
writel(p->bv[i], MDP_CSC_PRE_BV1n(i));
/* MDP cmd block disable */
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
}
}
#else
static void msmfb_set_color_conv(struct mdp_csc *p)
{
mdp4_vg_csc_update(p);
}
#endif
static int msmfb_notify_update(struct fb_info *info, unsigned long *argp)
{
int ret, notify;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
ret = copy_from_user(&notify, argp, sizeof(int));
if (ret) {
pr_err("%s:ioctl failed\n", __func__);
return ret;
}
if (notify > NOTIFY_UPDATE_STOP)
return -EINVAL;
if (notify == NOTIFY_UPDATE_START) {
INIT_COMPLETION(mfd->msmfb_update_notify);
ret = wait_for_completion_interruptible_timeout(
&mfd->msmfb_update_notify, 4*HZ);
} else {
INIT_COMPLETION(mfd->msmfb_no_update_notify);
ret = wait_for_completion_interruptible_timeout(
&mfd->msmfb_no_update_notify, 4*HZ);
}
if (ret == 0)
ret = -ETIMEDOUT;
return (ret > 0) ? 0 : ret;
}
static int msmfb_handle_pp_ioctl(struct msm_fb_data_type *mfd,
struct msmfb_mdp_pp *pp_ptr)
{
int ret = -1;
#ifdef CONFIG_FB_MSM_MDP40
int i = 0;
#endif
if (!pp_ptr)
return ret;
switch (pp_ptr->op) {
#ifdef CONFIG_FB_MSM_MDP40
case mdp_op_csc_cfg:
ret = mdp4_csc_config(&(pp_ptr->data.csc_cfg_data));
for (i = 0; i < CSC_MAX_BLOCKS; i++) {
if (pp_ptr->data.csc_cfg_data.block ==
csc_cfg_matrix[i].block) {
memcpy(&csc_cfg_matrix[i].csc_data,
&(pp_ptr->data.csc_cfg_data.csc_data),
sizeof(struct mdp_csc_cfg));
break;
}
}
break;
case mdp_op_pcc_cfg:
ret = mdp4_pcc_cfg(&(pp_ptr->data.pcc_cfg_data));
break;
case mdp_op_lut_cfg:
switch (pp_ptr->data.lut_cfg_data.lut_type) {
case mdp_lut_igc:
ret = mdp4_igc_lut_config(
(struct mdp_igc_lut_data *)
&pp_ptr->data.lut_cfg_data.data);
break;
case mdp_lut_pgc:
ret = mdp4_argc_cfg(
&pp_ptr->data.lut_cfg_data.data.pgc_lut_data);
break;
case mdp_lut_hist:
ret = mdp_hist_lut_config(
(struct mdp_hist_lut_data *)
&pp_ptr->data.lut_cfg_data.data);
break;
default:
break;
}
break;
case mdp_op_qseed_cfg:
ret = mdp4_qseed_cfg((struct mdp_qseed_cfg_data *)
&pp_ptr->data.qseed_cfg_data);
break;
#endif
case mdp_bl_scale_cfg:
ret = mdp_bl_scale_config(mfd, (struct mdp_bl_scale_data *)
&pp_ptr->data.bl_scale_data);
break;
default:
pr_warn("Unsupported request to MDP_PP IOCTL.\n");
ret = -EINVAL;
break;
}
return ret;
}
static int msmfb_handle_metadata_ioctl(struct msm_fb_data_type *mfd,
struct msmfb_metadata *metadata_ptr)
{
int ret;
switch (metadata_ptr->op) {
#ifdef CONFIG_FB_MSM_MDP40
case metadata_op_base_blend:
ret = mdp4_update_base_blend(mfd,
&metadata_ptr->data.blend_cfg);
break;
#endif
default:
pr_warn("Unsupported request to MDP META IOCTL.\n");
ret = -EINVAL;
break;
}
return ret;
}
static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
void __user *argp = (void __user *)arg;
struct fb_cursor cursor;
struct fb_cmap cmap;
struct mdp_histogram_data hist;
struct mdp_histogram_start_req hist_req;
uint32_t block;
#ifndef CONFIG_FB_MSM_MDP40
struct mdp_ccs ccs_matrix;
#else
struct mdp_csc csc_matrix;
#endif
struct mdp_page_protection fb_page_protection;
struct msmfb_mdp_pp mdp_pp;
struct msmfb_metadata mdp_metadata;
int ret = 0;
switch (cmd) {
#ifdef CONFIG_FB_MSM_OVERLAY
case MSMFB_OVERLAY_GET:
ret = msmfb_overlay_get(info, argp);
break;
case MSMFB_OVERLAY_SET:
ret = msmfb_overlay_set(info, argp);
break;
case MSMFB_OVERLAY_UNSET:
ret = msmfb_overlay_unset(info, argp);
break;
case MSMFB_OVERLAY_COMMIT:
down(&msm_fb_ioctl_ppp_sem);
ret = msmfb_overlay_commit(info, argp);
up(&msm_fb_ioctl_ppp_sem);
break;
case MSMFB_OVERLAY_PLAY:
ret = msmfb_overlay_play(info, argp);
break;
case MSMFB_OVERLAY_PLAY_ENABLE:
ret = msmfb_overlay_play_enable(info, argp);
break;
case MSMFB_OVERLAY_PLAY_WAIT:
ret = msmfb_overlay_play_wait(info, argp);
break;
case MSMFB_OVERLAY_BLT:
ret = msmfb_overlay_blt(info, argp);
break;
case MSMFB_OVERLAY_3D:
ret = msmfb_overlay_3d_sbys(info, argp);
break;
case MSMFB_MIXER_INFO:
ret = msmfb_mixer_info(info, argp);
break;
case MSMFB_WRITEBACK_INIT:
ret = msmfb_overlay_ioctl_writeback_init(info);
break;
case MSMFB_WRITEBACK_START:
ret = msmfb_overlay_ioctl_writeback_start(
info);
break;
case MSMFB_WRITEBACK_STOP:
ret = msmfb_overlay_ioctl_writeback_stop(
info);
break;
case MSMFB_WRITEBACK_QUEUE_BUFFER:
ret = msmfb_overlay_ioctl_writeback_queue_buffer(
info, argp);
break;
case MSMFB_WRITEBACK_DEQUEUE_BUFFER:
ret = msmfb_overlay_ioctl_writeback_dequeue_buffer(
info, argp);
break;
case MSMFB_WRITEBACK_TERMINATE:
ret = msmfb_overlay_ioctl_writeback_terminate(info);
break;
#endif
case MSMFB_VSYNC_CTRL:
case MSMFB_OVERLAY_VSYNC_CTRL:
down(&msm_fb_ioctl_ppp_sem);
if (mdp_rev >= MDP_REV_40)
ret = msmfb_overlay_vsync_ctrl(info, argp);
else
ret = msmfb_vsync_ctrl(info, argp);
up(&msm_fb_ioctl_ppp_sem);
break;
case MSMFB_BLIT:
down(&msm_fb_ioctl_ppp_sem);
ret = msmfb_blit(info, argp);
up(&msm_fb_ioctl_ppp_sem);
break;
/* Ioctl for setting ccs matrix from user space */
case MSMFB_SET_CCS_MATRIX:
#ifndef CONFIG_FB_MSM_MDP40
ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix));
if (ret) {
printk(KERN_ERR
"%s:MSMFB_SET_CCS_MATRIX ioctl failed \n",
__func__);
return ret;
}
down(&msm_fb_ioctl_ppp_sem);
if (ccs_matrix.direction == MDP_CCS_RGB2YUV)
mdp_ccs_rgb2yuv = ccs_matrix;
else
mdp_ccs_yuv2rgb = ccs_matrix;
msmfb_set_color_conv(&ccs_matrix) ;
up(&msm_fb_ioctl_ppp_sem);
#else
ret = copy_from_user(&csc_matrix, argp, sizeof(csc_matrix));
if (ret) {
pr_err("%s:MSMFB_SET_CSC_MATRIX ioctl failed\n",
__func__);
return ret;
}
down(&msm_fb_ioctl_ppp_sem);
msmfb_set_color_conv(&csc_matrix);
up(&msm_fb_ioctl_ppp_sem);
#endif
break;
/* Ioctl for getting ccs matrix to user space */
case MSMFB_GET_CCS_MATRIX:
#ifndef CONFIG_FB_MSM_MDP40
ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix)) ;
if (ret) {
printk(KERN_ERR
"%s:MSMFB_GET_CCS_MATRIX ioctl failed \n",
__func__);
return ret;
}
down(&msm_fb_ioctl_ppp_sem);
if (ccs_matrix.direction == MDP_CCS_RGB2YUV)
ccs_matrix = mdp_ccs_rgb2yuv;
else
ccs_matrix = mdp_ccs_yuv2rgb;
ret = copy_to_user(argp, &ccs_matrix, sizeof(ccs_matrix));
if (ret) {
printk(KERN_ERR
"%s:MSMFB_GET_CCS_MATRIX ioctl failed \n",
__func__);
return ret ;
}
up(&msm_fb_ioctl_ppp_sem);
#else
ret = -EINVAL;
#endif
break;
case MSMFB_GRP_DISP:
#ifdef CONFIG_FB_MSM_MDP22
{
unsigned long grp_id;
ret = copy_from_user(&grp_id, argp, sizeof(grp_id));
if (ret)
return ret;
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE);
writel(grp_id, MDP_FULL_BYPASS_WORD43);
mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF,
FALSE);
break;
}
#else
return -EFAULT;
#endif
case MSMFB_SUSPEND_SW_REFRESHER:
if (!mfd->panel_power_on)
return -EPERM;
mfd->sw_refreshing_enable = FALSE;
ret = msm_fb_stop_sw_refresher(mfd);
break;
case MSMFB_RESUME_SW_REFRESHER:
if (!mfd->panel_power_on)
return -EPERM;
mfd->sw_refreshing_enable = TRUE;
ret = msm_fb_resume_sw_refresher(mfd);
break;
case MSMFB_CURSOR:
ret = copy_from_user(&cursor, argp, sizeof(cursor));
if (ret)
return ret;
ret = msm_fb_cursor(info, &cursor);
break;
case MSMFB_SET_LUT:
ret = copy_from_user(&cmap, argp, sizeof(cmap));
if (ret)
return ret;
mutex_lock(&msm_fb_ioctl_lut_sem);
ret = msm_fb_set_lut(&cmap, info);
mutex_unlock(&msm_fb_ioctl_lut_sem);
break;
case MSMFB_HISTOGRAM:
if (!mfd->panel_power_on)
return -EPERM;
if (!mfd->do_histogram)
return -ENODEV;
ret = copy_from_user(&hist, argp, sizeof(hist));
if (ret)
return ret;
ret = mfd->do_histogram(info, &hist);
break;
case MSMFB_HISTOGRAM_START:
if (!mfd->panel_power_on)
return -EPERM;
if (!mfd->start_histogram)
return -ENODEV;
ret = copy_from_user(&hist_req, argp, sizeof(hist_req));
if (ret)
return ret;
ret = mfd->start_histogram(&hist_req);
break;
case MSMFB_HISTOGRAM_STOP:
if (!mfd->stop_histogram)
return -ENODEV;
ret = copy_from_user(&block, argp, sizeof(int));
if (ret)
return ret;
ret = mfd->stop_histogram(info, block);
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_NOTIFY_UPDATE:
ret = msmfb_notify_update(info, argp);
break;
case MSMFB_SET_PAGE_PROTECTION:
#if defined CONFIG_ARCH_QSD8X50 || defined CONFIG_ARCH_MSM8X60
ret = copy_from_user(&fb_page_protection, argp,
sizeof(fb_page_protection));
if (ret)
return ret;
/* Validate the proposed page protection settings. */
switch (fb_page_protection.page_protection) {
case MDP_FB_PAGE_PROTECTION_NONCACHED:
case MDP_FB_PAGE_PROTECTION_WRITECOMBINE:
case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE:
/* Write-back cache (read allocate) */
case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE:
/* Write-back cache (write allocate) */
case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE:
mfd->mdp_fb_page_protection =
fb_page_protection.page_protection;
break;
default:
ret = -EINVAL;
break;
}
#else
/*
* Don't allow caching until 7k DMA cache operations are
* available.
*/
ret = -EINVAL;
#endif
break;
case MSMFB_MDP_PP:
ret = copy_from_user(&mdp_pp, argp, sizeof(mdp_pp));
if (ret)
return ret;
ret = msmfb_handle_pp_ioctl(mfd, &mdp_pp);
break;
case MSMFB_METADATA_SET:
ret = copy_from_user(&mdp_metadata, argp, sizeof(mdp_metadata));
if (ret)
return ret;
ret = msmfb_handle_metadata_ioctl(mfd, &mdp_metadata);
break;
default:
MSM_FB_INFO("MDP: unknown ioctl (cmd=%x) received!\n", cmd);
ret = -EINVAL;
break;
}
return ret;
}
static int msm_fb_register_driver(void)
{
return platform_driver_register(&msm_fb_driver);
}
#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL
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);
int msm_fb_writeback_start(struct fb_info *info)
{
return mdp4_writeback_start(info);
}
EXPORT_SYMBOL(msm_fb_writeback_start);
int msm_fb_writeback_queue_buffer(struct fb_info *info,
struct msmfb_data *data)
{
return mdp4_writeback_queue_buffer(info, data);
}
EXPORT_SYMBOL(msm_fb_writeback_queue_buffer);
int msm_fb_writeback_dequeue_buffer(struct fb_info *info,
struct msmfb_data *data)
{
return mdp4_writeback_dequeue_buffer(info, data);
}
EXPORT_SYMBOL(msm_fb_writeback_dequeue_buffer);
int msm_fb_writeback_stop(struct fb_info *info)
{
return mdp4_writeback_stop(info);
}
EXPORT_SYMBOL(msm_fb_writeback_stop);
int msm_fb_writeback_init(struct fb_info *info)
{
return mdp4_writeback_init(info);
}
EXPORT_SYMBOL(msm_fb_writeback_init);
int msm_fb_writeback_terminate(struct fb_info *info)
{
return mdp4_writeback_terminate(info);
}
EXPORT_SYMBOL(msm_fb_writeback_terminate);
#endif
struct platform_device *msm_fb_add_device(struct platform_device *pdev)
{
struct msm_fb_panel_data *pdata;
struct platform_device *this_dev = NULL;
struct fb_info *fbi;
struct msm_fb_data_type *mfd = NULL;
u32 type, id, fb_num;
if (!pdev)
return NULL;
id = pdev->id;
pdata = pdev->dev.platform_data;
if (!pdata)
return NULL;
type = pdata->panel_info.type;
#if defined MSM_FB_NUM
/*
* over written fb_num which defined
* at panel_info
*
*/
if (type == HDMI_PANEL || type == DTV_PANEL ||
type == TV_PANEL || type == WRITEBACK_PANEL) {
if (hdmi_prim_display)
pdata->panel_info.fb_num = 2;
else
pdata->panel_info.fb_num = 1;
}
else
pdata->panel_info.fb_num = MSM_FB_NUM;
MSM_FB_INFO("setting pdata->panel_info.fb_num to %d. type: %d\n",
pdata->panel_info.fb_num, type);
#endif
fb_num = pdata->panel_info.fb_num;
if (fb_num <= 0)
return NULL;
if (fbi_list_index >= MAX_FBI_LIST) {
printk(KERN_ERR "msm_fb: no more framebuffer info list!\n");
return NULL;
}
/*
* alloc panel device data
*/
this_dev = msm_fb_device_alloc(pdata, type, id);
if (!this_dev) {
printk(KERN_ERR
"%s: msm_fb_device_alloc failed!\n", __func__);
return NULL;
}
/*
* alloc framebuffer info + par data
*/
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
if (fbi == NULL) {
platform_device_put(this_dev);
printk(KERN_ERR "msm_fb: can't alloca framebuffer info data!\n");
return NULL;
}
mfd = (struct msm_fb_data_type *)fbi->par;
mfd->key = MFD_KEY;
mfd->fbi = fbi;
mfd->panel.type = type;
mfd->panel.id = id;
mfd->fb_page = fb_num;
mfd->index = fbi_list_index;
mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE;
mfd->iclient = iclient;
/* link to the latest pdev */
mfd->pdev = this_dev;
mfd_list[mfd_list_index++] = mfd;
fbi_list[fbi_list_index++] = fbi;
/*
* set driver data
*/
platform_set_drvdata(this_dev, mfd);
if (platform_device_add(this_dev)) {
printk(KERN_ERR "msm_fb: platform_device_add failed!\n");
platform_device_put(this_dev);
framebuffer_release(fbi);
fbi_list_index--;
return NULL;
}
return this_dev;
}
EXPORT_SYMBOL(msm_fb_add_device);
int get_fb_phys_info(unsigned long *start, unsigned long *len, int fb_num,
int subsys_id)
{
struct fb_info *info;
struct msm_fb_data_type *mfd;
if (fb_num > MAX_FBI_LIST ||
(subsys_id != DISPLAY_SUBSYSTEM_ID &&
subsys_id != ROTATOR_SUBSYSTEM_ID)) {
pr_err("%s(): Invalid parameters\n", __func__);
return -1;
}
info = fbi_list[fb_num];
if (!info) {
pr_err("%s(): info is NULL\n", __func__);
return -1;
}
mfd = (struct msm_fb_data_type *)info->par;
if (subsys_id == DISPLAY_SUBSYSTEM_ID) {
if (mfd->display_iova)
*start = mfd->display_iova;
else
*start = info->fix.smem_start;
} else {
if (mfd->rotator_iova)
*start = mfd->rotator_iova;
else
*start = info->fix.smem_start;
}
*len = info->fix.smem_len;
return 0;
}
EXPORT_SYMBOL(get_fb_phys_info);
int __init msm_fb_init(void)
{
int rc = -ENODEV;
if (msm_fb_register_driver())
return rc;
#ifdef MSM_FB_ENABLE_DBGFS
{
struct dentry *root;
if ((root = msm_fb_get_debugfs_root()) != NULL) {
msm_fb_debugfs_file_create(root,
"msm_fb_msg_printing_level",
(u32 *) &msm_fb_msg_level);
msm_fb_debugfs_file_create(root,
"mddi_msg_printing_level",
(u32 *) &mddi_msg_level);
msm_fb_debugfs_file_create(root, "msm_fb_debug_enabled",
(u32 *) &msm_fb_debug_enabled);
}
}
#endif
return 0;
}
/* Called by v4l2 driver to enable/disable overlay pipe */
int msm_fb_v4l2_enable(struct mdp_overlay *req, bool enable, void **par)
{
int err = 0;
#ifdef CONFIG_FB_MSM_MDP40
struct mdp4_overlay_pipe *pipe;
if (enable) {
err = mdp4_v4l2_overlay_set(fbi_list[0], req, &pipe);
*(struct mdp4_overlay_pipe **)par = pipe;
} else {
pipe = *(struct mdp4_overlay_pipe **)par;
mdp4_v4l2_overlay_clear(pipe);
}
#else
#ifdef CONFIG_FB_MSM_MDP30
if (enable)
err = mdp_ppp_v4l2_overlay_set(fbi_list[0], req);
else
err = mdp_ppp_v4l2_overlay_clear();
#else
err = -EINVAL;
#endif
#endif
return err;
}
EXPORT_SYMBOL(msm_fb_v4l2_enable);
/* Called by v4l2 driver to provide a frame for display */
int msm_fb_v4l2_update(void *par,
unsigned long srcp0_addr, unsigned long srcp0_size,
unsigned long srcp1_addr, unsigned long srcp1_size,
unsigned long srcp2_addr, unsigned long srcp2_size)
{
#ifdef CONFIG_FB_MSM_MDP40
struct mdp4_overlay_pipe *pipe = (struct mdp4_overlay_pipe *)par;
return mdp4_v4l2_overlay_play(fbi_list[0], pipe,
srcp0_addr, srcp1_addr,
srcp2_addr);
#else
#ifdef CONFIG_FB_MSM_MDP30
return mdp_ppp_v4l2_overlay_play(fbi_list[0],
srcp0_addr, srcp0_size,
srcp1_addr, srcp1_size);
#else
return -EINVAL;
#endif
#endif
}
EXPORT_SYMBOL(msm_fb_v4l2_update);
module_init(msm_fb_init);