mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
19cc56631b
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>
1249 lines
30 KiB
C
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);
|