android_kernel_google_msm/drivers/video/msm/mdss/mdss_fb.c
Hanumant Singh 19cc56631b ion: Port heap mask change to ion
Heap mask field passes as argument at allocation time to specify ion
heap.

Change-Id: I6951da81b11bfcb0e006d2539c1707aabbb12db6
Signed-off-by: Hanumant Singh <hanumant@codeaurora.org>
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2013-03-07 15:22:59 -08:00

1249 lines
30 KiB
C

/*
* Core MDSS 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.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/android_pmem.h>
#include <linux/bootmem.h>
#include <linux/console.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/memory.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/msm_mdp.h>
#include <linux/proc_fs.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <mach/board.h>
#include "mdss_fb.h"
#include "mdss_mdp.h"
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
#define MDSS_FB_NUM 3
#else
#define MDSS_FB_NUM 2
#endif
#define MAX_FBI_LIST 32
static struct fb_info *fbi_list[MAX_FBI_LIST];
static int fbi_list_index;
static u32 mdss_fb_pseudo_palette[16] = {
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
};
static int mdss_fb_register(struct msm_fb_data_type *mfd);
static int mdss_fb_open(struct fb_info *info, int user);
static int mdss_fb_release(struct fb_info *info, int user);
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info);
static int mdss_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info);
static int mdss_fb_set_par(struct fb_info *info);
static int mdss_fb_blank_sub(int blank_mode, struct fb_info *info,
int op_enable);
static int mdss_fb_suspend_sub(struct msm_fb_data_type *mfd);
static int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg);
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma);
#define MAX_BACKLIGHT_BRIGHTNESS 255
static int lcd_backlight_registered;
static void mdss_fb_set_bl_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(led_cdev->dev->parent);
int bl_lvl;
if (value > 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;
mdss_fb_set_backlight(mfd, bl_lvl);
}
static struct led_classdev backlight_led = {
.name = "lcd-backlight",
.brightness = MAX_BACKLIGHT_BRIGHTNESS,
.brightness_set = mdss_fb_set_bl_brightness,
};
static ssize_t mdss_fb_get_type(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret = 0;
struct fb_info *fbi = dev_get_drvdata(dev);
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
switch (mfd->panel_info.type) {
case NO_PANEL:
ret = snprintf(buf, PAGE_SIZE, "no panel\n");
break;
case HDMI_PANEL:
ret = snprintf(buf, PAGE_SIZE, "hdmi panel\n");
break;
case LVDS_PANEL:
ret = snprintf(buf, PAGE_SIZE, "lvds panel\n");
break;
case DTV_PANEL:
ret = snprintf(buf, PAGE_SIZE, "dtv panel\n");
break;
case MIPI_VIDEO_PANEL:
ret = snprintf(buf, PAGE_SIZE, "mipi dsi video panel\n");
break;
case MIPI_CMD_PANEL:
ret = snprintf(buf, PAGE_SIZE, "mipi dsi cmd panel\n");
break;
case WRITEBACK_PANEL:
ret = snprintf(buf, PAGE_SIZE, "writeback panel\n");
break;
default:
ret = snprintf(buf, PAGE_SIZE, "unknown panel\n");
break;
}
return ret;
}
static DEVICE_ATTR(mdss_fb_type, S_IRUGO, mdss_fb_get_type, NULL);
static struct attribute *mdss_fb_attrs[] = {
&dev_attr_mdss_fb_type.attr,
NULL,
};
static struct attribute_group mdss_fb_attr_group = {
.attrs = mdss_fb_attrs,
};
static int mdss_fb_create_sysfs(struct msm_fb_data_type *mfd)
{
int rc;
rc = sysfs_create_group(&mfd->fbi->dev->kobj, &mdss_fb_attr_group);
if (rc)
pr_err("sysfs group creation failed, rc=%d\n", rc);
return rc;
}
static void mdss_fb_remove_sysfs(struct msm_fb_data_type *mfd)
{
sysfs_remove_group(&mfd->fbi->dev->kobj, &mdss_fb_attr_group);
}
static int mdss_fb_probe(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = NULL;
struct mdss_panel_data *pdata;
struct fb_info *fbi;
int rc;
if (fbi_list_index >= MAX_FBI_LIST)
return -ENOMEM;
pdata = dev_get_platdata(&pdev->dev);
if (!pdata)
return -ENODEV;
/*
* alloc framebuffer info + par data
*/
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), &pdev->dev);
if (fbi == NULL) {
pr_err("can't allocate framebuffer info data!\n");
return -ENOMEM;
}
mfd = (struct msm_fb_data_type *)fbi->par;
mfd->key = MFD_KEY;
mfd->fbi = fbi;
mfd->panel_info = pdata->panel_info;
mfd->panel.type = pdata->panel_info.type;
mfd->panel.id = mfd->index;
mfd->fb_page = MDSS_FB_NUM;
mfd->index = fbi_list_index;
mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE;
mfd->panel_info.frame_count = 0;
mfd->bl_level = 0;
mfd->fb_imgType = MDP_RGBA_8888;
mfd->iclient = msm_ion_client_create(-1, pdev->name);
if (IS_ERR(mfd->iclient))
mfd->iclient = NULL;
mfd->pdev = pdev;
mutex_init(&mfd->lock);
fbi_list[fbi_list_index++] = fbi;
platform_set_drvdata(pdev, mfd);
rc = mdss_fb_register(mfd);
if (rc)
return rc;
rc = pm_runtime_set_active(mfd->fbi->dev);
if (rc < 0)
pr_err("pm_runtime: fail to set active.\n");
pm_runtime_enable(mfd->fbi->dev);
/* android supports only one lcd-backlight/lcd for now */
if (!lcd_backlight_registered) {
if (led_classdev_register(&pdev->dev, &backlight_led))
pr_err("led_classdev_register failed\n");
else
lcd_backlight_registered = 1;
}
mdss_fb_create_sysfs(mfd);
return 0;
}
static int mdss_fb_remove(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd;
mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev);
mdss_fb_remove_sysfs(mfd);
pm_runtime_disable(mfd->fbi->dev);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
if (mdss_fb_suspend_sub(mfd))
pr_err("msm_fb_remove: can't stop the device %d\n",
mfd->index);
/* remove /dev/fb* */
unregister_framebuffer(mfd->fbi);
if (lcd_backlight_registered) {
lcd_backlight_registered = 0;
led_classdev_unregister(&backlight_led);
}
return 0;
}
static int mdss_fb_suspend_sub(struct msm_fb_data_type *mfd)
{
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
pr_debug("mdss_fb suspend index=%d\n", mfd->index);
mfd->suspend.op_enable = mfd->op_enable;
mfd->suspend.panel_power_on = mfd->panel_power_on;
if (mfd->op_enable) {
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi,
mfd->suspend.op_enable);
if (ret) {
pr_warn("can't turn off display!\n");
return ret;
}
mfd->op_enable = false;
}
return 0;
}
static int mdss_fb_resume_sub(struct msm_fb_data_type *mfd)
{
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
pr_debug("mdss_fb resume index=%d\n", mfd->index);
/* resume state var recover */
mfd->op_enable = mfd->suspend.op_enable;
if (mfd->suspend.panel_power_on) {
ret = mdss_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi,
mfd->op_enable);
if (ret)
pr_warn("can't turn on display!\n");
}
return ret;
}
int mdss_fb_suspend_all(void)
{
struct fb_info *fbi;
int ret, i;
int result = 0;
console_lock();
for (i = 0; i < fbi_list_index; i++) {
fbi = fbi_list[i];
fb_set_suspend(fbi, FBINFO_STATE_SUSPENDED);
ret = mdss_fb_suspend_sub(fbi->par);
if (ret != 0) {
fb_set_suspend(fbi, FBINFO_STATE_RUNNING);
result = ret;
}
}
console_unlock();
return result;
}
int mdss_fb_resume_all(void)
{
struct fb_info *fbi;
int ret, i;
int result = 0;
console_lock();
for (i = 0; i < fbi_list_index; i++) {
fbi = fbi_list[i];
ret = mdss_fb_resume_sub(fbi->par);
if (ret == 0)
fb_set_suspend(fbi, FBINFO_STATE_RUNNING);
}
console_unlock();
return result;
}
#if defined(CONFIG_PM) && defined(CONFIG_SUSPEND)
static int mdss_fb_ext_suspend(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
if (mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL)
ret = mdss_fb_suspend_sub(mfd);
return ret;
}
static int mdss_fb_ext_resume(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
int ret = 0;
if ((!mfd) || (mfd->key != MFD_KEY))
return 0;
if (mfd->panel_info.type == HDMI_PANEL ||
mfd->panel_info.type == DTV_PANEL)
ret = mdss_fb_resume_sub(mfd);
return ret;
}
#else
#define mdss_fb_ext_suspend NULL
#define mdss_fb_ext_resume NULL
#endif
static const struct dev_pm_ops mdss_fb_dev_pm_ops = {
.suspend = mdss_fb_ext_suspend,
.resume = mdss_fb_ext_resume,
};
static struct platform_driver mdss_fb_driver = {
.probe = mdss_fb_probe,
.remove = mdss_fb_remove,
.driver = {
.name = "mdss_fb",
.pm = &mdss_fb_dev_pm_ops,
},
};
static int unset_bl_level, bl_updated;
static int bl_level_old;
void mdss_fb_set_backlight(struct msm_fb_data_type *mfd, u32 bkl_lvl)
{
struct mdss_panel_data *pdata;
if (!mfd->panel_power_on || !bl_updated) {
unset_bl_level = bkl_lvl;
return;
} else {
unset_bl_level = 0;
}
pdata = dev_get_platdata(&mfd->pdev->dev);
if ((pdata) && (pdata->set_backlight)) {
mutex_lock(&mfd->lock);
if (bl_level_old == bkl_lvl) {
mutex_unlock(&mfd->lock);
return;
}
mfd->bl_level = bkl_lvl;
pdata->set_backlight(mfd->bl_level);
bl_level_old = mfd->bl_level;
mutex_unlock(&mfd->lock);
}
}
void mdss_fb_update_backlight(struct msm_fb_data_type *mfd)
{
struct mdss_panel_data *pdata;
if (unset_bl_level && !bl_updated) {
pdata = dev_get_platdata(&mfd->pdev->dev);
if ((pdata) && (pdata->set_backlight)) {
mutex_lock(&mfd->lock);
mfd->bl_level = unset_bl_level;
pdata->set_backlight(mfd->bl_level);
bl_level_old = unset_bl_level;
mutex_unlock(&mfd->lock);
bl_updated = 1;
}
}
}
static int mdss_fb_blank_sub(int blank_mode, struct fb_info *info,
int op_enable)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int ret = 0;
if (!op_enable)
return -EPERM;
switch (blank_mode) {
case FB_BLANK_UNBLANK:
if (!mfd->panel_power_on) {
msleep(20);
ret = mfd->on_fnc(mfd);
if (ret == 0)
mfd->panel_power_on = true;
}
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;
bl_updated = 0;
msleep(20);
ret = mfd->off_fnc(mfd);
if (ret)
mfd->panel_power_on = curr_pwr_state;
mfd->op_enable = true;
}
break;
}
return ret;
}
static int mdss_fb_blank(int blank_mode, struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
return mdss_fb_blank_sub(blank_mode, info, mfd->op_enable);
}
/*
* Custom Framebuffer mmap() function for MSM driver.
* Differs from standard mmap() function by allowing for customized
* page-protection.
*/
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
/* Get frame buffer memory range. */
unsigned long start = info->fix.smem_start;
u32 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (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 mdss_fb_ops = {
.owner = THIS_MODULE,
.fb_open = mdss_fb_open,
.fb_release = mdss_fb_release,
.fb_check_var = mdss_fb_check_var, /* vinfo check */
.fb_set_par = mdss_fb_set_par, /* set the video mode */
.fb_blank = mdss_fb_blank, /* blank display */
.fb_pan_display = mdss_fb_pan_display, /* pan display */
.fb_ioctl = mdss_fb_ioctl, /* perform fb specific ioctl */
.fb_mmap = mdss_fb_mmap,
};
static u32 mdss_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 mdss_fb_alloc_fbmem(struct msm_fb_data_type *mfd)
{
void *virt = NULL;
unsigned long phys = 0;
size_t size;
size = PAGE_ALIGN(mfd->fbi->fix.line_length * mfd->panel_info.yres);
size *= mfd->fb_page;
if (mfd->index == 0) {
struct ion_client *iclient = mfd->iclient;
if (iclient) {
mfd->ihdl = ion_alloc(iclient, size, SZ_4K,
ION_HEAP(ION_CP_MM_HEAP_ID) |
ION_HEAP(ION_SF_HEAP_ID), 0);
if (IS_ERR_OR_NULL(mfd->ihdl)) {
pr_err("unable to alloc fbmem from ion (%p)\n",
mfd->ihdl);
return -ENOMEM;
}
virt = ion_map_kernel(iclient, mfd->ihdl, 0);
ion_phys(iclient, mfd->ihdl, &phys, &size);
} else {
virt = dma_alloc_coherent(NULL, size,
(dma_addr_t *) &phys, GFP_KERNEL);
if (!virt) {
pr_err("unable to alloc fbmem size=%u\n", size);
return -ENOMEM;
}
}
pr_info("allocating %u bytes at %p (%lx phys) for fb %d\n",
size, virt, phys, mfd->index);
} else {
pr_debug("no memory allocated for fb%d\n", mfd->index);
size = 0;
}
mfd->fbi->screen_base = virt;
mfd->fbi->fix.smem_start = phys;
mfd->fbi->fix.smem_len = size;
return 0;
}
static int mdss_fb_register(struct msm_fb_data_type *mfd)
{
int ret = -ENODEV;
int bpp;
struct mdss_panel_info *panel_info = &mfd->panel_info;
struct fb_info *fbi = mfd->fbi;
struct fb_fix_screeninfo *fix;
struct fb_var_screeninfo *var;
int *id;
/*
* fb info initialization
*/
fix = &fbi->fix;
var = &fbi->var;
fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */
fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */
fix->ywrapstep = 0; /* No support */
fix->mmio_start = 0; /* No MMIO Address */
fix->mmio_len = 0; /* No MMIO Address */
fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */
var->xoffset = 0, /* Offset from virtual to visible */
var->yoffset = 0, /* resolution */
var->grayscale = 0, /* No graylevels */
var->nonstd = 0, /* standard pixel format */
var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */
var->height = -1, /* height of picture in mm */
var->width = -1, /* width of picture in mm */
var->accel_flags = 0, /* acceleration flags */
var->sync = 0, /* see FB_SYNC_* */
var->rotate = 0, /* angle we rotate counter clockwise */
mfd->op_enable = false;
switch (mfd->fb_imgType) {
case MDP_RGB_565:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 0;
var->green.offset = 5;
var->red.offset = 11;
var->blue.length = 5;
var->green.length = 6;
var->red.length = 5;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 2;
break;
case MDP_RGB_888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 0;
var->green.offset = 8;
var->red.offset = 16;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 3;
break;
case MDP_ARGB_8888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 0;
var->green.offset = 8;
var->red.offset = 16;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 24;
var->transp.length = 8;
bpp = 4;
break;
case MDP_RGBA_8888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 8;
var->green.offset = 16;
var->red.offset = 24;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 8;
bpp = 4;
break;
case MDP_YCRYCB_H2V1:
fix->type = FB_TYPE_INTERLEAVED_PLANES;
fix->xpanstep = 2;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
/* how about R/G/B offset? */
var->blue.offset = 0;
var->green.offset = 5;
var->red.offset = 11;
var->blue.length = 5;
var->green.length = 6;
var->red.length = 5;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 2;
break;
default:
pr_err("msm_fb_init: fb %d unkown image type!\n",
mfd->index);
return ret;
}
fix->type = panel_info->is_3d_panel;
fix->line_length = mdss_fb_line_length(mfd->index, panel_info->xres,
bpp);
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 */
var->upper_margin = panel_info->lcdc.v_front_porch;
var->lower_margin = panel_info->lcdc.v_back_porch;
var->vsync_len = panel_info->lcdc.v_pulse_width;
var->left_margin = panel_info->lcdc.h_front_porch;
var->right_margin = panel_info->lcdc.h_back_porch;
var->hsync_len = panel_info->lcdc.h_pulse_width;
var->pixclock = panel_info->clk_rate / 1000;
mfd->var_xres = var->xres;
mfd->var_yres = var->yres;
mfd->var_pixclock = var->pixclock;
/* id field for fb app */
id = (int *)&mfd->panel;
snprintf(fix->id, sizeof(fix->id), "mdssfb_%x", (u32) *id);
fbi->fbops = &mdss_fb_ops;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = mdss_fb_pseudo_palette;
panel_info->fbi = fbi;
mfd->ref_cnt = 0;
mfd->panel_power_on = false;
if (mdss_fb_alloc_fbmem(mfd)) {
pr_err("unable to allocate framebuffer memory\n");
return -ENOMEM;
}
mfd->op_enable = true;
/* cursor memory allocation */
if (mfd->cursor_update) {
mfd->cursor_buf = dma_alloc_coherent(NULL, MDSS_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)
pr_err("fb_alloc_cmap() failed!\n");
}
if (register_framebuffer(fbi) < 0) {
if (mfd->lut_update)
fb_dealloc_cmap(&fbi->cmap);
if (mfd->cursor_buf)
dma_free_coherent(NULL, MDSS_MDP_CURSOR_SIZE,
mfd->cursor_buf,
(dma_addr_t) mfd->cursor_buf_phys);
mfd->op_enable = false;
return -EPERM;
}
pr_info("FrameBuffer[%d] %dx%d size=%d registered successfully!\n",
mfd->index, fbi->var.xres, fbi->var.yres,
fbi->fix.smem_len);
ret = 0;
return ret;
}
static int mdss_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)
pr_err("pm_runtime: fail to wake up\n");
if (!mfd->ref_cnt) {
result = mdss_fb_blank_sub(FB_BLANK_UNBLANK, info,
mfd->op_enable);
if (result) {
pr_err("mdss_fb_open: can't turn on display!\n");
return result;
}
}
mfd->ref_cnt++;
return 0;
}
static int mdss_fb_release(struct fb_info *info, int user)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int ret = 0;
if (!mfd->ref_cnt) {
pr_info("try to close unopened fb %d!\n", mfd->index);
return -EINVAL;
}
mfd->ref_cnt--;
if (!mfd->ref_cnt) {
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info,
mfd->op_enable);
if (ret) {
pr_err("can't turn off display!\n");
return ret;
}
}
pm_runtime_put(info->dev);
return ret;
}
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if ((!mfd->op_enable) || (!mfd->panel_power_on))
return -EPERM;
if (var->xoffset > (info->var.xres_virtual - info->var.xres))
return -EINVAL;
if (var->yoffset > (info->var.yres_virtual - info->var.yres))
return -EINVAL;
if (info->fix.xpanstep)
info->var.xoffset =
(var->xoffset / info->fix.xpanstep) * info->fix.xpanstep;
if (info->fix.ypanstep)
info->var.yoffset =
(var->yoffset / info->fix.ypanstep) * info->fix.ypanstep;
if (mfd->dma_fnc)
mfd->dma_fnc(mfd);
else
pr_warn("dma function not set for panel type=%d\n",
mfd->panel.type);
mdss_fb_update_backlight(mfd);
++mfd->panel_info.frame_count;
return 0;
}
static int mdss_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
u32 len;
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;
len = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel / 8);
if (len > info->fix.smem_len)
return -EINVAL;
if ((var->xres == 0) || (var->yres == 0))
return -EINVAL;
if ((var->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 mdss_fb_set_par(struct fb_info *info)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct fb_var_screeninfo *var = &info->var;
int old_imgType;
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)))) {
mfd->var_xres = var->xres;
mfd->var_yres = var->yres;
mfd->var_pixclock = var->pixclock;
blank = 1;
}
mfd->fbi->fix.line_length = mdss_fb_line_length(mfd->index, var->xres,
var->bits_per_pixel / 8);
if (blank) {
mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable);
mdss_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable);
}
return 0;
}
static int mdss_fb_cursor(struct fb_info *info, void __user *p)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct fb_cursor cursor;
int ret;
if (!mfd->cursor_update)
return -ENODEV;
ret = copy_from_user(&cursor, p, sizeof(cursor));
if (ret)
return ret;
return mfd->cursor_update(info, &cursor);
}
static int mdss_fb_set_lut(struct fb_info *info, void __user *p)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
struct fb_cmap cmap;
int ret;
if (!mfd->lut_update)
return -ENODEV;
ret = copy_from_user(&cmap, p, sizeof(cmap));
if (ret)
return ret;
mfd->lut_update(info, &cmap);
return 0;
}
static int mdss_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 mdp_page_protection fb_page_protection;
int ret = -ENOSYS;
switch (cmd) {
case MSMFB_CURSOR:
ret = mdss_fb_cursor(info, argp);
break;
case MSMFB_SET_LUT:
ret = mdss_fb_set_lut(info, argp);
break;
case MSMFB_GET_PAGE_PROTECTION:
fb_page_protection.page_protection =
mfd->mdp_fb_page_protection;
ret = copy_to_user(argp, &fb_page_protection,
sizeof(fb_page_protection));
if (ret)
return ret;
break;
default:
if (mfd->ioctl_handler)
ret = mfd->ioctl_handler(mfd, cmd, argp);
break;
}
if (ret == -ENOSYS)
pr_err("unsupported ioctl (%x)\n", cmd);
return ret;
}
struct fb_info *msm_fb_get_writeback_fb(void)
{
int c = 0;
for (c = 0; c < fbi_list_index; ++c) {
struct msm_fb_data_type *mfd;
mfd = (struct msm_fb_data_type *)fbi_list[c]->par;
if (mfd->panel.type == WRITEBACK_PANEL)
return fbi_list[c];
}
return NULL;
}
EXPORT_SYMBOL(msm_fb_get_writeback_fb);
int mdss_register_panel(struct mdss_panel_data *pdata)
{
struct platform_device *mdss_fb_dev = NULL;
struct msm_fb_data_type *mfd;
int rc;
if (!mdss_res) {
pr_err("mdss mdp resources not initialized yet\n");
return -ENODEV;
}
mdss_fb_dev = platform_device_alloc("mdss_fb", pdata->panel_info.pdest);
if (!mdss_fb_dev) {
pr_err("unable to allocate mdss_fb device\n");
return -ENOMEM;
}
mdss_fb_dev->dev.platform_data = pdata;
rc = platform_device_add(mdss_fb_dev);
if (rc) {
platform_device_put(mdss_fb_dev);
pr_err("unable to probe mdss_fb device (%d)\n", rc);
return rc;
}
mfd = platform_get_drvdata(mdss_fb_dev);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
mfd->on_fnc = mdss_mdp_ctl_on;
mfd->off_fnc = mdss_mdp_ctl_off;
rc = mdss_mdp_overlay_init(mfd);
if (rc)
pr_err("unable to init overlay\n");
return rc;
}
EXPORT_SYMBOL(mdss_register_panel);
int mdss_fb_get_phys_info(unsigned long *start, unsigned long *len, int fb_num)
{
struct fb_info *info;
if (fb_num > MAX_FBI_LIST)
return -EINVAL;
info = fbi_list[fb_num];
if (!info)
return -ENOENT;
*start = info->fix.smem_start;
*len = info->fix.smem_len;
return 0;
}
EXPORT_SYMBOL(mdss_fb_get_phys_info);
int __init mdss_fb_init(void)
{
int rc = -ENODEV;
if (platform_driver_register(&mdss_fb_driver))
return rc;
return 0;
}
module_init(mdss_fb_init);