![Stepan Moskovchenko](/assets/img/avatar_default.png)
Qualcomm development of the MSM SOC framebuffer driver has diverged significantly from the driver used by Android. This is a snapshot of our current driver, in all it's agony. We are putting this in staging to help with the process of converging the two drivers. At this point, the driver has been tested only in dumb framebuffer mode. Signed-off-by: Stepan Moskovchenko <stepanm@codeaurora.org> Signed-off-by: David Brown <davidb@codeaurora.org> Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org> [dwalker@codeaurora.org: added a small compile fix and TODO.] Signed-off-by: Daniel Walker <dwalker@codeaurora.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2355 lines
58 KiB
C
2355 lines
58 KiB
C
/*
|
|
*
|
|
* Core MSM framebuffer driver.
|
|
*
|
|
* Copyright (C) 2007 Google Incorporated
|
|
* Copyright (c) 2008-2010, 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 "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 <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/leds.h>
|
|
#include <asm/dma-mapping.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_LOGO
|
|
#define INIT_IMAGE_FILE "/logo.rle"
|
|
extern int load_565rle_image(char *filename);
|
|
#endif
|
|
|
|
|
|
#define pgprot_noncached(prot) \
|
|
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
|
|
#define pgprot_writecombine(prot) \
|
|
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)
|
|
#define pgprot_device(prot) \
|
|
__pgprot_modify(prot, L_PTE_MT_MASK|L_PTE_EXEC, L_PTE_MT_DEV_NONSHARED)
|
|
#define pgprot_writethroughcache(prot) \
|
|
__pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITETHROUGH)
|
|
#define pgprot_writebackcache(prot) \
|
|
__pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITEBACK)
|
|
#define pgprot_writebackwacache(prot) \
|
|
__pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITEALLOC)
|
|
|
|
static unsigned char *fbram;
|
|
static unsigned char *fbram_phys;
|
|
static int fbram_size;
|
|
|
|
static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST];
|
|
static int pdev_list_cnt;
|
|
|
|
int vsync_mode = 1;
|
|
|
|
#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
|
|
};
|
|
|
|
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_resume_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);
|
|
|
|
#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];
|
|
|
|
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;
|
|
|
|
msm_fb_set_backlight(mfd, bl_lvl, 1);
|
|
}
|
|
|
|
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;
|
|
|
|
int msm_fb_detect_client(const char *name)
|
|
{
|
|
int ret = -EPERM;
|
|
#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT
|
|
u32 id;
|
|
#endif
|
|
|
|
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 int msm_fb_probe(struct platform_device *pdev)
|
|
{
|
|
struct msm_fb_data_type *mfd;
|
|
int rc;
|
|
|
|
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 = ioremap((unsigned long)fbram_phys, fbram_size);
|
|
|
|
if (!fbram) {
|
|
printk(KERN_ERR "fbram ioremap failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
MSM_FB_INFO("msm_fb_probe: phy_Addr = 0x%x virt = 0x%x\n",
|
|
(int)fbram_phys, (int)fbram);
|
|
|
|
msm_fb_resource_initialized = 1;
|
|
return 0;
|
|
}
|
|
|
|
if (!msm_fb_resource_initialized)
|
|
return -EPERM;
|
|
|
|
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
if (mfd->key != MFD_KEY)
|
|
return -EINVAL;
|
|
|
|
if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST)
|
|
return -ENOMEM;
|
|
|
|
mfd->panel_info.frame_count = 0;
|
|
mfd->bl_level = mfd->panel_info.bl_max;
|
|
|
|
if (mfd->panel_info.type == LCDC_PANEL)
|
|
mfd->allow_set_offset =
|
|
msm_fb_pdata->allow_set_offset != NULL ?
|
|
msm_fb_pdata->allow_set_offset() : 0;
|
|
else
|
|
mfd->allow_set_offset = 0;
|
|
|
|
rc = msm_fb_register(mfd);
|
|
if (rc)
|
|
return rc;
|
|
|
|
#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;
|
|
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);
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
|
|
acquire_console_sem();
|
|
fb_set_suspend(mfd->fbi, 1);
|
|
|
|
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, 0);
|
|
} else {
|
|
pdev->dev.power.power_state = state;
|
|
}
|
|
|
|
release_console_sem();
|
|
return ret;
|
|
}
|
|
#else
|
|
#define msm_fb_suspend NULL
|
|
#endif
|
|
|
|
static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((!mfd) || (mfd->key != MFD_KEY))
|
|
return 0;
|
|
|
|
/*
|
|
* suspend this channel
|
|
*/
|
|
mfd->suspend.sw_refreshing_enable = mfd->sw_refreshing_enable;
|
|
mfd->suspend.op_enable = mfd->op_enable;
|
|
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;
|
|
}
|
|
|
|
#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;
|
|
|
|
acquire_console_sem();
|
|
ret = msm_fb_resume_sub(mfd);
|
|
pdev->dev.power.power_state = PMSG_ON;
|
|
fb_set_suspend(mfd->fbi, 1);
|
|
release_console_sem();
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
#define msm_fb_resume NULL
|
|
#endif
|
|
|
|
static int msm_fb_resume_sub(struct msm_fb_data_type *mfd)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((!mfd) || (mfd->key != MFD_KEY))
|
|
return 0;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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",
|
|
},
|
|
};
|
|
|
|
#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);
|
|
msm_fb_suspend_sub(mfd);
|
|
}
|
|
|
|
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);
|
|
msm_fb_resume_sub(mfd);
|
|
}
|
|
#endif
|
|
|
|
void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl, u32 save)
|
|
{
|
|
struct msm_fb_panel_data *pdata;
|
|
|
|
pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
|
|
|
|
if ((pdata) && (pdata->set_backlight)) {
|
|
down(&mfd->sem);
|
|
if ((bkl_lvl != mfd->bl_level) || (!save)) {
|
|
u32 old_lvl;
|
|
|
|
old_lvl = mfd->bl_level;
|
|
mfd->bl_level = bkl_lvl;
|
|
pdata->set_backlight(mfd);
|
|
|
|
if (!save)
|
|
mfd->bl_level = old_lvl;
|
|
}
|
|
up(&mfd->sem);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
mdelay(100);
|
|
ret = pdata->on(mfd->pdev);
|
|
if (ret == 0) {
|
|
mfd->panel_power_on = TRUE;
|
|
|
|
msm_fb_set_backlight(mfd,
|
|
mfd->bl_level, 0);
|
|
|
|
/* 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;
|
|
|
|
mdelay(100);
|
|
ret = pdata->off(mfd->pdev);
|
|
if (ret)
|
|
mfd->panel_power_on = curr_pwr_state;
|
|
|
|
msm_fb_set_backlight(mfd, 0, 0);
|
|
mfd->op_enable = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 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;
|
|
|
|
/*
|
|
* 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 = 3;
|
|
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;
|
|
}
|
|
|
|
/* 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 (mfd->index == 0)
|
|
fix->line_length = ALIGN(panel_info->xres * bpp, 32);
|
|
else
|
|
fix->line_length = panel_info->xres * bpp;
|
|
|
|
fix->smem_len = fix->line_length * panel_info->yres * mfd->fb_page;
|
|
|
|
mfd->var_xres = panel_info->xres;
|
|
mfd->var_yres = panel_info->yres;
|
|
|
|
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;
|
|
var->bits_per_pixel = bpp * 8, /* FrameBuffer color depth */
|
|
/*
|
|
* id field for fb app
|
|
*/
|
|
id = (int *)&mfd->panel;
|
|
|
|
#if defined(CONFIG_FB_MSM_MDP22)
|
|
snprintf(fix->id, sizeof(fix->id), "msmfb22_%x", (__u32) *id);
|
|
#elif defined(CONFIG_FB_MSM_MDP30)
|
|
snprintf(fix->id, sizeof(fix->id), "msmfb30_%x", (__u32) *id);
|
|
#elif defined(CONFIG_FB_MSM_MDP31)
|
|
snprintf(fix->id, sizeof(fix->id), "msmfb31_%x", (__u32) *id);
|
|
#elif defined(CONFIG_FB_MSM_MDP40)
|
|
snprintf(fix->id, sizeof(fix->id), "msmfb40_%x", (__u32) *id);
|
|
#else
|
|
error CONFIG_FB_MSM_MDP undefined !
|
|
#endif
|
|
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);
|
|
init_MUTEX(&mfd->sem);
|
|
|
|
fbram_offset = PAGE_ALIGN((int)fbram)-(int)fbram;
|
|
fbram += fbram_offset;
|
|
fbram_phys += fbram_offset;
|
|
fbram_size -= fbram_offset;
|
|
|
|
if (fbram_size < fix->smem_len) {
|
|
printk(KERN_ERR "error: no more framebuffer memory!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fbi->screen_base = fbram;
|
|
fbi->fix.smem_start = (unsigned long)fbram_phys;
|
|
|
|
memset(fbi->screen_base, 0x0, fix->smem_len);
|
|
|
|
mfd->op_enable = TRUE;
|
|
mfd->panel_power_on = FALSE;
|
|
|
|
/* cursor memory allocation */
|
|
if (mfd->cursor_update) {
|
|
mfd->cursor_buf = dma_alloc_coherent(NULL,
|
|
MDP_CURSOR_SIZE,
|
|
(dma_addr_t *) &mfd->cursor_buf_phys,
|
|
GFP_KERNEL);
|
|
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
|
|
if (!load_565rle_image(INIT_IMAGE_FILE)) ; /* Flip buffer */
|
|
#endif
|
|
ret = 0;
|
|
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
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;
|
|
|
|
if (!mfd->ref_cnt) {
|
|
mdp_set_dma_pan_info(info, NULL, TRUE);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
DECLARE_MUTEX(msm_fb_pan_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 ((!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;
|
|
}
|
|
|
|
/* Flip */
|
|
/* A constant value is used to indicate that we should change the DMA
|
|
output buffer instead of just panning */
|
|
|
|
if (var->reserved[0] == 0x466c6970) {
|
|
unsigned long length, address;
|
|
struct file *p_src_file;
|
|
struct mdp_img imgdata;
|
|
int bpp;
|
|
|
|
if (mfd->allow_set_offset) {
|
|
imgdata.memory_id = var->reserved[1];
|
|
imgdata.priv = var->reserved[2];
|
|
|
|
/* If there is no memory ID then we want to reset back
|
|
to the original fb visibility */
|
|
if (var->reserved[1]) {
|
|
if (var->reserved[4] == MDP_BLIT_SRC_GEM) {
|
|
panic("waaaaaaaaaaaaaah");
|
|
if ( /*get_gem_img(&imgdata,
|
|
(unsigned long *) &address,
|
|
&length)*/ -1 < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
/*get_img(&imgdata, info, &address,
|
|
&length, &p_src_file);*/
|
|
panic("waaaaaah");
|
|
}
|
|
mfd->ibuf.visible_swapped = TRUE;
|
|
} else {
|
|
/* Flip back to the original address
|
|
adjusted for xoffset and yoffset */
|
|
|
|
bpp = info->var.bits_per_pixel / 8;
|
|
address = (unsigned long) info->fix.smem_start;
|
|
address += info->var.xoffset * bpp +
|
|
info->var.yoffset * info->fix.line_length;
|
|
|
|
mfd->ibuf.visible_swapped = FALSE;
|
|
}
|
|
|
|
mdp_set_offset_info(info, address,
|
|
(var->activate == FB_ACTIVATE_VBL));
|
|
|
|
mfd->dma_fnc(mfd);
|
|
return 0;
|
|
} else
|
|
return -EINVAL;
|
|
}
|
|
|
|
down(&msm_fb_pan_sem);
|
|
mdp_set_dma_pan_info(info, dirtyPtr,
|
|
(var->activate == FB_ACTIVATE_VBL));
|
|
mdp_dma_pan_update(info);
|
|
up(&msm_fb_pan_sem);
|
|
|
|
++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;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((var->xres_virtual <= 0) || (var->yres_virtual <= 0))
|
|
return -EINVAL;
|
|
|
|
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 > mfd->panel_info.xres) ||
|
|
(var->yres > mfd->panel_info.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;
|
|
}
|
|
|
|
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;
|
|
|
|
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)))) {
|
|
mfd->var_xres = var->xres;
|
|
mfd->var_yres = var->yres;
|
|
mfd->var_pixclock = var->pixclock;
|
|
blank = 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void mdp_ppp_put_img(struct file *p_src_file, struct file *p_dst_file)
|
|
{
|
|
#ifdef CONFIG_ANDROID_PMEM
|
|
if (p_src_file)
|
|
put_pmem_file(p_src_file);
|
|
if (p_dst_file)
|
|
put_pmem_file(p_dst_file);
|
|
#endif
|
|
}
|
|
|
|
int mdp_blit(struct fb_info *info, struct mdp_blit_req *req)
|
|
{
|
|
int ret;
|
|
struct file *p_src_file = 0, *p_dst_file = 0;
|
|
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;
|
|
|
|
ret = mdp_ppp_blit(info, req, &p_src_file, &p_dst_file);
|
|
mdp_ppp_put_img(p_src_file, p_dst_file);
|
|
return ret;
|
|
}
|
|
|
|
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.
|
|
*/
|
|
|
|
char * const pmem_start = info->screen_base;
|
|
/* int bytes_per_pixel = mdp_get_bytes_per_pixel(img->format);
|
|
unsigned long start = (unsigned long)pmem_start + img->offset +
|
|
(img->width * rect->y + rect->x) * bytes_per_pixel;
|
|
size_t size = ((rect->h - 1) * img->width + rect->w) * bytes_per_pixel;
|
|
(*dma_barrier_fp) ((void *) start, size);
|
|
*/
|
|
panic("waaaaah");
|
|
}
|
|
|
|
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)
|
|
{
|
|
#warning this
|
|
// dma_cache_pre_ops(start, size, DMA_TO_DEVICE);
|
|
}
|
|
|
|
static inline void msm_dma_fromdevice_wb_pre(void *start, size_t size)
|
|
{
|
|
#warning this
|
|
// 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)
|
|
{
|
|
#warning this
|
|
// dma_cache_post_ops(start, size, DMA_FROM_DEVICE);
|
|
}
|
|
|
|
static inline void msm_dma_todevice_wb_post(void *start, size_t size)
|
|
{
|
|
#warning this
|
|
// dma_cache_post_ops(start, size, DMA_TO_DEVICE);
|
|
}
|
|
|
|
static inline void msm_dma_fromdevice_wb_post(void *start, size_t size)
|
|
{
|
|
#warning this
|
|
// 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
|
|
}
|
|
|
|
#ifdef CONFIG_MDP_PPP_ASYNC_OP
|
|
void msm_fb_ensure_mem_coherency_after_dma(struct fb_info *info,
|
|
struct mdp_blit_req *req_list, int req_list_count)
|
|
{
|
|
BUG_ON(!info);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
static int msmfb_async_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;
|
|
|
|
/* 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;
|
|
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)) {
|
|
int ret = 0;
|
|
struct mdp_ppp_djob *job = NULL;
|
|
|
|
if (unlikely(req_list[i].src_rect.h == 0 ||
|
|
req_list[i].src_rect.w == 0)) {
|
|
MSM_FB_ERR("mpd_ppp: "
|
|
"src img of zero size!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(req_list[i].dst_rect.h == 0 ||
|
|
req_list[i].dst_rect.w == 0))
|
|
continue;
|
|
|
|
/* create a new display job */
|
|
job = mdp_ppp_new_djob();
|
|
if (unlikely(!job))
|
|
return -ENOMEM;
|
|
|
|
job->info = info;
|
|
memcpy(&job->req, &req_list[i],
|
|
sizeof(struct mdp_blit_req));
|
|
|
|
/* Do the actual blit. */
|
|
ret = mdp_ppp_blit(info, &job->req,
|
|
&job->p_src_file, &job->p_dst_file);
|
|
|
|
/*
|
|
* Note that early returns don't guarantee
|
|
* memory coherency.
|
|
*/
|
|
if (ret || mdp_ppp_get_ret_code()) {
|
|
mdp_ppp_clear_curr_djob();
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Go to next window of requests. */
|
|
count -= req_list_count;
|
|
p += sizeof(struct mdp_blit_req)*req_list_count;
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* 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;
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
#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 \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_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_play(struct fb_info *info, unsigned long *argp)
|
|
{
|
|
int ret;
|
|
struct msmfb_overlay_data req;
|
|
struct file *p_src_file = 0;
|
|
|
|
ret = copy_from_user(&req, argp, sizeof(req));
|
|
if (ret) {
|
|
printk(KERN_ERR "%s:msmfb_overlay_play ioctl failed \n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = mdp4_overlay_play(info, &req, &p_src_file);
|
|
|
|
if (p_src_file)
|
|
put_pmem_file(p_src_file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
DECLARE_MUTEX(msm_fb_ioctl_ppp_sem);
|
|
DEFINE_MUTEX(msm_fb_ioctl_lut_sem);
|
|
DEFINE_MUTEX(msm_fb_ioctl_hist_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);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
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 hist;
|
|
#ifndef CONFIG_FB_MSM_MDP40
|
|
struct mdp_ccs ccs_matrix;
|
|
#endif
|
|
struct mdp_page_protection fb_page_protection;
|
|
int ret = 0;
|
|
|
|
if (!mfd->op_enable)
|
|
return -EPERM;
|
|
|
|
switch (cmd) {
|
|
#ifdef CONFIG_FB_MSM_OVERLAY
|
|
case MSMFB_OVERLAY_GET:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
ret = msmfb_overlay_get(info, argp);
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
case MSMFB_OVERLAY_SET:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
ret = msmfb_overlay_set(info, argp);
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
case MSMFB_OVERLAY_UNSET:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
ret = msmfb_overlay_unset(info, argp);
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
case MSMFB_OVERLAY_PLAY:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
ret = msmfb_overlay_play(info, argp);
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
#endif
|
|
case MSMFB_BLIT:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
#ifdef CONFIG_MDP_PPP_ASYNC_OP
|
|
ret = msmfb_async_blit(info, argp);
|
|
mdp_ppp_wait(); /* Wait for all blits to be finished. */
|
|
#else
|
|
ret = msmfb_blit(info, argp);
|
|
#endif
|
|
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 = -EINVAL;
|
|
#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;
|
|
|
|
#ifdef CONFIG_MDP_PPP_ASYNC_OP
|
|
case MSMFB_ASYNC_BLIT:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
ret = msmfb_async_blit(info, argp);
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
|
|
case MSMFB_BLIT_FLUSH:
|
|
down(&msm_fb_ioctl_ppp_sem);
|
|
mdp_ppp_wait();
|
|
up(&msm_fb_ioctl_ppp_sem);
|
|
break;
|
|
#endif
|
|
|
|
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->do_histogram)
|
|
return -ENODEV;
|
|
|
|
ret = copy_from_user(&hist, argp, sizeof(hist));
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&msm_fb_ioctl_hist_sem);
|
|
ret = mfd->do_histogram(info, &hist);
|
|
mutex_unlock(&msm_fb_ioctl_hist_sem);
|
|
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_SET_PAGE_PROTECTION:
|
|
#ifdef CONFIG_ARCH_QSD8X50
|
|
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;
|
|
|
|
default:
|
|
MSM_FB_INFO("MDP: unknown ioctl (cmd=%d) received!\n", cmd);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int msm_fb_register_driver(void)
|
|
{
|
|
return platform_driver_register(&msm_fb_driver);
|
|
}
|
|
|
|
void 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;
|
|
id = pdev->id;
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
if (!pdata)
|
|
return;
|
|
type = pdata->panel_info.type;
|
|
fb_num = pdata->panel_info.fb_num;
|
|
|
|
if (fb_num <= 0)
|
|
return;
|
|
|
|
if (fbi_list_index >= MAX_FBI_LIST) {
|
|
printk(KERN_ERR "msm_fb: no more framebuffer info list!\n");
|
|
return;
|
|
}
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_add_device);
|
|
|
|
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;
|
|
}
|
|
|
|
module_init(msm_fb_init);
|