| /* |
| * 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); |
| |