blob: 3721e07ae982a9f7c1afdff5074847dbbc63f1b6 [file] [log] [blame]
/*
* Core MDSS framebuffer driver.
*
* Copyright (C) 2007 Google Incorporated
* Copyright (c) 2008-2014, 2020 The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "mdss_fb.h"
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
#define MDSS_FB_NUM 3
#else
#define MDSS_FB_NUM 2
#endif
#ifndef EXPORT_COMPAT
#define EXPORT_COMPAT(x)
#endif
#define MAX_FBI_LIST 32
#define BLANK_FLAG_LP FB_BLANK_VSYNC_SUSPEND
#define BLANK_FLAG_ULP FB_BLANK_NORMAL
#define DEFERRED_IO_REFRESH_RATE 25
#define DEFERRED_IO_DELAY (HZ / DEFERRED_IO_REFRESH_RATE)
enum {
MDP_RGB_565, /* RGB 565 planer */
MDP_XRGB_8888, /* RGB 888 padded */
MDP_ARGB_8888, /* ARGB 888 */
MDP_RGB_888, /* RGB 888 planer */
MDP_RGBA_8888, /* ARGB 888 */
MDP_BGRA_8888, /* ABGR 888 */
MDP_BGR_565, /* BGR 565 planer */
};
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 struct msm_mdp_interface *mdp_instance;
static struct fb_deferred_io mdss_fb_defio;
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_release_all(struct fb_info *info, bool release_all);
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 void mdss_fb_shutdown(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
lock_fb_info(mfd->fbi);
mdss_fb_release_all(mfd->fbi, true);
unlock_fb_info(mfd->fbi);
}
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 -EPROBE_DEFER;
/*
* alloc framebuffer info + par data
*/
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
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->fb_imgType = MDP_RGBA_8888;
mfd->fbi->fbdefio = &mdss_fb_defio;
mfd->pdev = pdev;
mfd->mdp = *mdp_instance;
fbi_list[fbi_list_index++] = fbi;
platform_set_drvdata(pdev, mfd);
rc = mdss_fb_register(mfd);
if (rc)
return rc;
if (mfd->mdp.init_fnc) {
rc = mfd->mdp.init_fnc(mfd);
if (rc) {
pr_err("init_fnc failed\n");
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);
return rc;
}
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);
if (!mfd)
return -ENODEV;
pm_runtime_disable(mfd->fbi->dev);
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);
fb_deferred_io_cleanup(mfd->fbi);
/* remove /dev/fb* */
unregister_framebuffer(mfd->fbi);
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_state = mfd->panel_power_state;
if (mfd->op_enable) {
/*
* Ideally, display should have either been blanked by now, or
* should have transitioned to a low power state. If not, then
* as a fall back option, enter ulp state to leave the display
* on, but turn off all interface clocks.
*/
if (mdss_fb_is_power_on(mfd)) {
ret = mdss_fb_blank_sub(BLANK_FLAG_ULP, mfd->fbi,
mfd->suspend.op_enable);
if (ret) {
pr_err("can't turn off display!\n");
return ret;
}
}
mfd->op_enable = false;
fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED);
}
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 the fb was explicitly blanked or transitioned to ulp during
* suspend, then undo it during resume with the appropriate unblank
* flag. If fb was in ulp state when entering suspend, then nothing
* needs to be done.
*/
if (mdss_panel_is_power_on(mfd->suspend.panel_power_state) &&
!mdss_panel_is_power_on_ulp(mfd->suspend.panel_power_state)) {
int unblank_flag = mdss_panel_is_power_on_interactive(
mfd->suspend.panel_power_state) ? FB_BLANK_UNBLANK :
BLANK_FLAG_LP;
ret = mdss_fb_blank_sub(unblank_flag, mfd->fbi, mfd->op_enable);
if (ret)
pr_warn("can't turn on display!\n");
else
fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING);
}
return ret;
}
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
static int mdss_fb_suspend(struct platform_device *pdev, pm_message_t state)
{
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
if (!mfd)
return -ENODEV;
dev_dbg(&pdev->dev, "display suspend\n");
return mdss_fb_suspend_sub(mfd);
}
static int mdss_fb_resume(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
if (!mfd)
return -ENODEV;
dev_dbg(&pdev->dev, "display resume\n");
return mdss_fb_resume_sub(mfd);
}
#else
#define mdss_fb_suspend NULL
#define mdss_fb_resume NULL
#endif
#ifdef CONFIG_PM_SLEEP
static int mdss_fb_pm_suspend(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
if (!mfd)
return -ENODEV;
dev_dbg(dev, "display pm suspend\n");
return mdss_fb_suspend_sub(mfd);
}
static int mdss_fb_pm_resume(struct device *dev)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(dev);
if (!mfd)
return -ENODEV;
dev_dbg(dev, "display pm resume\n");
/*
* It is possible that the runtime status of the fb device may
* have been active when the system was suspended. Reset the runtime
* status to suspended state after a complete system resume.
*/
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
pm_runtime_enable(dev);
return mdss_fb_resume_sub(mfd);
}
#endif
static const struct dev_pm_ops mdss_fb_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mdss_fb_pm_suspend, mdss_fb_pm_resume)
};
static const struct of_device_id mdss_fb_dt_match[] = {
{ .compatible = "qti,mdss-fb",},
{}
};
EXPORT_COMPAT("qti,mdss-fb");
static struct platform_driver mdss_fb_driver = {
.probe = mdss_fb_probe,
.remove = mdss_fb_remove,
.suspend = mdss_fb_suspend,
.resume = mdss_fb_resume,
.shutdown = mdss_fb_shutdown,
.driver = {
.name = "mdss_fb",
.of_match_table = mdss_fb_dt_match,
.pm = &mdss_fb_pm_ops,
},
};
static int mdss_fb_blank_blank(struct msm_fb_data_type *mfd,
int req_power_state)
{
int ret = 0;
int cur_power_state;
if (!mfd)
return -EINVAL;
if (!mdss_fb_is_power_on(mfd) || !mfd->mdp.off_fnc)
return 0;
cur_power_state = mfd->panel_power_state;
pr_debug("Transitioning from %d --> %d\n", cur_power_state,
req_power_state);
if (cur_power_state == req_power_state) {
pr_debug("No change in power state\n");
return 0;
}
mfd->op_enable = false;
mfd->panel_power_state = req_power_state;
ret = mfd->mdp.off_fnc(mfd);
if (ret)
mfd->panel_power_state = cur_power_state;
mfd->op_enable = true;
return ret;
}
static int mdss_fb_blank_unblank(struct msm_fb_data_type *mfd)
{
int ret = 0;
int cur_power_state;
if (!mfd)
return -EINVAL;
cur_power_state = mfd->panel_power_state;
pr_debug("Transitioning from %d --> %d\n", cur_power_state,
MDSS_PANEL_POWER_ON);
if (mdss_panel_is_power_on_interactive(cur_power_state)) {
pr_debug("No change in power state\n");
return 0;
}
if (mfd->mdp.on_fnc) {
ret = mfd->mdp.on_fnc(mfd);
if (ret)
goto error;
mfd->panel_power_state = MDSS_PANEL_POWER_ON;
mfd->panel_info->panel_dead = false;
}
error:
return ret;
}
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;
int cur_power_state, req_power_state = MDSS_PANEL_POWER_OFF;
if (!mfd || !op_enable)
return -EPERM;
pr_debug("%pS mode:%d\n", __builtin_return_address(0),
blank_mode);
cur_power_state = mfd->panel_power_state;
switch (blank_mode) {
case FB_BLANK_UNBLANK:
pr_debug("unblank called. cur pwr state=%d\n", cur_power_state);
ret = mdss_fb_blank_unblank(mfd);
break;
case BLANK_FLAG_ULP:
req_power_state = MDSS_PANEL_POWER_LP2;
pr_debug("ultra low power mode requested\n");
if (mdss_fb_is_power_off(mfd)) {
pr_debug("Unsupp transition: off --> ulp\n");
return 0;
}
ret = mdss_fb_blank_blank(mfd, req_power_state);
break;
case BLANK_FLAG_LP:
req_power_state = MDSS_PANEL_POWER_LP1;
pr_debug(" power mode requested\n");
/*
* If low power mode is requested when panel is already off,
* then first unblank the panel before entering low power mode
*/
if (mdss_fb_is_power_off(mfd) && mfd->mdp.on_fnc) {
pr_debug("off --> lp. switch to on first\n");
ret = mdss_fb_blank_unblank(mfd);
if (ret)
break;
}
ret = mdss_fb_blank_blank(mfd, req_power_state);
break;
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
default:
req_power_state = MDSS_PANEL_POWER_OFF;
pr_debug("blank powerdown called\n");
ret = mdss_fb_blank_blank(mfd, req_power_state);
break;
}
return ret;
}
static void mdss_fb_deferred_io(struct fb_info *info,
struct list_head *pagelist)
{
mdss_fb_pan_display(&info->var, info);
}
static int mdss_fb_blank(int blank_mode, struct fb_info *info)
{
struct mdss_panel_data *pdata;
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
if (mfd->op_enable == 0) {
if (blank_mode == FB_BLANK_UNBLANK)
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_ON;
else if (blank_mode == BLANK_FLAG_ULP)
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP2;
else if (blank_mode == BLANK_FLAG_LP)
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP1;
else
mfd->suspend.panel_power_state = MDSS_PANEL_POWER_OFF;
return 0;
}
pr_debug("mode: %d\n", blank_mode);
pdata = dev_get_platdata(&mfd->pdev->dev);
if (pdata->panel_info.is_lpm_mode &&
blank_mode == FB_BLANK_UNBLANK) {
pr_debug("panel is in lpm mode\n");
mfd->mdp.configure_panel(mfd, 0);
pdata->panel_info.is_lpm_mode = false;
}
return mdss_fb_blank_sub(blank_mode, info, mfd->op_enable);
}
static int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
return dma_mmap_coherent(info->dev, vma,
info->screen_base,
info->fix.smem_start,
info->fix.smem_len);
}
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_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = mdss_fb_mmap,
};
static struct fb_deferred_io mdss_fb_defio = {
.delay = DEFERRED_IO_DELAY,
.deferred_io = mdss_fb_deferred_io,
};
static int mdss_fb_alloc_fbmem(struct msm_fb_data_type *mfd)
{
if (mfd->mdp.fb_mem_alloc_fnc) {
return mfd->mdp.fb_mem_alloc_fnc(mfd);
} else {
pr_err("no fb memory allocator function defined\n");
return -ENOMEM;
}
}
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 = 24;
var->green.offset = 16;
var->red.offset = 8;
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_RGBA_8888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 16;
var->green.offset = 8;
var->red.offset = 0;
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;
default:
pr_err("msm_fb_init: fb %d unkown image type!\n",
mfd->index);
return ret;
}
var->xres = panel_info->xres;
fix->type = panel_info->is_3d_panel;
if (mfd->mdp.fb_stride)
fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres,
bpp);
else
fix->line_length = var->xres * bpp;
var->yres = panel_info->yres;
if (panel_info->physical_width)
var->width = panel_info->physical_width;
if (panel_info->physical_height)
var->height = panel_info->physical_height;
var->xres_virtual = var->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_back_porch;
var->lower_margin = panel_info->lcdc.v_front_porch;
var->vsync_len = panel_info->lcdc.v_pulse_width;
var->left_margin = panel_info->lcdc.h_back_porch;
var->right_margin = panel_info->lcdc.h_front_porch;
var->hsync_len = panel_info->lcdc.h_pulse_width;
var->pixclock = panel_info->clk_rate / 1000;
/*
* Populate smem length here for uspace to get the
* Framebuffer size when FBIO_FSCREENINFO ioctl is
* called.
*/
fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;
/* 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 | FBINFO_DMAFB;
fbi->pseudo_palette = mdss_fb_pseudo_palette;
mfd->ref_cnt = 0;
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
if (mdss_fb_alloc_fbmem(mfd))
pr_warn("unable to allocate fb memory in fb register\n");
mfd->op_enable = true;
fb_deferred_io_init(mfd->fbi);
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
if (ret)
pr_err("fb_alloc_cmap() failed!\n");
if (register_framebuffer(fbi) < 0) {
fb_dealloc_cmap(&fbi->cmap);
mfd->op_enable = false;
return -EPERM;
}
pr_info("FrameBuffer[%d] %dx%d registered successfully!\n", mfd->index,
fbi->var.xres, fbi->var.yres);
return 0;
}
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");
goto pm_error;
}
if (!mfd->ref_cnt) {
result = mdss_fb_blank_sub(FB_BLANK_UNBLANK, info,
mfd->op_enable);
if (result) {
pr_err("can't turn on fb%d! rc=%d\n", mfd->index,
result);
goto blank_error;
}
}
mfd->ref_cnt++;
return 0;
blank_error:
pm_runtime_put(info->dev);
pm_error:
return result;
}
static int mdss_fb_release_all(struct fb_info *info, bool release_all)
{
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
int ret = 0;
bool release_needed = false;
if (!mfd->ref_cnt) {
pr_info("try to close unopened fb %d!\n", mfd->index);
return -EINVAL;
}
pr_debug("release_all = %s\n", release_all ? "true" : "false");
mfd->ref_cnt--;
if (!mfd->ref_cnt || release_all) {
/* resources (if any) will be released during blank */
if (mfd->mdp.release_fnc)
mfd->mdp.release_fnc(mfd, true);
ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info,
mfd->op_enable);
if (ret) {
pr_err("can't turn off fb%d!\n", ret);
return ret;
}
} else if (release_needed) {
if (mfd->mdp.release_fnc) {
ret = mfd->mdp.release_fnc(mfd, false);
/* display commit is needed to release resources */
if (ret)
mdss_fb_pan_display(&mfd->fbi->var, mfd->fbi);
}
}
return ret;
}
static int mdss_fb_release(struct fb_info *info, int user)
{
return mdss_fb_release_all(info, false);
}
static int mdss_fb_pan_display_sub(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)
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->mdp.dma_fnc)
mfd->mdp.dma_fnc(mfd);
else
pr_warn("dma function not set for panel type=%d\n",
mfd->panel.type);
return 0;
}
static int mdss_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
return mdss_fb_pan_display_sub(var, info);
}
static void mdss_fb_var_to_panelinfo(struct fb_var_screeninfo *var,
struct mdss_panel_info *pinfo)
{
pinfo->xres = var->xres;
pinfo->yres = var->yres;
pinfo->lcdc.v_front_porch = var->lower_margin;
pinfo->lcdc.v_back_porch = var->upper_margin;
pinfo->lcdc.v_pulse_width = var->vsync_len;
pinfo->lcdc.h_front_porch = var->right_margin;
pinfo->lcdc.h_back_porch = var->left_margin;
pinfo->lcdc.h_pulse_width = var->hsync_len;
pinfo->clk_rate = var->pixclock;
}
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;
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:
/* Check user specified color format BGRA/ARGB/RGBA
and verify the position of the RGB components */
if (!((var->transp.offset == 24) &&
(var->blue.offset == 0) &&
(var->green.offset == 8) &&
(var->red.offset == 16)) &&
!((var->transp.offset == 24) &&
(var->blue.offset == 16) &&
(var->green.offset == 8) &&
(var->red.offset == 0)))
return -EINVAL;
/* Check the common values for both RGBA and ARGB */
if ((var->blue.length != 8) ||
(var->green.length != 8) ||
(var->red.length != 8) ||
(var->transp.length != 8) ||
(var->blue.msb_right != 0) ||
(var->green.msb_right != 0) ||
(var->red.msb_right != 0))
return -EINVAL;
break;
default:
return -EINVAL;
}
if ((var->xres_virtual <= 0) || (var->yres_virtual <= 0))
return -EINVAL;
if (info->fix.smem_start) {
u32 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->xoffset > (var->xres_virtual - var->xres))
return -EINVAL;
if (var->yoffset > (var->yres_virtual - var->yres))
return -EINVAL;
if (mfd->panel_info) {
struct mdss_panel_info panel_info;
memcpy(&panel_info, mfd->panel_info, sizeof(panel_info));
mdss_fb_var_to_panelinfo(var, &panel_info);
mfd->panel_reconfig = true;
}
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;
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->red.offset == 0) &&
(var->green.offset == 8) &&
(var->blue.offset == 16) &&
(var->transp.offset == 24))
mfd->fb_imgType = MDP_RGBA_8888;
else if ((var->red.offset == 16) &&
(var->green.offset == 8) &&
(var->blue.offset == 0) &&
(var->transp.offset == 24))
mfd->fb_imgType = MDP_BGRA_8888;
else if ((var->red.offset == 8) &&
(var->green.offset == 16) &&
(var->blue.offset == 24) &&
(var->transp.offset == 0))
mfd->fb_imgType = MDP_ARGB_8888;
else
mfd->fb_imgType = MDP_RGBA_8888;
break;
default:
return -EINVAL;
}
if (mfd->mdp.fb_stride)
mfd->fbi->fix.line_length = mfd->mdp.fb_stride(mfd->index,
var->xres,
var->bits_per_pixel / 8);
else
mfd->fbi->fix.line_length = var->xres * var->bits_per_pixel / 8;
mfd->fbi->fix.smem_len = PAGE_ALIGN(mfd->fbi->fix.line_length *
mfd->fbi->var.yres) * mfd->fb_page;
if (mfd->panel_reconfig || (mfd->fb_imgType != old_imgType)) {
mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable);
mdss_fb_var_to_panelinfo(var, mfd->panel_info);
mdss_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable);
mfd->panel_reconfig = false;
}
return 0;
}
static int mdss_fb_register_extra_panel(struct platform_device *pdev,
struct mdss_panel_data *pdata)
{
struct mdss_panel_data *fb_pdata;
fb_pdata = dev_get_platdata(&pdev->dev);
if (!fb_pdata) {
pr_err("framebuffer device %s contains invalid panel data\n",
dev_name(&pdev->dev));
return -EINVAL;
}
if (fb_pdata->next) {
pr_err("split panel already setup for framebuffer device %s\n",
dev_name(&pdev->dev));
return -EEXIST;
}
fb_pdata->next = pdata;
return 0;
}
int mdss_register_panel(struct platform_device *pdev,
struct mdss_panel_data *pdata)
{
struct platform_device *fb_pdev, *mdss_pdev;
struct device_node *node;
int rc = 0;
bool master_panel = true;
if (!pdev || !pdev->dev.of_node) {
pr_err("Invalid device node\n");
return -ENODEV;
}
if (!mdp_instance) {
pr_err("mdss mdp resource not initialized yet\n");
return -EPROBE_DEFER;
}
node = of_parse_phandle(pdev->dev.of_node, "qti,mdss-fb-map", 0);
if (!node) {
pr_err("Unable to find fb node for device: %s\n",
pdev->name);
return -ENODEV;
}
mdss_pdev = of_find_device_by_node(node->parent);
if (!mdss_pdev) {
pr_err("Unable to find mdss for node: %s\n", node->full_name);
rc = -ENODEV;
goto mdss_notfound;
}
fb_pdev = of_find_device_by_node(node);
if (fb_pdev) {
rc = mdss_fb_register_extra_panel(fb_pdev, pdata);
if (rc == 0)
master_panel = false;
} else {
pr_info("adding framebuffer device %s\n", dev_name(&pdev->dev));
fb_pdev = of_platform_device_create(node, NULL,
&mdss_pdev->dev);
if (fb_pdev)
fb_pdev->dev.platform_data = pdata;
}
if (master_panel && mdp_instance->panel_register_done)
mdp_instance->panel_register_done(pdata);
mdss_notfound:
of_node_put(node);
return rc;
}
EXPORT_SYMBOL(mdss_register_panel);
int mdss_fb_register_mdp_instance(struct msm_mdp_interface *mdp)
{
if (mdp_instance) {
pr_err("multiple MDP instance registration\n");
return -EINVAL;
}
mdp_instance = mdp;
return 0;
}
EXPORT_SYMBOL(mdss_fb_register_mdp_instance);
int mdss_fb_get_phys_info(dma_addr_t *start, unsigned long *len, int fb_num)
{
struct fb_info *info;
struct msm_fb_data_type *mfd;
if (fb_num >= MAX_FBI_LIST)
return -EINVAL;
info = fbi_list[fb_num];
if (!info)
return -ENOENT;
mfd = (struct msm_fb_data_type *)info->par;
if (!mfd)
return -ENODEV;
*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);