| /* |
| * Core MDSS framebuffer driver. |
| * |
| * Copyright (C) 2007 Google Incorporated |
| * Copyright (c) 2008-2018, 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/videodev2.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/dma-buf.h> |
| #include <linux/fb.h> |
| #include <linux/init.h> |
| #include <linux/ioport.h> |
| #include <linux/kernel.h> |
| #include <linux/memory.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/msm_mdp.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.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 <linux/sync.h> |
| #include <linux/sw_sync.h> |
| #include <linux/file.h> |
| #include <linux/kthread.h> |
| #include <linux/dma-buf.h> |
| #include "mdss_fb.h" |
| #include "mdss_mdp_splash_logo.h" |
| #define CREATE_TRACE_POINTS |
| #include "mdss_debug.h" |
| #include "mdss_smmu.h" |
| #include "mdss_mdp.h" |
| #include "mdp3_ctrl.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 |
| |
| #ifndef TARGET_HW_MDSS_MDP3 |
| #define BLANK_FLAG_LP FB_BLANK_NORMAL |
| #define BLANK_FLAG_ULP FB_BLANK_VSYNC_SUSPEND |
| #else |
| #define BLANK_FLAG_LP FB_BLANK_VSYNC_SUSPEND |
| #define BLANK_FLAG_ULP FB_BLANK_NORMAL |
| #endif |
| |
| /* |
| * Time period for fps calulation in micro seconds. |
| * Default value is set to 1 sec. |
| */ |
| #define MDP_TIME_PERIOD_CALC_FPS_US 1000000 |
| |
| 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 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 int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg, struct file *file); |
| static int mdss_fb_fbmem_ion_mmap(struct fb_info *info, |
| struct vm_area_struct *vma); |
| static int mdss_fb_alloc_fb_ion_memory(struct msm_fb_data_type *mfd, |
| size_t size); |
| static void mdss_fb_release_fences(struct msm_fb_data_type *mfd); |
| static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p, |
| unsigned long val, void *data); |
| |
| static int __mdss_fb_display_thread(void *data); |
| static int mdss_fb_pan_idle(struct msm_fb_data_type *mfd); |
| static int mdss_fb_send_panel_event(struct msm_fb_data_type *mfd, |
| int event, void *arg); |
| static void mdss_fb_set_mdp_sync_pt_threshold(struct msm_fb_data_type *mfd, |
| int type); |
| void mdss_fb_no_update_notify_timer_cb(unsigned long data) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; |
| |
| if (!mfd) { |
| pr_err("%s mfd NULL\n", __func__); |
| return; |
| } |
| mfd->no_update.value = NOTIFY_TYPE_NO_UPDATE; |
| complete(&mfd->no_update.comp); |
| } |
| |
| void mdss_fb_bl_update_notify(struct msm_fb_data_type *mfd, |
| uint32_t notification_type) |
| { |
| #ifndef TARGET_HW_MDSS_MDP3 |
| struct mdss_overlay_private *mdp5_data = NULL; |
| #endif |
| #ifdef TARGET_HW_MDSS_MDP3 |
| struct mdp3_session_data *mdp3_session = NULL; |
| #endif |
| if (!mfd) { |
| pr_err("%s mfd NULL\n", __func__); |
| return; |
| } |
| mutex_lock(&mfd->update.lock); |
| if (mfd->update.is_suspend) { |
| mutex_unlock(&mfd->update.lock); |
| return; |
| } |
| if (mfd->update.ref_count > 0) { |
| mutex_unlock(&mfd->update.lock); |
| mfd->update.value = notification_type; |
| complete(&mfd->update.comp); |
| mutex_lock(&mfd->update.lock); |
| } |
| mutex_unlock(&mfd->update.lock); |
| |
| mutex_lock(&mfd->no_update.lock); |
| if (mfd->no_update.ref_count > 0) { |
| mutex_unlock(&mfd->no_update.lock); |
| mfd->no_update.value = notification_type; |
| complete(&mfd->no_update.comp); |
| mutex_lock(&mfd->no_update.lock); |
| } |
| mutex_unlock(&mfd->no_update.lock); |
| #ifndef TARGET_HW_MDSS_MDP3 |
| mdp5_data = mfd_to_mdp5_data(mfd); |
| if (mdp5_data) { |
| if (notification_type == NOTIFY_TYPE_BL_AD_ATTEN_UPDATE) { |
| mdp5_data->ad_bl_events++; |
| sysfs_notify_dirent(mdp5_data->ad_bl_event_sd); |
| } else if (notification_type == NOTIFY_TYPE_BL_UPDATE) { |
| mdp5_data->bl_events++; |
| sysfs_notify_dirent(mdp5_data->bl_event_sd); |
| } |
| } |
| #endif |
| #ifdef TARGET_HW_MDSS_MDP3 |
| mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; |
| if (mdp3_session) { |
| mdp3_session->bl_events++; |
| sysfs_notify_dirent(mdp3_session->bl_event_sd); |
| pr_debug("bl_event = %u\n", mdp3_session->bl_events); |
| } |
| #endif |
| } |
| |
| static int mdss_fb_notify_update(struct msm_fb_data_type *mfd, |
| unsigned long *argp) |
| { |
| int ret; |
| unsigned int notify = 0x0, to_user = 0x0; |
| |
| ret = copy_from_user(¬ify, argp, sizeof(unsigned int)); |
| if (ret) { |
| pr_err("%s:ioctl failed\n", __func__); |
| return ret; |
| } |
| |
| if (notify > NOTIFY_UPDATE_POWER_OFF) |
| return -EINVAL; |
| |
| if (notify == NOTIFY_UPDATE_INIT) { |
| mutex_lock(&mfd->update.lock); |
| mfd->update.init_done = true; |
| mutex_unlock(&mfd->update.lock); |
| ret = 1; |
| } else if (notify == NOTIFY_UPDATE_DEINIT) { |
| mutex_lock(&mfd->update.lock); |
| mfd->update.init_done = false; |
| mutex_unlock(&mfd->update.lock); |
| complete(&mfd->update.comp); |
| complete(&mfd->no_update.comp); |
| ret = 1; |
| } else if (mfd->update.is_suspend) { |
| to_user = NOTIFY_TYPE_SUSPEND; |
| mfd->update.is_suspend = 0; |
| ret = 1; |
| } else if (notify == NOTIFY_UPDATE_START) { |
| mutex_lock(&mfd->update.lock); |
| if (mfd->update.init_done) |
| reinit_completion(&mfd->update.comp); |
| else { |
| mutex_unlock(&mfd->update.lock); |
| pr_err("notify update start called without init\n"); |
| return -EINVAL; |
| } |
| mfd->update.ref_count++; |
| mutex_unlock(&mfd->update.lock); |
| ret = wait_for_completion_interruptible_timeout( |
| &mfd->update.comp, 4 * HZ); |
| mutex_lock(&mfd->update.lock); |
| mfd->update.ref_count--; |
| mutex_unlock(&mfd->update.lock); |
| to_user = (unsigned int)mfd->update.value; |
| if (mfd->update.type == NOTIFY_TYPE_SUSPEND) { |
| to_user = (unsigned int)mfd->update.type; |
| ret = 1; |
| } |
| } else if (notify == NOTIFY_UPDATE_STOP) { |
| mutex_lock(&mfd->update.lock); |
| if (mfd->update.init_done) |
| reinit_completion(&mfd->no_update.comp); |
| else { |
| mutex_unlock(&mfd->update.lock); |
| pr_err("notify update stop called without init\n"); |
| return -EINVAL; |
| } |
| mfd->no_update.ref_count++; |
| mutex_unlock(&mfd->no_update.lock); |
| ret = wait_for_completion_interruptible_timeout( |
| &mfd->no_update.comp, 4 * HZ); |
| mutex_lock(&mfd->no_update.lock); |
| mfd->no_update.ref_count--; |
| mutex_unlock(&mfd->no_update.lock); |
| to_user = (unsigned int)mfd->no_update.value; |
| } else { |
| if (mdss_fb_is_power_on(mfd)) { |
| reinit_completion(&mfd->power_off_comp); |
| ret = wait_for_completion_interruptible_timeout( |
| &mfd->power_off_comp, 1 * HZ); |
| } |
| } |
| |
| if (ret == 0) |
| ret = -ETIMEDOUT; |
| else if (ret > 0) |
| ret = copy_to_user(argp, &to_user, sizeof(unsigned int)); |
| return ret; |
| } |
| |
| 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 (mfd->boot_notification_led) { |
| led_trigger_event(mfd->boot_notification_led, 0); |
| mfd->boot_notification_led = NULL; |
| } |
| |
| if (value > mfd->panel_info->brightness_max) |
| value = mfd->panel_info->brightness_max; |
| |
| /* This maps android backlight level 0 to 255 into |
| * driver backlight level 0 to bl_max with rounding |
| */ |
| MDSS_BRIGHT_TO_BL(bl_lvl, value, mfd->panel_info->bl_max, |
| mfd->panel_info->brightness_max); |
| |
| if (!bl_lvl && value) |
| bl_lvl = 1; |
| |
| if (!IS_CALIB_MODE_BL(mfd) && (!mfd->ext_bl_ctrl || !value || |
| !mfd->bl_level)) { |
| mutex_lock(&mfd->bl_lock); |
| mdss_fb_set_backlight(mfd, bl_lvl); |
| mutex_unlock(&mfd->bl_lock); |
| } |
| } |
| |
| static struct led_classdev backlight_led = { |
| .name = "lcd-backlight", |
| .brightness = MDSS_MAX_BL_BRIGHTNESS / 2, |
| .brightness_set = mdss_fb_set_bl_brightness, |
| .max_brightness = MDSS_MAX_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.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; |
| case EDP_PANEL: |
| ret = snprintf(buf, PAGE_SIZE, "edp panel\n"); |
| break; |
| default: |
| ret = snprintf(buf, PAGE_SIZE, "unknown panel\n"); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int mdss_fb_get_panel_xres(struct mdss_panel_info *pinfo) |
| { |
| struct mdss_panel_data *pdata; |
| int xres; |
| |
| pdata = container_of(pinfo, struct mdss_panel_data, panel_info); |
| |
| xres = pinfo->xres; |
| if (pdata->next && pdata->next->active) |
| xres += mdss_fb_get_panel_xres(&pdata->next->panel_info); |
| |
| return xres; |
| } |
| |
| static inline int mdss_fb_validate_split(int left, int right, |
| struct msm_fb_data_type *mfd) |
| { |
| int rc = -EINVAL; |
| u32 panel_xres = mdss_fb_get_panel_xres(mfd->panel_info); |
| |
| pr_debug("%pS: split_mode = %d left=%d right=%d panel_xres=%d\n", |
| __builtin_return_address(0), mfd->split_mode, |
| left, right, panel_xres); |
| |
| /* more validate condition could be added if needed */ |
| if (left && right) { |
| if (panel_xres == left + right) { |
| mfd->split_fb_left = left; |
| mfd->split_fb_right = right; |
| rc = 0; |
| } |
| } else { |
| if (mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { |
| mfd->split_fb_left = mfd->panel_info->xres; |
| mfd->split_fb_right = panel_xres - mfd->split_fb_left; |
| rc = 0; |
| } else { |
| mfd->split_fb_left = mfd->split_fb_right = 0; |
| } |
| } |
| |
| return rc; |
| } |
| |
| static ssize_t mdss_fb_store_split(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t len) |
| { |
| int data[2] = {0}; |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| |
| if (sscanf(buf, "%d %d", &data[0], &data[1]) != 2) |
| pr_debug("Not able to read split values\n"); |
| else if (!mdss_fb_validate_split(data[0], data[1], mfd)) |
| pr_debug("split left=%d right=%d\n", data[0], data[1]); |
| |
| return len; |
| } |
| |
| static ssize_t mdss_fb_show_split(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; |
| |
| ret = snprintf(buf, PAGE_SIZE, "%d %d\n", |
| mfd->split_fb_left, mfd->split_fb_right); |
| return ret; |
| } |
| |
| static void mdss_fb_get_split(struct msm_fb_data_type *mfd) |
| { |
| if ((mfd->split_mode == MDP_SPLIT_MODE_NONE) && |
| (mfd->split_fb_left && mfd->split_fb_right)) |
| mfd->split_mode = MDP_DUAL_LM_SINGLE_DISPLAY; |
| |
| pr_debug("split fb%d left=%d right=%d mode=%d\n", mfd->index, |
| mfd->split_fb_left, mfd->split_fb_right, mfd->split_mode); |
| } |
| |
| static ssize_t mdss_fb_get_src_split_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| |
| if (is_split_lm(mfd) && (fbi->var.yres > fbi->var.xres)) { |
| pr_debug("always split mode enabled\n"); |
| ret = scnprintf(buf, PAGE_SIZE, |
| "src_split_always\n"); |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_get_thermal_level(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int ret; |
| |
| ret = scnprintf(buf, PAGE_SIZE, "thermal_level=%d\n", |
| mfd->thermal_level); |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_set_thermal_level(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int rc = 0; |
| int thermal_level = 0; |
| |
| rc = kstrtoint(buf, 10, &thermal_level); |
| if (rc) { |
| pr_err("kstrtoint failed. rc=%d\n", rc); |
| return rc; |
| } |
| |
| pr_debug("Thermal level set to %d\n", thermal_level); |
| mfd->thermal_level = thermal_level; |
| sysfs_notify(&mfd->fbi->dev->kobj, NULL, "msm_fb_thermal_level"); |
| |
| return count; |
| } |
| |
| static ssize_t mdss_mdp_show_blank_event(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| int ret; |
| |
| pr_debug("fb%d panel_power_state = %d\n", mfd->index, |
| mfd->panel_power_state); |
| ret = scnprintf(buf, PAGE_SIZE, "panel_power_on = %d\n", |
| mfd->panel_power_state); |
| |
| return ret; |
| } |
| |
| static void __mdss_fb_idle_notify_work(struct work_struct *work) |
| { |
| struct delayed_work *dw = to_delayed_work(work); |
| struct msm_fb_data_type *mfd = container_of(dw, struct msm_fb_data_type, |
| idle_notify_work); |
| |
| /* Notify idle-ness here */ |
| pr_debug("Idle timeout %dms expired!\n", mfd->idle_time); |
| if (mfd->idle_time) |
| sysfs_notify(&mfd->fbi->dev->kobj, NULL, "idle_notify"); |
| mfd->idle_state = MDSS_FB_IDLE; |
| } |
| |
| |
| static ssize_t mdss_fb_get_fps_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| unsigned int fps_int, fps_float; |
| |
| if (mfd->panel_power_state != MDSS_PANEL_POWER_ON) |
| mfd->fps_info.measured_fps = 0; |
| fps_int = (unsigned int) mfd->fps_info.measured_fps; |
| fps_float = do_div(fps_int, 10); |
| return scnprintf(buf, PAGE_SIZE, "%d.%d\n", fps_int, fps_float); |
| |
| } |
| |
| static ssize_t mdss_fb_get_idle_time(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int ret; |
| |
| ret = scnprintf(buf, PAGE_SIZE, "%d", mfd->idle_time); |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_set_idle_time(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int rc = 0; |
| int idle_time = 0; |
| |
| rc = kstrtoint(buf, 10, &idle_time); |
| if (rc) { |
| pr_err("kstrtoint failed. rc=%d\n", rc); |
| return rc; |
| } |
| |
| pr_debug("Idle time = %d\n", idle_time); |
| mfd->idle_time = idle_time; |
| |
| return count; |
| } |
| |
| static ssize_t mdss_fb_get_idle_notify(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int ret; |
| |
| ret = scnprintf(buf, PAGE_SIZE, "%s", |
| work_busy(&mfd->idle_notify_work.work) ? "no" : "yes"); |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_get_panel_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| struct mdss_panel_info *pinfo = mfd->panel_info; |
| int ret; |
| |
| ret = scnprintf(buf, PAGE_SIZE, |
| "pu_en=%d\nxstart=%d\nwalign=%d\nystart=%d\nhalign=%d\n" |
| "min_w=%d\nmin_h=%d\nroi_merge=%d\ndyn_fps_en=%d\n" |
| "min_fps=%d\nmax_fps=%d\npanel_name=%s\n" |
| "primary_panel=%d\nis_pluggable=%d\ndisplay_id=%s\n" |
| "is_cec_supported=%d\nis_pingpong_split=%d\n" |
| "is_hdr_enabled=%d\n" |
| "peak_brightness=%d\nblackness_level=%d\n" |
| "white_chromaticity_x=%d\nwhite_chromaticity_y=%d\n" |
| "red_chromaticity_x=%d\nred_chromaticity_y=%d\n" |
| "green_chromaticity_x=%d\ngreen_chromaticity_y=%d\n" |
| "blue_chromaticity_x=%d\nblue_chromaticity_y=%d\n", |
| pinfo->partial_update_enabled, |
| pinfo->roi_alignment.xstart_pix_align, |
| pinfo->roi_alignment.width_pix_align, |
| pinfo->roi_alignment.ystart_pix_align, |
| pinfo->roi_alignment.height_pix_align, |
| pinfo->roi_alignment.min_width, |
| pinfo->roi_alignment.min_height, |
| pinfo->partial_update_roi_merge, |
| pinfo->dynamic_fps, pinfo->min_fps, pinfo->max_fps, |
| pinfo->panel_name, pinfo->is_prim_panel, |
| pinfo->is_pluggable, pinfo->display_id, |
| pinfo->is_cec_supported, is_pingpong_split(mfd), |
| pinfo->hdr_properties.hdr_enabled, |
| pinfo->hdr_properties.peak_brightness, |
| pinfo->hdr_properties.blackness_level, |
| pinfo->hdr_properties.display_primaries[0], |
| pinfo->hdr_properties.display_primaries[1], |
| pinfo->hdr_properties.display_primaries[2], |
| pinfo->hdr_properties.display_primaries[3], |
| pinfo->hdr_properties.display_primaries[4], |
| pinfo->hdr_properties.display_primaries[5], |
| pinfo->hdr_properties.display_primaries[6], |
| pinfo->hdr_properties.display_primaries[7]); |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_get_panel_status(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = fbi->par; |
| int ret; |
| int panel_status; |
| |
| if (mdss_panel_is_power_off(mfd->panel_power_state)) { |
| ret = scnprintf(buf, PAGE_SIZE, "panel_status=%s\n", "suspend"); |
| } else { |
| panel_status = mdss_fb_send_panel_event(mfd, |
| MDSS_EVENT_DSI_PANEL_STATUS, NULL); |
| ret = scnprintf(buf, PAGE_SIZE, "panel_status=%s\n", |
| panel_status > 0 ? "alive" : "dead"); |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_force_panel_dead(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t len) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| struct mdss_panel_data *pdata; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected!\n"); |
| return len; |
| } |
| |
| if (kstrtouint(buf, 0, &pdata->panel_info.panel_force_dead)) |
| pr_err("kstrtouint buf error!\n"); |
| |
| return len; |
| } |
| |
| /* |
| * mdss_fb_blanking_mode_switch() - Function triggers dynamic mode switch |
| * @mfd: Framebuffer data structure for display |
| * @mode: Enabled/Disable LowPowerMode |
| * 1: Enter into LowPowerMode |
| * 0: Exit from LowPowerMode |
| * |
| * This Function dynamically switches to and from video mode. This |
| * swtich involves the panel turning off backlight during trantision. |
| */ |
| static int mdss_fb_blanking_mode_switch(struct msm_fb_data_type *mfd, int mode) |
| { |
| int ret = 0; |
| u32 bl_lvl = 0; |
| struct mdss_panel_info *pinfo = NULL; |
| struct mdss_panel_data *pdata; |
| |
| if (!mfd || !mfd->panel_info) |
| return -EINVAL; |
| |
| pinfo = mfd->panel_info; |
| |
| if (!pinfo->mipi.dms_mode) { |
| pr_warn("Panel does not support dynamic switch!\n"); |
| return 0; |
| } |
| |
| if (mode == pinfo->mipi.mode) { |
| pr_debug("Already in requested mode!\n"); |
| return 0; |
| } |
| pr_debug("Enter mode: %d\n", mode); |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| |
| pdata->panel_info.dynamic_switch_pending = true; |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_err("mdss_fb_pan_idle for fb%d failed. ret=%d\n", |
| mfd->index, ret); |
| pdata->panel_info.dynamic_switch_pending = false; |
| return ret; |
| } |
| |
| mutex_lock(&mfd->bl_lock); |
| bl_lvl = mfd->bl_level; |
| mdss_fb_set_backlight(mfd, 0); |
| mutex_unlock(&mfd->bl_lock); |
| |
| lock_fb_info(mfd->fbi); |
| ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi, |
| mfd->op_enable); |
| if (ret) { |
| pr_err("can't turn off display!\n"); |
| unlock_fb_info(mfd->fbi); |
| return ret; |
| } |
| |
| mfd->op_enable = false; |
| |
| ret = mfd->mdp.configure_panel(mfd, mode, 1); |
| mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type); |
| |
| mfd->op_enable = true; |
| |
| ret = mdss_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi, |
| mfd->op_enable); |
| if (ret) { |
| pr_err("can't turn on display!\n"); |
| unlock_fb_info(mfd->fbi); |
| return ret; |
| } |
| unlock_fb_info(mfd->fbi); |
| |
| mutex_lock(&mfd->bl_lock); |
| mfd->allow_bl_update = true; |
| mdss_fb_set_backlight(mfd, bl_lvl); |
| mutex_unlock(&mfd->bl_lock); |
| |
| pdata->panel_info.dynamic_switch_pending = false; |
| pdata->panel_info.is_lpm_mode = mode ? 1 : 0; |
| |
| if (ret) { |
| pr_err("can't turn on display!\n"); |
| return ret; |
| } |
| |
| pr_debug("Exit mode: %d\n", mode); |
| |
| return 0; |
| } |
| |
| static ssize_t mdss_fb_change_dfps_mode(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t len) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| struct mdss_panel_data *pdata; |
| struct mdss_panel_info *pinfo; |
| u32 dfps_mode; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected!\n"); |
| return len; |
| } |
| pinfo = &pdata->panel_info; |
| |
| if (kstrtouint(buf, 0, &dfps_mode)) { |
| pr_err("kstrtouint buf error!\n"); |
| return len; |
| } |
| |
| if (dfps_mode >= DFPS_MODE_MAX) { |
| pinfo->dynamic_fps = false; |
| return len; |
| } |
| |
| if (mfd->idle_time != 0) { |
| pr_err("ERROR: Idle time is not disabled.\n"); |
| return len; |
| } |
| |
| if (pinfo->current_fps != pinfo->default_fps) { |
| pr_err("ERROR: panel not configured to default fps\n"); |
| return len; |
| } |
| |
| pinfo->dynamic_fps = true; |
| pinfo->dfps_update = dfps_mode; |
| |
| if (pdata->next) |
| pdata->next->panel_info.dfps_update = dfps_mode; |
| |
| return len; |
| } |
| |
| static ssize_t mdss_fb_get_dfps_mode(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| struct mdss_panel_data *pdata; |
| struct mdss_panel_info *pinfo; |
| int ret; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected!\n"); |
| return -EINVAL; |
| } |
| pinfo = &pdata->panel_info; |
| |
| ret = scnprintf(buf, PAGE_SIZE, "dfps enabled=%d mode=%d\n", |
| pinfo->dynamic_fps, pinfo->dfps_update); |
| |
| return ret; |
| } |
| |
| static ssize_t mdss_fb_change_persist_mode(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t len) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| struct mdss_panel_info *pinfo = NULL; |
| struct mdss_panel_data *pdata; |
| int ret = 0; |
| u32 persist_mode; |
| |
| if (!mfd || !mfd->panel_info) { |
| pr_err("%s: Panel info is NULL!\n", __func__); |
| return len; |
| } |
| |
| pinfo = mfd->panel_info; |
| |
| if (kstrtouint(buf, 0, &persist_mode)) { |
| pr_err("kstrtouint buf error!\n"); |
| return len; |
| } |
| |
| mutex_lock(&mfd->mdss_sysfs_lock); |
| if (mdss_panel_is_power_off(mfd->panel_power_state)) { |
| pinfo->persist_mode = persist_mode; |
| goto end; |
| } |
| |
| mutex_lock(&mfd->bl_lock); |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if ((pdata) && (pdata->apply_display_setting)) |
| ret = pdata->apply_display_setting(pdata, persist_mode); |
| |
| mutex_unlock(&mfd->bl_lock); |
| |
| if (!ret) { |
| pr_debug("%s: Persist mode %d\n", __func__, persist_mode); |
| pinfo->persist_mode = persist_mode; |
| } |
| |
| end: |
| mutex_unlock(&mfd->mdss_sysfs_lock); |
| return len; |
| } |
| |
| static ssize_t mdss_fb_get_persist_mode(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct fb_info *fbi = dev_get_drvdata(dev); |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; |
| struct mdss_panel_data *pdata; |
| struct mdss_panel_info *pinfo; |
| int ret; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected!\n"); |
| return -EINVAL; |
| } |
| pinfo = &pdata->panel_info; |
| |
| ret = scnprintf(buf, PAGE_SIZE, "%d\n", pinfo->persist_mode); |
| |
| return ret; |
| } |
| |
| static DEVICE_ATTR(msm_fb_type, 0444, mdss_fb_get_type, NULL); |
| static DEVICE_ATTR(msm_fb_split, 0644, mdss_fb_show_split, |
| mdss_fb_store_split); |
| static DEVICE_ATTR(show_blank_event, 0444, mdss_mdp_show_blank_event, NULL); |
| static DEVICE_ATTR(idle_time, 0644, |
| mdss_fb_get_idle_time, mdss_fb_set_idle_time); |
| static DEVICE_ATTR(idle_notify, 0444, mdss_fb_get_idle_notify, NULL); |
| static DEVICE_ATTR(msm_fb_panel_info, 0444, mdss_fb_get_panel_info, NULL); |
| static DEVICE_ATTR(msm_fb_src_split_info, 0444, mdss_fb_get_src_split_info, |
| NULL); |
| static DEVICE_ATTR(msm_fb_thermal_level, 0644, |
| mdss_fb_get_thermal_level, mdss_fb_set_thermal_level); |
| static DEVICE_ATTR(msm_fb_panel_status, 0644, |
| mdss_fb_get_panel_status, mdss_fb_force_panel_dead); |
| static DEVICE_ATTR(msm_fb_dfps_mode, 0644, |
| mdss_fb_get_dfps_mode, mdss_fb_change_dfps_mode); |
| static DEVICE_ATTR(measured_fps, 0664, |
| mdss_fb_get_fps_info, NULL); |
| static DEVICE_ATTR(msm_fb_persist_mode, 0644, |
| mdss_fb_get_persist_mode, mdss_fb_change_persist_mode); |
| static struct attribute *mdss_fb_attrs[] = { |
| &dev_attr_msm_fb_type.attr, |
| &dev_attr_msm_fb_split.attr, |
| &dev_attr_show_blank_event.attr, |
| &dev_attr_idle_time.attr, |
| &dev_attr_idle_notify.attr, |
| &dev_attr_msm_fb_panel_info.attr, |
| &dev_attr_msm_fb_src_split_info.attr, |
| &dev_attr_msm_fb_thermal_level.attr, |
| &dev_attr_msm_fb_panel_status.attr, |
| &dev_attr_msm_fb_dfps_mode.attr, |
| &dev_attr_measured_fps.attr, |
| &dev_attr_msm_fb_persist_mode.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 void mdss_fb_shutdown(struct platform_device *pdev) |
| { |
| struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); |
| |
| if (!mfd) |
| return; |
| |
| mfd->shutdown_pending = true; |
| |
| /* wake up threads waiting on idle or kickoff queues */ |
| wake_up_all(&mfd->idle_wait_q); |
| wake_up_all(&mfd->kickoff_wait_q); |
| |
| lock_fb_info(mfd->fbi); |
| mdss_fb_release_all(mfd->fbi, true); |
| sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event"); |
| unlock_fb_info(mfd->fbi); |
| } |
| |
| static void mdss_fb_input_event_handler(struct input_handle *handle, |
| unsigned int type, |
| unsigned int code, |
| int value) |
| { |
| struct msm_fb_data_type *mfd = handle->handler->private; |
| int rc; |
| |
| if ((type != EV_ABS) || !mdss_fb_is_power_on(mfd)) |
| return; |
| |
| if (mfd->mdp.input_event_handler) { |
| rc = mfd->mdp.input_event_handler(mfd); |
| if (rc) |
| pr_err("mdp input event handler failed\n"); |
| } |
| } |
| |
| static int mdss_fb_input_connect(struct input_handler *handler, |
| struct input_dev *dev, |
| const struct input_device_id *id) |
| { |
| int rc; |
| struct input_handle *handle; |
| |
| handle = kzalloc(sizeof(*handle), GFP_KERNEL); |
| if (!handle) |
| return -ENOMEM; |
| |
| handle->dev = dev; |
| handle->handler = handler; |
| handle->name = handler->name; |
| |
| rc = input_register_handle(handle); |
| if (rc) { |
| pr_err("failed to register input handle, rc = %d\n", rc); |
| goto error; |
| } |
| |
| rc = input_open_device(handle); |
| if (rc) { |
| pr_err("failed to open input device, rc = %d\n", rc); |
| goto error_unregister; |
| } |
| |
| return 0; |
| |
| error_unregister: |
| input_unregister_handle(handle); |
| error: |
| kfree(handle); |
| return rc; |
| } |
| |
| static void mdss_fb_input_disconnect(struct input_handle *handle) |
| { |
| input_close_device(handle); |
| input_unregister_handle(handle); |
| kfree(handle); |
| } |
| |
| /* |
| * Structure for specifying event parameters on which to receive callbacks. |
| * This structure will trigger a callback in case of a touch event (specified by |
| * EV_ABS) where there is a change in X and Y coordinates, |
| */ |
| static const struct input_device_id mdss_fb_input_ids[] = { |
| { |
| .flags = INPUT_DEVICE_ID_MATCH_EVBIT, |
| .evbit = { BIT_MASK(EV_ABS) }, |
| .absbit = { [BIT_WORD(ABS_MT_POSITION_X)] = |
| BIT_MASK(ABS_MT_POSITION_X) | |
| BIT_MASK(ABS_MT_POSITION_Y) }, |
| }, |
| { }, |
| }; |
| |
| static int mdss_fb_register_input_handler(struct msm_fb_data_type *mfd) |
| { |
| int rc; |
| struct input_handler *handler; |
| |
| if (mfd->input_handler) |
| return -EINVAL; |
| |
| handler = kzalloc(sizeof(*handler), GFP_KERNEL); |
| if (!handler) |
| return -ENOMEM; |
| |
| handler->event = mdss_fb_input_event_handler; |
| handler->connect = mdss_fb_input_connect; |
| handler->disconnect = mdss_fb_input_disconnect, |
| handler->name = "mdss_fb", |
| handler->id_table = mdss_fb_input_ids; |
| handler->private = mfd; |
| |
| rc = input_register_handler(handler); |
| if (rc) { |
| pr_err("Unable to register the input handler\n"); |
| kfree(handler); |
| } else { |
| mfd->input_handler = handler; |
| } |
| |
| return rc; |
| } |
| |
| static void mdss_fb_unregister_input_handler(struct msm_fb_data_type *mfd) |
| { |
| if (!mfd->input_handler) |
| return; |
| |
| input_unregister_handler(mfd->input_handler); |
| kfree(mfd->input_handler); |
| } |
| |
| static void mdss_fb_videomode_from_panel_timing(struct fb_videomode *videomode, |
| struct mdss_panel_timing *pt) |
| { |
| videomode->name = pt->name; |
| videomode->xres = pt->xres; |
| videomode->yres = pt->yres; |
| videomode->left_margin = pt->h_back_porch; |
| videomode->right_margin = pt->h_front_porch; |
| videomode->hsync_len = pt->h_pulse_width; |
| videomode->upper_margin = pt->v_back_porch; |
| videomode->lower_margin = pt->v_front_porch; |
| videomode->vsync_len = pt->v_pulse_width; |
| videomode->refresh = pt->frame_rate; |
| videomode->flag = 0; |
| videomode->vmode = 0; |
| videomode->sync = 0; |
| |
| if (videomode->refresh) { |
| unsigned long clk_rate, h_total, v_total; |
| |
| h_total = videomode->xres + videomode->left_margin |
| + videomode->right_margin + videomode->hsync_len; |
| v_total = videomode->yres + videomode->lower_margin |
| + videomode->upper_margin + videomode->vsync_len; |
| clk_rate = h_total * v_total * videomode->refresh; |
| videomode->pixclock = |
| KHZ2PICOS(clk_rate / 1000); |
| } else { |
| videomode->pixclock = |
| KHZ2PICOS((unsigned long)pt->clk_rate / 1000); |
| } |
| } |
| |
| static void mdss_fb_set_split_mode(struct msm_fb_data_type *mfd, |
| struct mdss_panel_data *pdata) |
| { |
| if (pdata->panel_info.is_split_display) { |
| struct mdss_panel_data *pnext = pdata->next; |
| |
| mfd->split_fb_left = pdata->panel_info.lm_widths[0]; |
| if (pnext) |
| mfd->split_fb_right = pnext->panel_info.lm_widths[0]; |
| |
| if (pdata->panel_info.use_pingpong_split) |
| mfd->split_mode = MDP_PINGPONG_SPLIT; |
| else |
| mfd->split_mode = MDP_DUAL_LM_DUAL_DISPLAY; |
| } else if ((pdata->panel_info.lm_widths[0] != 0) |
| && (pdata->panel_info.lm_widths[1] != 0)) { |
| mfd->split_fb_left = pdata->panel_info.lm_widths[0]; |
| mfd->split_fb_right = pdata->panel_info.lm_widths[1]; |
| mfd->split_mode = MDP_DUAL_LM_SINGLE_DISPLAY; |
| } else { |
| mfd->split_mode = MDP_SPLIT_MODE_NONE; |
| } |
| } |
| |
| static int mdss_fb_init_panel_modes(struct msm_fb_data_type *mfd, |
| struct mdss_panel_data *pdata) |
| { |
| struct fb_info *fbi = mfd->fbi; |
| struct fb_videomode *modedb; |
| struct mdss_panel_timing *pt; |
| struct list_head *pos; |
| int num_timings = 0; |
| int i = 0; |
| |
| /* check if multiple modes are supported */ |
| if (!pdata->timings_list.prev || !pdata->timings_list.next) |
| INIT_LIST_HEAD(&pdata->timings_list); |
| |
| if (!fbi || !pdata->current_timing || list_empty(&pdata->timings_list)) |
| return 0; |
| |
| list_for_each(pos, &pdata->timings_list) |
| num_timings++; |
| |
| modedb = devm_kzalloc(fbi->dev, num_timings * sizeof(*modedb), |
| GFP_KERNEL); |
| if (!modedb) |
| return -ENOMEM; |
| |
| list_for_each_entry(pt, &pdata->timings_list, list) { |
| struct mdss_panel_timing *spt = NULL; |
| |
| mdss_fb_videomode_from_panel_timing(modedb + i, pt); |
| if (pdata->next) { |
| spt = mdss_panel_get_timing_by_name(pdata->next, |
| modedb[i].name); |
| if (!IS_ERR_OR_NULL(spt)) |
| modedb[i].xres += spt->xres; |
| else |
| pr_debug("no matching split config for %s\n", |
| modedb[i].name); |
| |
| /* |
| * if no panel timing found for current, need to |
| * disable it otherwise mark it as active |
| */ |
| if (pt == pdata->current_timing) |
| pdata->next->active = !IS_ERR_OR_NULL(spt); |
| } |
| |
| if (pt == pdata->current_timing) { |
| pr_debug("found current mode: %s\n", pt->name); |
| fbi->mode = modedb + i; |
| } |
| i++; |
| } |
| |
| fbi->monspecs.modedb = modedb; |
| fbi->monspecs.modedb_len = num_timings; |
| |
| /* destroy and recreate modelist */ |
| fb_destroy_modelist(&fbi->modelist); |
| |
| if (fbi->mode) |
| fb_videomode_to_var(&fbi->var, fbi->mode); |
| fb_videomode_to_modelist(modedb, num_timings, &fbi->modelist); |
| |
| return 0; |
| } |
| |
| 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; |
| |
| if (!mdp_instance) { |
| pr_err("mdss mdp resource not initialized yet\n"); |
| return -ENODEV; |
| } |
| |
| /* |
| * 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->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE; |
| |
| mfd->ext_ad_ctrl = -1; |
| if (mfd->panel_info && mfd->panel_info->brightness_max > 0) |
| MDSS_BRIGHT_TO_BL(mfd->bl_level, backlight_led.brightness, |
| mfd->panel_info->bl_max, mfd->panel_info->brightness_max); |
| else |
| mfd->bl_level = 0; |
| |
| mfd->bl_scale = 1024; |
| mfd->bl_min_lvl = 30; |
| mfd->ad_bl_level = 0; |
| mfd->fb_imgType = MDP_RGBA_8888; |
| mfd->calib_mode_bl = 0; |
| mfd->unset_bl_level = U32_MAX; |
| |
| mfd->pdev = pdev; |
| |
| mfd->split_fb_left = mfd->split_fb_right = 0; |
| |
| mdss_fb_set_split_mode(mfd, pdata); |
| pr_info("fb%d: split_mode:%d left:%d right:%d\n", mfd->index, |
| mfd->split_mode, mfd->split_fb_left, mfd->split_fb_right); |
| |
| mfd->mdp = *mdp_instance; |
| |
| rc = of_property_read_bool(pdev->dev.of_node, |
| "qcom,boot-indication-enabled"); |
| |
| if (rc) { |
| led_trigger_register_simple("boot-indication", |
| &(mfd->boot_notification_led)); |
| } |
| |
| INIT_LIST_HEAD(&mfd->file_list); |
| |
| mutex_init(&mfd->bl_lock); |
| mutex_init(&mfd->mdss_sysfs_lock); |
| mutex_init(&mfd->switch_lock); |
| |
| fbi_list[fbi_list_index++] = fbi; |
| |
| platform_set_drvdata(pdev, mfd); |
| |
| rc = mdss_fb_register(mfd); |
| if (rc) |
| return rc; |
| |
| mdss_fb_create_sysfs(mfd); |
| mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi); |
| |
| if (mfd->mdp.init_fnc) { |
| rc = mfd->mdp.init_fnc(mfd); |
| if (rc) { |
| pr_err("init_fnc failed\n"); |
| return rc; |
| } |
| } |
| mdss_fb_init_fps_info(mfd); |
| |
| 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) { |
| backlight_led.brightness = mfd->panel_info->brightness_max; |
| backlight_led.max_brightness = mfd->panel_info->brightness_max; |
| if (led_classdev_register(&pdev->dev, &backlight_led)) |
| pr_err("led_classdev_register failed\n"); |
| else |
| lcd_backlight_registered = 1; |
| } |
| |
| mdss_fb_init_panel_modes(mfd, pdata); |
| |
| mfd->mdp_sync_pt_data.fence_name = "mdp-fence"; |
| if (mfd->mdp_sync_pt_data.timeline == NULL) { |
| char timeline_name[16]; |
| |
| snprintf(timeline_name, sizeof(timeline_name), |
| "mdss_fb_%d", mfd->index); |
| mfd->mdp_sync_pt_data.timeline = |
| sw_sync_timeline_create(timeline_name); |
| if (mfd->mdp_sync_pt_data.timeline == NULL) { |
| pr_err("cannot create release fence time line\n"); |
| return -ENOMEM; |
| } |
| mfd->mdp_sync_pt_data.notifier.notifier_call = |
| __mdss_fb_sync_buf_done_callback; |
| } |
| |
| mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type); |
| |
| if (mfd->mdp.splash_init_fnc) |
| mfd->mdp.splash_init_fnc(mfd); |
| |
| /* |
| * Register with input driver for a callback for command mode panels. |
| * When there is an input event, mdp clocks will be turned on to reduce |
| * latency when a frame update happens. |
| * For video mode panels, idle timeout will be delayed so that userspace |
| * does not get an idle event while new frames are expected. In case of |
| * an idle event, user space tries to fall back to GPU composition which |
| * can lead to increased load when there are new frames. |
| */ |
| if (mfd->mdp.input_event_handler && |
| ((mfd->panel_info->type == MIPI_CMD_PANEL) || |
| (mfd->panel_info->type == MIPI_VIDEO_PANEL))) |
| if (mdss_fb_register_input_handler(mfd)) |
| pr_err("failed to register input handler\n"); |
| |
| INIT_DELAYED_WORK(&mfd->idle_notify_work, __mdss_fb_idle_notify_work); |
| |
| return rc; |
| } |
| |
| static void mdss_fb_set_mdp_sync_pt_threshold(struct msm_fb_data_type *mfd, |
| int type) |
| { |
| if (!mfd) |
| return; |
| |
| switch (type) { |
| case WRITEBACK_PANEL: |
| mfd->mdp_sync_pt_data.threshold = 1; |
| mfd->mdp_sync_pt_data.retire_threshold = 0; |
| break; |
| case MIPI_CMD_PANEL: |
| mfd->mdp_sync_pt_data.threshold = 1; |
| mfd->mdp_sync_pt_data.retire_threshold = 1; |
| break; |
| default: |
| mfd->mdp_sync_pt_data.threshold = 2; |
| mfd->mdp_sync_pt_data.retire_threshold = 0; |
| break; |
| } |
| } |
| |
| 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; |
| |
| mdss_fb_remove_sysfs(mfd); |
| |
| pm_runtime_disable(mfd->fbi->dev); |
| |
| if (mfd->key != MFD_KEY) |
| return -EINVAL; |
| |
| mdss_fb_unregister_input_handler(mfd); |
| mdss_panel_debugfs_cleanup(mfd->panel_info); |
| |
| 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_send_panel_event(struct msm_fb_data_type *mfd, |
| int event, void *arg) |
| { |
| int ret = 0; |
| struct mdss_panel_data *pdata; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected\n"); |
| return -ENODEV; |
| } |
| |
| pr_debug("sending event=%d for fb%d\n", event, mfd->index); |
| |
| do { |
| if (pdata->event_handler) |
| ret = pdata->event_handler(pdata, event, arg); |
| |
| pdata = pdata->next; |
| } while (!ret && pdata); |
| |
| return ret; |
| } |
| |
| 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); |
| |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n", |
| mfd->index, ret); |
| goto exit; |
| } |
| |
| ret = mdss_fb_send_panel_event(mfd, MDSS_EVENT_SUSPEND, NULL); |
| if (ret) { |
| pr_warn("unable to suspend fb%d (%d)\n", mfd->index, ret); |
| goto exit; |
| } |
| |
| 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"); |
| goto exit; |
| } |
| } |
| mfd->op_enable = false; |
| fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED); |
| } |
| exit: |
| return ret; |
| } |
| |
| static int mdss_fb_resume_sub(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| if ((!mfd) || (mfd->key != MFD_KEY)) |
| return 0; |
| |
| reinit_completion(&mfd->power_set_comp); |
| mfd->is_power_setting = true; |
| pr_debug("mdss_fb resume index=%d\n", mfd->index); |
| |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n", |
| mfd->index, ret); |
| return ret; |
| } |
| |
| ret = mdss_fb_send_panel_event(mfd, MDSS_EVENT_RESUME, NULL); |
| if (ret) { |
| pr_warn("unable to resume fb%d (%d)\n", mfd->index, ret); |
| return ret; |
| } |
| |
| /* 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); |
| } |
| mfd->is_power_setting = false; |
| complete_all(&mfd->power_set_comp); |
| |
| 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 = "qcom,mdss-fb",}, |
| {} |
| }; |
| EXPORT_COMPAT("qcom,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 void mdss_fb_scale_bl(struct msm_fb_data_type *mfd, u32 *bl_lvl) |
| { |
| u32 temp = *bl_lvl; |
| |
| pr_debug("input = %d, scale = %d\n", temp, mfd->bl_scale); |
| if (temp >= mfd->bl_min_lvl) { |
| if (temp > mfd->panel_info->bl_max) { |
| pr_warn("%s: invalid bl level\n", |
| __func__); |
| temp = mfd->panel_info->bl_max; |
| } |
| if (mfd->bl_scale > 1024) { |
| pr_warn("%s: invalid bl scale\n", |
| __func__); |
| mfd->bl_scale = 1024; |
| } |
| /* |
| * bl_scale is the numerator of |
| * scaling fraction (x/1024) |
| */ |
| temp = (temp * mfd->bl_scale) / 1024; |
| |
| /*if less than minimum level, use min level*/ |
| if (temp < mfd->bl_min_lvl) |
| temp = mfd->bl_min_lvl; |
| } |
| pr_debug("output = %d\n", temp); |
| |
| (*bl_lvl) = temp; |
| } |
| |
| /* must call this function from within mfd->bl_lock */ |
| void mdss_fb_set_backlight(struct msm_fb_data_type *mfd, u32 bkl_lvl) |
| { |
| struct mdss_panel_data *pdata; |
| u32 temp = bkl_lvl; |
| bool ad_bl_notify_needed = false; |
| bool bl_notify_needed = false; |
| |
| if ((((mdss_fb_is_power_off(mfd) && mfd->dcm_state != DCM_ENTER) |
| || !mfd->allow_bl_update) && !IS_CALIB_MODE_BL(mfd)) || |
| mfd->panel_info->cont_splash_enabled) { |
| mfd->unset_bl_level = bkl_lvl; |
| return; |
| } else if (mdss_fb_is_power_on(mfd) && mfd->panel_info->panel_dead) { |
| mfd->unset_bl_level = mfd->bl_level; |
| } else { |
| mfd->unset_bl_level = U32_MAX; |
| } |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| |
| if ((pdata) && (pdata->set_backlight)) { |
| if (mfd->mdp.ad_calc_bl) |
| (*mfd->mdp.ad_calc_bl)(mfd, temp, &temp, |
| &ad_bl_notify_needed); |
| if (!IS_CALIB_MODE_BL(mfd)) |
| mdss_fb_scale_bl(mfd, &temp); |
| /* |
| * Even though backlight has been scaled, want to show that |
| * backlight has been set to bkl_lvl to those that read from |
| * sysfs node. Thus, need to set bl_level even if it appears |
| * the backlight has already been set to the level it is at, |
| * as well as setting bl_level to bkl_lvl even though the |
| * backlight has been set to the scaled value. |
| */ |
| if (mfd->bl_level_scaled == temp) { |
| mfd->bl_level = bkl_lvl; |
| } else { |
| if (mfd->bl_level != bkl_lvl) |
| bl_notify_needed = true; |
| pr_debug("backlight sent to panel :%d\n", temp); |
| pdata->set_backlight(pdata, temp); |
| mfd->bl_level = bkl_lvl; |
| mfd->bl_level_scaled = temp; |
| } |
| if (ad_bl_notify_needed) |
| mdss_fb_bl_update_notify(mfd, |
| NOTIFY_TYPE_BL_AD_ATTEN_UPDATE); |
| if (bl_notify_needed) |
| mdss_fb_bl_update_notify(mfd, |
| NOTIFY_TYPE_BL_UPDATE); |
| } |
| } |
| |
| void mdss_fb_update_backlight(struct msm_fb_data_type *mfd) |
| { |
| struct mdss_panel_data *pdata; |
| u32 temp; |
| bool bl_notify = false; |
| |
| if (mfd->unset_bl_level == U32_MAX) |
| return; |
| mutex_lock(&mfd->bl_lock); |
| if (!mfd->allow_bl_update) { |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if ((pdata) && (pdata->set_backlight)) { |
| mfd->bl_level = mfd->unset_bl_level; |
| temp = mfd->bl_level; |
| if (mfd->mdp.ad_calc_bl) |
| (*mfd->mdp.ad_calc_bl)(mfd, temp, &temp, |
| &bl_notify); |
| if (bl_notify) |
| mdss_fb_bl_update_notify(mfd, |
| NOTIFY_TYPE_BL_AD_ATTEN_UPDATE); |
| mdss_fb_bl_update_notify(mfd, NOTIFY_TYPE_BL_UPDATE); |
| pdata->set_backlight(pdata, temp); |
| mfd->bl_level_scaled = mfd->unset_bl_level; |
| mfd->allow_bl_update = true; |
| } |
| } |
| mutex_unlock(&mfd->bl_lock); |
| } |
| |
| static int mdss_fb_start_disp_thread(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| pr_debug("%pS: start display thread fb%d\n", |
| __builtin_return_address(0), mfd->index); |
| |
| /* this is needed for new split request from debugfs */ |
| mdss_fb_get_split(mfd); |
| |
| atomic_set(&mfd->commits_pending, 0); |
| mfd->disp_thread = kthread_run(__mdss_fb_display_thread, |
| mfd, "mdss_fb%d", mfd->index); |
| |
| if (IS_ERR(mfd->disp_thread)) { |
| pr_err("ERROR: unable to start display thread %d\n", |
| mfd->index); |
| ret = PTR_ERR(mfd->disp_thread); |
| mfd->disp_thread = NULL; |
| } |
| |
| return ret; |
| } |
| |
| static void mdss_fb_stop_disp_thread(struct msm_fb_data_type *mfd) |
| { |
| pr_debug("%pS: stop display thread fb%d\n", |
| __builtin_return_address(0), mfd->index); |
| |
| kthread_stop(mfd->disp_thread); |
| mfd->disp_thread = NULL; |
| } |
| |
| static void mdss_panel_validate_debugfs_info(struct msm_fb_data_type *mfd) |
| { |
| struct mdss_panel_info *panel_info = mfd->panel_info; |
| struct fb_info *fbi = mfd->fbi; |
| struct fb_var_screeninfo *var = &fbi->var; |
| struct mdss_panel_data *pdata = container_of(panel_info, |
| struct mdss_panel_data, panel_info); |
| |
| if (panel_info->debugfs_info->override_flag) { |
| if (mfd->mdp.off_fnc) { |
| mfd->panel_reconfig = true; |
| mfd->mdp.off_fnc(mfd); |
| mfd->panel_reconfig = false; |
| } |
| |
| pr_debug("Overriding panel_info with debugfs_info\n"); |
| panel_info->debugfs_info->override_flag = 0; |
| mdss_panel_debugfsinfo_to_panelinfo(panel_info); |
| if (is_panel_split(mfd) && pdata->next) |
| mdss_fb_validate_split(pdata->panel_info.xres, |
| pdata->next->panel_info.xres, mfd); |
| mdss_panelinfo_to_fb_var(panel_info, var); |
| if (mdss_fb_send_panel_event(mfd, MDSS_EVENT_CHECK_PARAMS, |
| panel_info)) |
| pr_err("Failed to send panel event CHECK_PARAMS\n"); |
| } |
| } |
| |
| static int mdss_fb_blank_blank(struct msm_fb_data_type *mfd, |
| int req_power_state) |
| { |
| int ret = 0; |
| int cur_power_state, current_bl; |
| |
| 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; |
| } |
| |
| mutex_lock(&mfd->update.lock); |
| mfd->update.type = NOTIFY_TYPE_SUSPEND; |
| mfd->update.is_suspend = 1; |
| mutex_unlock(&mfd->update.lock); |
| complete(&mfd->update.comp); |
| del_timer(&mfd->no_update.timer); |
| mfd->no_update.value = NOTIFY_TYPE_SUSPEND; |
| complete(&mfd->no_update.comp); |
| |
| mfd->op_enable = false; |
| if (mdss_panel_is_power_off(req_power_state)) { |
| /* Stop Display thread */ |
| if (mfd->disp_thread) |
| mdss_fb_stop_disp_thread(mfd); |
| mutex_lock(&mfd->bl_lock); |
| current_bl = mfd->bl_level; |
| mfd->allow_bl_update = true; |
| mdss_fb_set_backlight(mfd, 0); |
| mfd->allow_bl_update = false; |
| mfd->unset_bl_level = current_bl; |
| mutex_unlock(&mfd->bl_lock); |
| } |
| mfd->panel_power_state = req_power_state; |
| |
| ret = mfd->mdp.off_fnc(mfd); |
| if (ret) |
| mfd->panel_power_state = cur_power_state; |
| else if (mdss_panel_is_power_off(req_power_state)) |
| mdss_fb_release_fences(mfd); |
| mfd->op_enable = true; |
| complete(&mfd->power_off_comp); |
| |
| 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; |
| |
| if (mfd->panel_info->debugfs_info) |
| mdss_panel_validate_debugfs_info(mfd); |
| |
| /* Start Display thread */ |
| if (mfd->disp_thread == NULL) { |
| ret = mdss_fb_start_disp_thread(mfd); |
| if (IS_ERR_VALUE(ret)) |
| return ret; |
| } |
| |
| 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) { |
| struct mdss_panel_info *panel_info = mfd->panel_info; |
| struct fb_var_screeninfo *var = &mfd->fbi->var; |
| |
| ret = mfd->mdp.on_fnc(mfd); |
| if (ret) { |
| mdss_fb_stop_disp_thread(mfd); |
| goto error; |
| } |
| |
| mfd->panel_power_state = MDSS_PANEL_POWER_ON; |
| mfd->panel_info->panel_dead = false; |
| mutex_lock(&mfd->update.lock); |
| mfd->update.type = NOTIFY_TYPE_UPDATE; |
| mfd->update.is_suspend = 0; |
| mutex_unlock(&mfd->update.lock); |
| |
| /* |
| * Panel info can change depending in the information |
| * programmed in the controller. |
| * Update this info in the upstream structs. |
| */ |
| mdss_panelinfo_to_fb_var(panel_info, var); |
| |
| /* Start the work thread to signal idle time */ |
| if (mfd->idle_time) |
| schedule_delayed_work(&mfd->idle_notify_work, |
| msecs_to_jiffies(mfd->idle_time)); |
| } |
| |
| /* Reset the backlight only if the panel was off */ |
| if (mdss_panel_is_power_off(cur_power_state)) { |
| mutex_lock(&mfd->bl_lock); |
| if (!mfd->allow_bl_update) { |
| mfd->allow_bl_update = true; |
| /* |
| * If in AD calibration mode then frameworks would not |
| * be allowed to update backlight hence post unblank |
| * the backlight would remain 0 (0 is set in blank). |
| * Hence resetting back to calibration mode value |
| */ |
| if (IS_CALIB_MODE_BL(mfd)) |
| mdss_fb_set_backlight(mfd, mfd->calib_mode_bl); |
| else if ((!mfd->panel_info->mipi.post_init_delay) && |
| (mfd->unset_bl_level != U32_MAX)) |
| mdss_fb_set_backlight(mfd, mfd->unset_bl_level); |
| |
| /* |
| * it blocks the backlight update between unblank and |
| * first kickoff to avoid backlight turn on before black |
| * frame is transferred to panel through unblank call. |
| */ |
| mfd->allow_bl_update = false; |
| } |
| mutex_unlock(&mfd->bl_lock); |
| } |
| |
| 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; |
| char trace_buffer[32]; |
| |
| if (!mfd || !op_enable) |
| return -EPERM; |
| |
| if (mfd->dcm_state == DCM_ENTER) |
| return -EPERM; |
| |
| pr_debug("%pS mode:%d\n", __builtin_return_address(0), |
| blank_mode); |
| |
| snprintf(trace_buffer, sizeof(trace_buffer), "fb%d blank %d", |
| mfd->index, blank_mode); |
| ATRACE_BEGIN(trace_buffer); |
| |
| cur_power_state = mfd->panel_power_state; |
| |
| /* |
| * Low power (lp) and ultra low power (ulp) modes are currently only |
| * supported for command mode panels. For all other panel, treat lp |
| * mode as full unblank and ulp mode as full blank. |
| */ |
| if (mfd->panel_info->type != MIPI_CMD_PANEL) { |
| if (blank_mode == BLANK_FLAG_LP) { |
| pr_debug("lp mode only valid for cmd mode panels\n"); |
| if (mdss_fb_is_power_on_interactive(mfd)) |
| return 0; |
| blank_mode = FB_BLANK_UNBLANK; |
| } else if (blank_mode == BLANK_FLAG_ULP) { |
| pr_debug("ulp mode valid for cmd mode panels\n"); |
| if (mdss_fb_is_power_off(mfd)) |
| return 0; |
| blank_mode = FB_BLANK_POWERDOWN; |
| } |
| } |
| |
| 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; |
| } |
| |
| /* Notify listeners */ |
| sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event"); |
| |
| ATRACE_END(trace_buffer); |
| |
| return ret; |
| } |
| |
| static int mdss_fb_blank(int blank_mode, struct fb_info *info) |
| { |
| int ret; |
| struct mdss_panel_data *pdata; |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d\n", |
| mfd->index, ret); |
| return ret; |
| } |
| mutex_lock(&mfd->mdss_sysfs_lock); |
| 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; |
| ret = 0; |
| goto end; |
| } |
| 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, 1); |
| mdss_fb_set_mdp_sync_pt_threshold(mfd, mfd->panel.type); |
| pdata->panel_info.is_lpm_mode = false; |
| } |
| |
| ret = mdss_fb_blank_sub(blank_mode, info, mfd->op_enable); |
| |
| end: |
| mutex_unlock(&mfd->mdss_sysfs_lock); |
| return ret; |
| } |
| |
| static inline int mdss_fb_create_ion_client(struct msm_fb_data_type *mfd) |
| { |
| mfd->fb_ion_client = msm_ion_client_create("mdss_fb_iclient"); |
| if (IS_ERR_OR_NULL(mfd->fb_ion_client)) { |
| pr_err("Err:client not created, val %d\n", |
| PTR_RET(mfd->fb_ion_client)); |
| mfd->fb_ion_client = NULL; |
| return PTR_RET(mfd->fb_ion_client); |
| } |
| return 0; |
| } |
| |
| void mdss_fb_free_fb_ion_memory(struct msm_fb_data_type *mfd) |
| { |
| if (!mfd) { |
| pr_err("no mfd\n"); |
| return; |
| } |
| |
| if (!mfd->fbi->screen_base) |
| return; |
| |
| if (!mfd->fb_ion_client || !mfd->fb_ion_handle) { |
| pr_err("invalid input parameters for fb%d\n", mfd->index); |
| return; |
| } |
| |
| mfd->fbi->screen_base = NULL; |
| mfd->fbi->fix.smem_start = 0; |
| |
| ion_unmap_kernel(mfd->fb_ion_client, mfd->fb_ion_handle); |
| |
| if (mfd->mdp.fb_mem_get_iommu_domain && !(!mfd->fb_attachment || |
| !mfd->fb_attachment->dmabuf || |
| !mfd->fb_attachment->dmabuf->ops)) { |
| dma_buf_unmap_attachment(mfd->fb_attachment, mfd->fb_table, |
| DMA_BIDIRECTIONAL); |
| dma_buf_detach(mfd->fbmem_buf, mfd->fb_attachment); |
| dma_buf_put(mfd->fbmem_buf); |
| } |
| |
| ion_free(mfd->fb_ion_client, mfd->fb_ion_handle); |
| mfd->fb_ion_handle = NULL; |
| mfd->fbmem_buf = NULL; |
| } |
| |
| int mdss_fb_alloc_fb_ion_memory(struct msm_fb_data_type *mfd, size_t fb_size) |
| { |
| int rc = 0; |
| void *vaddr; |
| int domain; |
| |
| if (!mfd) { |
| pr_err("Invalid input param - no mfd\n"); |
| return -EINVAL; |
| } |
| |
| if (!mfd->fb_ion_client) { |
| rc = mdss_fb_create_ion_client(mfd); |
| if (rc < 0) { |
| pr_err("fb ion client couldn't be created - %d\n", rc); |
| return rc; |
| } |
| } |
| |
| pr_debug("size for mmap = %zu\n", fb_size); |
| mfd->fb_ion_handle = ion_alloc(mfd->fb_ion_client, fb_size, SZ_4K, |
| ION_HEAP(ION_SYSTEM_HEAP_ID), 0); |
| if (IS_ERR_OR_NULL(mfd->fb_ion_handle)) { |
| pr_err("unable to alloc fbmem from ion - %ld\n", |
| PTR_ERR(mfd->fb_ion_handle)); |
| return PTR_ERR(mfd->fb_ion_handle); |
| } |
| |
| if (mfd->mdp.fb_mem_get_iommu_domain) { |
| mfd->fbmem_buf = ion_share_dma_buf(mfd->fb_ion_client, |
| mfd->fb_ion_handle); |
| if (IS_ERR(mfd->fbmem_buf)) { |
| rc = PTR_ERR(mfd->fbmem_buf); |
| goto fb_mmap_failed; |
| } |
| |
| domain = mfd->mdp.fb_mem_get_iommu_domain(); |
| |
| mfd->fb_attachment = mdss_smmu_dma_buf_attach(mfd->fbmem_buf, |
| &mfd->pdev->dev, domain); |
| if (IS_ERR(mfd->fb_attachment)) { |
| rc = PTR_ERR(mfd->fb_attachment); |
| goto err_put; |
| } |
| |
| mfd->fb_table = dma_buf_map_attachment(mfd->fb_attachment, |
| DMA_BIDIRECTIONAL); |
| if (IS_ERR(mfd->fb_table)) { |
| rc = PTR_ERR(mfd->fb_table); |
| goto err_detach; |
| } |
| } else { |
| pr_err("No IOMMU Domain\n"); |
| rc = -EINVAL; |
| goto fb_mmap_failed; |
| } |
| |
| vaddr = ion_map_kernel(mfd->fb_ion_client, mfd->fb_ion_handle); |
| if (IS_ERR_OR_NULL(vaddr)) { |
| pr_err("ION memory mapping failed - %ld\n", PTR_ERR(vaddr)); |
| rc = PTR_ERR(vaddr); |
| goto err_unmap; |
| } |
| pr_debug("alloc 0x%zxB vaddr = %pK for fb%d\n", fb_size, |
| vaddr, mfd->index); |
| |
| mfd->fbi->screen_base = (char *) vaddr; |
| mfd->fbi->fix.smem_len = fb_size; |
| |
| return rc; |
| |
| err_unmap: |
| dma_buf_unmap_attachment(mfd->fb_attachment, mfd->fb_table, |
| DMA_BIDIRECTIONAL); |
| err_detach: |
| dma_buf_detach(mfd->fbmem_buf, mfd->fb_attachment); |
| err_put: |
| dma_buf_put(mfd->fbmem_buf); |
| fb_mmap_failed: |
| ion_free(mfd->fb_ion_client, mfd->fb_ion_handle); |
| mfd->fb_attachment = NULL; |
| mfd->fb_table = NULL; |
| mfd->fb_ion_handle = NULL; |
| mfd->fbmem_buf = NULL; |
| return rc; |
| } |
| |
| /** |
| * mdss_fb_fbmem_ion_mmap() - Custom fb mmap() function for MSM driver. |
| * |
| * @info - Framebuffer info. |
| * @vma - VM area which is part of the process virtual memory. |
| * |
| * This framebuffer mmap function differs from standard mmap() function by |
| * allowing for customized page-protection and dynamically allocate framebuffer |
| * memory from system heap and map to iommu virtual address. |
| * |
| * Return: virtual address is returned through vma |
| */ |
| static int mdss_fb_fbmem_ion_mmap(struct fb_info *info, |
| struct vm_area_struct *vma) |
| { |
| int rc = 0; |
| size_t req_size, fb_size; |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct sg_table *table; |
| unsigned long addr = vma->vm_start; |
| unsigned long offset = vma->vm_pgoff * PAGE_SIZE; |
| struct scatterlist *sg; |
| unsigned int i; |
| struct page *page; |
| |
| if (!mfd || !mfd->pdev || !mfd->pdev->dev.of_node) { |
| pr_err("Invalid device node\n"); |
| return -ENODEV; |
| } |
| |
| req_size = vma->vm_end - vma->vm_start; |
| fb_size = mfd->fbi->fix.smem_len; |
| if (req_size > fb_size) { |
| pr_warn("requested map is greater than framebuffer\n"); |
| return -EOVERFLOW; |
| } |
| |
| if (!mfd->fbi->screen_base) { |
| rc = mdss_fb_alloc_fb_ion_memory(mfd, fb_size); |
| if (rc < 0) { |
| pr_err("fb mmap failed!!!!\n"); |
| return rc; |
| } |
| } |
| |
| table = mfd->fb_table; |
| if (IS_ERR(table)) { |
| pr_err("Unable to get sg_table from ion:%ld\n", PTR_ERR(table)); |
| mfd->fbi->screen_base = NULL; |
| return PTR_ERR(table); |
| } else if (!table) { |
| pr_err("sg_list is NULL\n"); |
| mfd->fbi->screen_base = NULL; |
| return -EINVAL; |
| } |
| |
| page = sg_page(table->sgl); |
| if (page) { |
| for_each_sg(table->sgl, sg, table->nents, i) { |
| unsigned long remainder = vma->vm_end - addr; |
| unsigned long len = sg->length; |
| |
| page = sg_page(sg); |
| |
| if (offset >= sg->length) { |
| offset -= sg->length; |
| continue; |
| } else if (offset) { |
| page += offset / PAGE_SIZE; |
| len = sg->length - offset; |
| offset = 0; |
| } |
| len = min(len, remainder); |
| |
| if (mfd->mdp_fb_page_protection == |
| MDP_FB_PAGE_PROTECTION_WRITECOMBINE) |
| vma->vm_page_prot = |
| pgprot_writecombine(vma->vm_page_prot); |
| |
| pr_debug("vma=%pK, addr=%x len=%ld\n", |
| vma, (unsigned int)addr, len); |
| pr_debug("vm_start=%x vm_end=%x vm_page_prot=%ld\n", |
| (unsigned int)vma->vm_start, |
| (unsigned int)vma->vm_end, |
| (unsigned long int)vma->vm_page_prot); |
| |
| io_remap_pfn_range(vma, addr, page_to_pfn(page), len, |
| vma->vm_page_prot); |
| addr += len; |
| if (addr >= vma->vm_end) |
| break; |
| } |
| } else { |
| pr_err("PAGE is null\n"); |
| mdss_fb_free_fb_ion_memory(mfd); |
| return -ENOMEM; |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * mdss_fb_physical_mmap() - Custom fb mmap() function for MSM driver. |
| * |
| * @info - Framebuffer info. |
| * @vma - VM area which is part of the process virtual memory. |
| * |
| * This framebuffer mmap function differs from standard mmap() function as |
| * map to framebuffer memory from the CMA memory which is allocated during |
| * bootup. |
| * |
| * Return: virtual address is returned through vma |
| */ |
| static int mdss_fb_physical_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 (!start) { |
| pr_warn("No framebuffer memory is allocated\n"); |
| return -ENOMEM; |
| } |
| |
| /* Set VM flags. */ |
| start &= PAGE_MASK; |
| if ((vma->vm_end <= vma->vm_start) || |
| (off >= len) || |
| ((vma->vm_end - vma->vm_start) > (len - off))) |
| return -EINVAL; |
| off += start; |
| if (off < start) |
| return -EINVAL; |
| vma->vm_pgoff = off >> PAGE_SHIFT; |
| /* This is an IO map - tell maydump to skip this VMA */ |
| vma->vm_flags |= VM_IO; |
| |
| if (mfd->mdp_fb_page_protection == MDP_FB_PAGE_PROTECTION_WRITECOMBINE) |
| vma->vm_page_prot = pgprot_writecombine(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 int mdss_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| int rc = -EINVAL; |
| |
| if (mfd->fb_mmap_type == MDP_FB_MMAP_ION_ALLOC) { |
| rc = mdss_fb_fbmem_ion_mmap(info, vma); |
| } else if (mfd->fb_mmap_type == MDP_FB_MMAP_PHYSICAL_ALLOC) { |
| rc = mdss_fb_physical_mmap(info, vma); |
| } else { |
| if (!info->fix.smem_start && !mfd->fb_ion_handle) { |
| rc = mdss_fb_fbmem_ion_mmap(info, vma); |
| mfd->fb_mmap_type = MDP_FB_MMAP_ION_ALLOC; |
| } else { |
| rc = mdss_fb_physical_mmap(info, vma); |
| mfd->fb_mmap_type = MDP_FB_MMAP_PHYSICAL_ALLOC; |
| } |
| } |
| if (rc < 0) |
| pr_err("fb mmap failed with rc = %d\n", rc); |
| |
| return rc; |
| } |
| |
| 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_v2 = mdss_fb_ioctl, /* perform fb specific ioctl */ |
| #ifdef CONFIG_COMPAT |
| .fb_compat_ioctl_v2 = mdss_fb_compat_ioctl, |
| #endif |
| .fb_mmap = mdss_fb_mmap, |
| }; |
| |
| static int mdss_fb_alloc_fbmem_iommu(struct msm_fb_data_type *mfd, int dom) |
| { |
| void *virt = NULL; |
| phys_addr_t phys = 0; |
| size_t size = 0; |
| struct platform_device *pdev = mfd->pdev; |
| int rc = 0; |
| struct device_node *fbmem_pnode = NULL; |
| |
| if (!pdev || !pdev->dev.of_node) { |
| pr_err("Invalid device node\n"); |
| return -ENODEV; |
| } |
| |
| fbmem_pnode = of_parse_phandle(pdev->dev.of_node, |
| "linux,contiguous-region", 0); |
| if (!fbmem_pnode) { |
| pr_debug("fbmem is not reserved for %s\n", pdev->name); |
| mfd->fbi->screen_base = NULL; |
| mfd->fbi->fix.smem_start = 0; |
| return 0; |
| } |
| { |
| const u32 *addr; |
| u64 len; |
| |
| addr = of_get_address(fbmem_pnode, 0, &len, NULL); |
| if (!addr) { |
| pr_err("fbmem size is not specified\n"); |
| of_node_put(fbmem_pnode); |
| return -EINVAL; |
| } |
| size = (size_t)len; |
| of_node_put(fbmem_pnode); |
| } |
| |
| pr_debug("%s frame buffer reserve_size=0x%zx\n", __func__, size); |
| |
| if (size < PAGE_ALIGN(mfd->fbi->fix.line_length * |
| mfd->fbi->var.yres_virtual)) |
| pr_warn("reserve size is smaller than framebuffer size\n"); |
| |
| rc = mdss_smmu_dma_alloc_coherent(&pdev->dev, size, &phys, &mfd->iova, |
| &virt, GFP_KERNEL, dom); |
| if (rc) { |
| pr_err("unable to alloc fbmem size=%zx\n", size); |
| return -ENOMEM; |
| } |
| |
| if (MDSS_LPAE_CHECK(phys)) { |
| pr_warn("fb mem phys %pa > 4GB is not supported.\n", &phys); |
| mdss_smmu_dma_free_coherent(&pdev->dev, size, &virt, |
| phys, mfd->iova, dom); |
| return -ERANGE; |
| } |
| |
| pr_debug("alloc 0x%zxB @ (%pa phys) (0x%pK virt) (%pa iova) for fb%d\n", |
| size, &phys, virt, &mfd->iova, mfd->index); |
| |
| mfd->fbi->screen_base = virt; |
| mfd->fbi->fix.smem_start = phys; |
| mfd->fbi->fix.smem_len = size; |
| |
| return 0; |
| } |
| |
| 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 if (mfd->mdp.fb_mem_get_iommu_domain) { |
| int dom = mfd->mdp.fb_mem_get_iommu_domain(); |
| |
| if (dom >= 0) |
| return mdss_fb_alloc_fbmem_iommu(mfd, dom); |
| else |
| return -ENOMEM; |
| } 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; |
| char panel_name[20]; |
| 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; |
| |
| 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 unknown image type!\n", |
| mfd->index); |
| return ret; |
| } |
| |
| mdss_panelinfo_to_fb_var(panel_info, var); |
| |
| 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->xres_virtual = var->xres; |
| var->yres_virtual = panel_info->yres * mfd->fb_page; |
| var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */ |
| |
| /* |
| * 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; |
| fbi->pseudo_palette = mdss_fb_pseudo_palette; |
| |
| mfd->ref_cnt = 0; |
| mfd->panel_power_state = MDSS_PANEL_POWER_OFF; |
| mfd->dcm_state = DCM_UNINIT; |
| |
| if (mdss_fb_alloc_fbmem(mfd)) |
| pr_warn("unable to allocate fb memory in fb register\n"); |
| |
| mfd->op_enable = true; |
| |
| mutex_init(&mfd->update.lock); |
| mutex_init(&mfd->no_update.lock); |
| mutex_init(&mfd->mdp_sync_pt_data.sync_mutex); |
| atomic_set(&mfd->mdp_sync_pt_data.commit_cnt, 0); |
| atomic_set(&mfd->commits_pending, 0); |
| atomic_set(&mfd->ioctl_ref_cnt, 0); |
| atomic_set(&mfd->kickoff_pending, 0); |
| |
| init_timer(&mfd->no_update.timer); |
| mfd->no_update.timer.function = mdss_fb_no_update_notify_timer_cb; |
| mfd->no_update.timer.data = (unsigned long)mfd; |
| mfd->update.ref_count = 0; |
| mfd->no_update.ref_count = 0; |
| mfd->update.init_done = false; |
| init_completion(&mfd->update.comp); |
| init_completion(&mfd->no_update.comp); |
| init_completion(&mfd->power_off_comp); |
| init_completion(&mfd->power_set_comp); |
| init_waitqueue_head(&mfd->commit_wait_q); |
| init_waitqueue_head(&mfd->idle_wait_q); |
| init_waitqueue_head(&mfd->ioctl_q); |
| init_waitqueue_head(&mfd->kickoff_wait_q); |
| |
| 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; |
| } |
| |
| snprintf(panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d", |
| mfd->index); |
| mdss_panel_debugfs_init(panel_info, panel_name); |
| 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; |
| struct mdss_fb_file_info *file_info = NULL; |
| int result; |
| struct task_struct *task = current->group_leader; |
| |
| if (mfd->shutdown_pending) { |
| pr_err_once("Shutdown pending. Aborting operation. Request from pid:%d name=%s\n", |
| current->tgid, task->comm); |
| sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event"); |
| return -ESHUTDOWN; |
| } |
| |
| file_info = kmalloc(sizeof(*file_info), GFP_KERNEL); |
| if (!file_info) |
| return -ENOMEM; |
| |
| file_info->file = info->file; |
| list_add(&file_info->list, &mfd->file_list); |
| |
| 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++; |
| pr_debug("mfd refcount:%d file:%pK\n", mfd->ref_cnt, info->file); |
| |
| return 0; |
| |
| blank_error: |
| pm_runtime_put(info->dev); |
| pm_error: |
| list_del(&file_info->list); |
| kfree(file_info); |
| 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; |
| struct mdss_fb_file_info *file_info = NULL, *temp_file_info = NULL; |
| struct file *file = info->file; |
| int ret = 0; |
| bool node_found = false; |
| struct task_struct *task = current->group_leader; |
| |
| if (!mfd->ref_cnt) { |
| pr_info("try to close unopened fb %d! from pid:%d name:%s\n", |
| mfd->index, current->tgid, task->comm); |
| return -EINVAL; |
| } |
| |
| if (!wait_event_timeout(mfd->ioctl_q, |
| !atomic_read(&mfd->ioctl_ref_cnt) || !release_all, |
| msecs_to_jiffies(1000))) |
| pr_warn("fb%d ioctl could not finish. waited 1 sec.\n", |
| mfd->index); |
| |
| /* wait only for the last release */ |
| if (release_all || (mfd->ref_cnt == 1)) { |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret && (ret != -ESHUTDOWN)) |
| pr_warn("mdss_fb_pan_idle for fb%d failed. ret=%d ignoring.\n", |
| mfd->index, ret); |
| } |
| |
| pr_debug("release_all = %s\n", release_all ? "true" : "false"); |
| |
| list_for_each_entry_safe(file_info, temp_file_info, &mfd->file_list, |
| list) { |
| if (!release_all && file_info->file != file) |
| continue; |
| |
| pr_debug("found file node mfd->ref=%d\n", mfd->ref_cnt); |
| list_del(&file_info->list); |
| kfree(file_info); |
| |
| mfd->ref_cnt--; |
| pm_runtime_put(info->dev); |
| |
| node_found = true; |
| |
| if (!release_all) |
| break; |
| } |
| |
| if (!node_found || (release_all && mfd->ref_cnt)) |
| pr_warn("file node not found or wrong ref cnt: release all:%d refcnt:%d\n", |
| release_all, mfd->ref_cnt); |
| |
| pr_debug("current process=%s pid=%d mfd->ref=%d file:%pK\n", |
| task->comm, current->tgid, mfd->ref_cnt, info->file); |
| |
| if (!mfd->ref_cnt || release_all) { |
| /* resources (if any) will be released during blank */ |
| if (mfd->mdp.release_fnc) |
| mfd->mdp.release_fnc(mfd, NULL); |
| |
| if (mfd->mdp.pp_release_fnc) { |
| ret = (*mfd->mdp.pp_release_fnc)(mfd); |
| if (ret) |
| pr_err("PP release failed ret %d\n", ret); |
| } |
| |
| /* reset backlight before blank to prevent backlight from |
| * enabling ahead of unblank. for some special cases like |
| * adb shell stop/start. |
| */ |
| mdss_fb_set_backlight(mfd, 0); |
| |
| ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, info, |
| mfd->op_enable); |
| if (ret) { |
| pr_err("can't turn off fb%d! rc=%d current process=%s pid=%d\n", |
| mfd->index, ret, task->comm, current->tgid); |
| return ret; |
| } |
| if (mfd->fb_ion_handle) |
| mdss_fb_free_fb_ion_memory(mfd); |
| |
| atomic_set(&mfd->ioctl_ref_cnt, 0); |
| } else { |
| if (mfd->mdp.release_fnc) |
| ret = mfd->mdp.release_fnc(mfd, file); |
| |
| /* 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 void mdss_fb_power_setting_idle(struct msm_fb_data_type *mfd) |
| { |
| int ret; |
| |
| if (mfd->is_power_setting) { |
| ret = wait_for_completion_timeout( |
| &mfd->power_set_comp, |
| msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT)); |
| if (ret < 0) |
| ret = -ERESTARTSYS; |
| else if (!ret) |
| pr_err("%s wait for power_set_comp timeout %d %d", |
| __func__, ret, mfd->is_power_setting); |
| if (ret <= 0) { |
| mfd->is_power_setting = false; |
| complete_all(&mfd->power_set_comp); |
| } |
| } |
| } |
| |
| static void __mdss_fb_copy_fence(struct msm_sync_pt_data *sync_pt_data, |
| struct sync_fence **fences, u32 *fence_cnt) |
| { |
| pr_debug("%s: wait for fences\n", sync_pt_data->fence_name); |
| |
| mutex_lock(&sync_pt_data->sync_mutex); |
| /* |
| * Assuming that acq_fen_cnt is sanitized in bufsync ioctl |
| * to check for sync_pt_data->acq_fen_cnt <= MDP_MAX_FENCE_FD |
| */ |
| *fence_cnt = sync_pt_data->acq_fen_cnt; |
| sync_pt_data->acq_fen_cnt = 0; |
| if (*fence_cnt) |
| memcpy(fences, sync_pt_data->acq_fen, |
| *fence_cnt * sizeof(struct sync_fence *)); |
| mutex_unlock(&sync_pt_data->sync_mutex); |
| } |
| |
| static int __mdss_fb_wait_for_fence_sub(struct msm_sync_pt_data *sync_pt_data, |
| struct sync_fence **fences, int fence_cnt) |
| { |
| int i, ret = 0; |
| unsigned long max_wait = msecs_to_jiffies(WAIT_MAX_FENCE_TIMEOUT); |
| unsigned long timeout = jiffies + max_wait; |
| long wait_ms, wait_jf; |
| |
| /* buf sync */ |
| for (i = 0; i < fence_cnt && !ret; i++) { |
| wait_jf = timeout - jiffies; |
| wait_ms = jiffies_to_msecs(wait_jf); |
| |
| /* |
| * In this loop, if one of the previous fence took long |
| * time, give a chance for the next fence to check if |
| * fence is already signalled. If not signalled it breaks |
| * in the final wait timeout. |
| */ |
| if (wait_jf < 0) |
| wait_ms = WAIT_MIN_FENCE_TIMEOUT; |
| else |
| wait_ms = min_t(long, WAIT_FENCE_FIRST_TIMEOUT, |
| wait_ms); |
| |
| ret = sync_fence_wait(fences[i], wait_ms); |
| |
| if (ret == -ETIME) { |
| wait_jf = timeout - jiffies; |
| wait_ms = jiffies_to_msecs(wait_jf); |
| if (wait_jf < 0) |
| break; |
| |
| wait_ms = min_t(long, WAIT_FENCE_FINAL_TIMEOUT, |
| wait_ms); |
| |
| pr_warn("%s: sync_fence_wait timed out! ", |
| fences[i]->name); |
| pr_cont("Waiting %ld.%ld more seconds\n", |
| (wait_ms/MSEC_PER_SEC), (wait_ms%MSEC_PER_SEC)); |
| MDSS_XLOG(sync_pt_data->timeline_value); |
| MDSS_XLOG_TOUT_HANDLER("mdp"); |
| ret = sync_fence_wait(fences[i], wait_ms); |
| |
| if (ret == -ETIME) |
| break; |
| } |
| sync_fence_put(fences[i]); |
| } |
| |
| if (ret < 0) { |
| pr_err("%s: sync_fence_wait failed! ret = %x\n", |
| sync_pt_data->fence_name, ret); |
| for (; i < fence_cnt; i++) |
| sync_fence_put(fences[i]); |
| } |
| return ret; |
| } |
| |
| int mdss_fb_wait_for_fence(struct msm_sync_pt_data *sync_pt_data) |
| { |
| struct sync_fence *fences[MDP_MAX_FENCE_FD]; |
| int fence_cnt = 0; |
| |
| __mdss_fb_copy_fence(sync_pt_data, fences, &fence_cnt); |
| |
| if (fence_cnt) |
| __mdss_fb_wait_for_fence_sub(sync_pt_data, |
| fences, fence_cnt); |
| |
| return fence_cnt; |
| } |
| |
| /** |
| * mdss_fb_signal_timeline() - signal a single release fence |
| * @sync_pt_data: Sync point data structure for the timeline which |
| * should be signaled. |
| * |
| * This is called after a frame has been pushed to display. This signals the |
| * timeline to release the fences associated with this frame. |
| */ |
| void mdss_fb_signal_timeline(struct msm_sync_pt_data *sync_pt_data) |
| { |
| mutex_lock(&sync_pt_data->sync_mutex); |
| if (atomic_add_unless(&sync_pt_data->commit_cnt, -1, 0) && |
| sync_pt_data->timeline) { |
| sw_sync_timeline_inc(sync_pt_data->timeline, 1); |
| MDSS_XLOG(sync_pt_data->timeline_value); |
| sync_pt_data->timeline_value++; |
| |
| pr_debug("%s: buffer signaled! timeline val=%d remaining=%d\n", |
| sync_pt_data->fence_name, sync_pt_data->timeline_value, |
| atomic_read(&sync_pt_data->commit_cnt)); |
| } else { |
| pr_debug("%s timeline signaled without commits val=%d\n", |
| sync_pt_data->fence_name, sync_pt_data->timeline_value); |
| } |
| mutex_unlock(&sync_pt_data->sync_mutex); |
| } |
| |
| /** |
| * mdss_fb_release_fences() - signal all pending release fences |
| * @mfd: Framebuffer data structure for display |
| * |
| * Release all currently pending release fences, including those that are in |
| * the process to be commtted. |
| * |
| * Note: this should only be called during close or suspend sequence. |
| */ |
| static void mdss_fb_release_fences(struct msm_fb_data_type *mfd) |
| { |
| struct msm_sync_pt_data *sync_pt_data = &mfd->mdp_sync_pt_data; |
| int val; |
| |
| mutex_lock(&sync_pt_data->sync_mutex); |
| if (sync_pt_data->timeline) { |
| val = sync_pt_data->threshold + |
| atomic_read(&sync_pt_data->commit_cnt); |
| sw_sync_timeline_inc(sync_pt_data->timeline, val); |
| sync_pt_data->timeline_value += val; |
| atomic_set(&sync_pt_data->commit_cnt, 0); |
| } |
| mutex_unlock(&sync_pt_data->sync_mutex); |
| } |
| |
| static void mdss_fb_release_kickoff(struct msm_fb_data_type *mfd) |
| { |
| if (mfd->wait_for_kickoff) { |
| atomic_set(&mfd->kickoff_pending, 0); |
| wake_up_all(&mfd->kickoff_wait_q); |
| } |
| } |
| |
| /** |
| * __mdss_fb_sync_buf_done_callback() - process async display events |
| * @p: Notifier block registered for async events. |
| * @event: Event enum to identify the event. |
| * @data: Optional argument provided with the event. |
| * |
| * See enum mdp_notify_event for events handled. |
| */ |
| static int __mdss_fb_sync_buf_done_callback(struct notifier_block *p, |
| unsigned long event, void *data) |
| { |
| struct msm_sync_pt_data *sync_pt_data; |
| struct msm_fb_data_type *mfd; |
| int fence_cnt; |
| int ret = NOTIFY_OK; |
| |
| sync_pt_data = container_of(p, struct msm_sync_pt_data, notifier); |
| mfd = container_of(sync_pt_data, struct msm_fb_data_type, |
| mdp_sync_pt_data); |
| |
| switch (event) { |
| case MDP_NOTIFY_FRAME_BEGIN: |
| if (mfd->idle_time && !mod_delayed_work(system_wq, |
| &mfd->idle_notify_work, |
| msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT))) |
| pr_debug("fb%d: start idle delayed work\n", |
| mfd->index); |
| |
| mfd->idle_state = MDSS_FB_NOT_IDLE; |
| break; |
| case MDP_NOTIFY_FRAME_READY: |
| if (sync_pt_data->async_wait_fences && |
| sync_pt_data->temp_fen_cnt) { |
| fence_cnt = sync_pt_data->temp_fen_cnt; |
| sync_pt_data->temp_fen_cnt = 0; |
| ret = __mdss_fb_wait_for_fence_sub(sync_pt_data, |
| sync_pt_data->temp_fen, fence_cnt); |
| } |
| if (mfd->idle_time && !mod_delayed_work(system_wq, |
| &mfd->idle_notify_work, |
| msecs_to_jiffies(mfd->idle_time))) |
| pr_debug("fb%d: restarted idle work\n", |
| mfd->index); |
| if (ret == -ETIME) |
| ret = NOTIFY_BAD; |
| mfd->idle_state = MDSS_FB_IDLE_TIMER_RUNNING; |
| break; |
| case MDP_NOTIFY_FRAME_FLUSHED: |
| pr_debug("%s: frame flushed\n", sync_pt_data->fence_name); |
| sync_pt_data->flushed = true; |
| break; |
| case MDP_NOTIFY_FRAME_TIMEOUT: |
| pr_err("%s: frame timeout\n", sync_pt_data->fence_name); |
| mdss_fb_signal_timeline(sync_pt_data); |
| break; |
| case MDP_NOTIFY_FRAME_DONE: |
| pr_debug("%s: frame done\n", sync_pt_data->fence_name); |
| mdss_fb_signal_timeline(sync_pt_data); |
| mdss_fb_calc_fps(mfd); |
| break; |
| case MDP_NOTIFY_FRAME_CFG_DONE: |
| if (sync_pt_data->async_wait_fences) |
| __mdss_fb_copy_fence(sync_pt_data, |
| sync_pt_data->temp_fen, |
| &sync_pt_data->temp_fen_cnt); |
| break; |
| case MDP_NOTIFY_FRAME_CTX_DONE: |
| mdss_fb_release_kickoff(mfd); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * mdss_fb_pan_idle() - wait for panel programming to be idle |
| * @mfd: Framebuffer data structure for display |
| * |
| * Wait for any pending programming to be done if in the process of programming |
| * hardware configuration. After this function returns it is safe to perform |
| * software updates for next frame. |
| */ |
| static int mdss_fb_pan_idle(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| ret = wait_event_timeout(mfd->idle_wait_q, |
| (!atomic_read(&mfd->commits_pending) || |
| mfd->shutdown_pending), |
| msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT)); |
| if (!ret) { |
| pr_err("%pS: wait for idle timeout commits=%d\n", |
| __builtin_return_address(0), |
| atomic_read(&mfd->commits_pending)); |
| MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt", |
| "dbg_bus", "vbif_dbg_bus"); |
| ret = -ETIMEDOUT; |
| } else if (mfd->shutdown_pending) { |
| pr_debug("Shutdown signalled\n"); |
| ret = -ESHUTDOWN; |
| } else { |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static int mdss_fb_wait_for_kickoff(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| if (!mfd->wait_for_kickoff) |
| return mdss_fb_pan_idle(mfd); |
| |
| ret = wait_event_timeout(mfd->kickoff_wait_q, |
| (!atomic_read(&mfd->kickoff_pending) || |
| mfd->shutdown_pending), |
| msecs_to_jiffies(WAIT_DISP_OP_TIMEOUT)); |
| if (!ret) { |
| pr_err("%pS: wait for kickoff timeout koff=%d commits=%d\n", |
| __builtin_return_address(0), |
| atomic_read(&mfd->kickoff_pending), |
| atomic_read(&mfd->commits_pending)); |
| MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt", |
| "dbg_bus", "vbif_dbg_bus"); |
| ret = -ETIMEDOUT; |
| } else if (mfd->shutdown_pending) { |
| pr_debug("Shutdown signalled\n"); |
| ret = -ESHUTDOWN; |
| } else { |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static int mdss_fb_pan_display_ex(struct fb_info *info, |
| struct mdp_display_commit *disp_commit) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct fb_var_screeninfo *var = &disp_commit->var; |
| u32 wait_for_finish = disp_commit->wait_for_finish; |
| int ret = 0; |
| |
| if (!mfd || (!mfd->op_enable)) |
| return -EPERM; |
| |
| if ((mdss_fb_is_power_off(mfd)) && |
| !((mfd->dcm_state == DCM_ENTER) && |
| (mfd->panel.type == MIPI_CMD_PANEL))) |
| 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; |
| |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_err("wait_for_kick failed. rc=%d\n", ret); |
| return ret; |
| } |
| |
| if (mfd->mdp.pre_commit_fnc) { |
| ret = mfd->mdp.pre_commit_fnc(mfd); |
| if (ret) { |
| pr_err("fb%d: pre commit failed %d\n", |
| mfd->index, ret); |
| return ret; |
| } |
| } |
| |
| mutex_lock(&mfd->mdp_sync_pt_data.sync_mutex); |
| 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; |
| |
| mfd->msm_fb_backup.info = *info; |
| mfd->msm_fb_backup.disp_commit = *disp_commit; |
| |
| atomic_inc(&mfd->mdp_sync_pt_data.commit_cnt); |
| atomic_inc(&mfd->commits_pending); |
| atomic_inc(&mfd->kickoff_pending); |
| wake_up_all(&mfd->commit_wait_q); |
| mutex_unlock(&mfd->mdp_sync_pt_data.sync_mutex); |
| if (wait_for_finish) { |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) |
| pr_err("mdss_fb_pan_idle failed. rc=%d\n", ret); |
| } |
| return ret; |
| } |
| |
| u32 mdss_fb_get_mode_switch(struct msm_fb_data_type *mfd) |
| { |
| /* If there is no attached mfd then there is no pending mode switch */ |
| if (!mfd) |
| return 0; |
| |
| if (mfd->pending_switch) |
| return mfd->switch_new_mode; |
| |
| return 0; |
| } |
| |
| /* |
| * __ioctl_transition_dyn_mode_state() - State machine for mode switch |
| * @mfd: Framebuffer data structure for display |
| * @cmd: ioctl that was called |
| * @validate: used with atomic commit when doing validate layers |
| * |
| * This function assists with dynamic mode switch of DSI panel. States |
| * are used to make sure that panel mode switch occurs on next |
| * prepare/sync/commit (for legacy) and validate/pre_commit (for |
| * atomic commit) pairing. This state machine insure that calculation |
| * and return values (such as buffer release fences) are based on the |
| * panel mode being switching into. |
| */ |
| static int __ioctl_transition_dyn_mode_state(struct msm_fb_data_type *mfd, |
| unsigned int cmd, bool validate, bool null_commit) |
| { |
| if (mfd->switch_state == MDSS_MDP_NO_UPDATE_REQUESTED) |
| return 0; |
| |
| mutex_lock(&mfd->switch_lock); |
| switch (cmd) { |
| case MSMFB_ATOMIC_COMMIT: |
| if ((mfd->switch_state == MDSS_MDP_WAIT_FOR_VALIDATE) |
| && validate) { |
| if (mfd->switch_new_mode != SWITCH_RESOLUTION) |
| mfd->pending_switch = true; |
| mfd->switch_state = MDSS_MDP_WAIT_FOR_COMMIT; |
| } else if (mfd->switch_state == MDSS_MDP_WAIT_FOR_COMMIT) { |
| if (mfd->switch_new_mode != SWITCH_RESOLUTION) |
| mdss_fb_set_mdp_sync_pt_threshold(mfd, |
| mfd->switch_new_mode); |
| mfd->switch_state = MDSS_MDP_WAIT_FOR_KICKOFF; |
| } else if ((mfd->switch_state == MDSS_MDP_WAIT_FOR_VALIDATE) |
| && null_commit) { |
| mfd->switch_state = MDSS_MDP_WAIT_FOR_KICKOFF; |
| } |
| break; |
| } |
| mutex_unlock(&mfd->switch_lock); |
| return 0; |
| } |
| |
| static inline bool mdss_fb_is_wb_config_same(struct msm_fb_data_type *mfd, |
| struct mdp_output_layer *output_layer) |
| { |
| struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); |
| struct msm_mdp_interface *mdp5_interface = &mfd->mdp; |
| |
| if (!mdp5_data->wfd |
| || (mdp5_interface->is_config_same |
| && !mdp5_interface->is_config_same(mfd, output_layer))) |
| return false; |
| return true; |
| } |
| |
| /* update pinfo and var for WB on config change */ |
| static void mdss_fb_update_resolution(struct msm_fb_data_type *mfd, |
| u32 xres, u32 yres, u32 format) |
| { |
| struct mdss_panel_info *pinfo = mfd->panel_info; |
| struct fb_var_screeninfo *var = &mfd->fbi->var; |
| struct fb_fix_screeninfo *fix = &mfd->fbi->fix; |
| struct mdss_mdp_format_params *fmt = NULL; |
| |
| pinfo->xres = xres; |
| pinfo->yres = yres; |
| mfd->fb_imgType = format; |
| if (mfd->mdp.get_format_params) { |
| fmt = mfd->mdp.get_format_params(format); |
| if (fmt) { |
| pinfo->bpp = fmt->bpp; |
| var->bits_per_pixel = fmt->bpp * 8; |
| } |
| if (mfd->mdp.fb_stride) |
| fix->line_length = mfd->mdp.fb_stride(mfd->index, |
| var->xres, |
| var->bits_per_pixel / 8); |
| else |
| fix->line_length = var->xres * var->bits_per_pixel / 8; |
| |
| } |
| var->xres_virtual = var->xres; |
| var->yres_virtual = pinfo->yres * mfd->fb_page; |
| mdss_panelinfo_to_fb_var(pinfo, var); |
| } |
| |
| int mdss_fb_atomic_commit(struct fb_info *info, |
| struct mdp_layer_commit *commit, struct file *file) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct mdp_layer_commit_v1 *commit_v1; |
| struct mdp_output_layer *output_layer; |
| struct mdss_panel_info *pinfo; |
| bool wait_for_finish, wb_change = false; |
| int ret = -EPERM; |
| u32 old_xres, old_yres, old_format; |
| |
| if (!mfd || (!mfd->op_enable)) { |
| pr_err("mfd is NULL or operation not permitted\n"); |
| return -EPERM; |
| } |
| |
| if ((mdss_fb_is_power_off(mfd)) && |
| !((mfd->dcm_state == DCM_ENTER) && |
| (mfd->panel.type == MIPI_CMD_PANEL))) { |
| pr_err("commit is not supported when interface is in off state\n"); |
| goto end; |
| } |
| pinfo = mfd->panel_info; |
| |
| /* only supports version 1.0 */ |
| if (commit->version != MDP_COMMIT_VERSION_1_0) { |
| pr_err("commit version is not supported\n"); |
| goto end; |
| } |
| |
| if (!mfd->mdp.pre_commit || !mfd->mdp.atomic_validate) { |
| pr_err("commit callback is not registered\n"); |
| goto end; |
| } |
| |
| commit_v1 = &commit->commit_v1; |
| if (commit_v1->flags & MDP_VALIDATE_LAYER) { |
| ret = mdss_fb_wait_for_kickoff(mfd); |
| if (ret) { |
| pr_err("wait for kickoff failed\n"); |
| } else { |
| __ioctl_transition_dyn_mode_state(mfd, |
| MSMFB_ATOMIC_COMMIT, true, false); |
| if (mfd->panel.type == WRITEBACK_PANEL) { |
| output_layer = commit_v1->output_layer; |
| if (!output_layer) { |
| pr_err("Output layer is null\n"); |
| goto end; |
| } |
| wb_change = !mdss_fb_is_wb_config_same(mfd, |
| commit_v1->output_layer); |
| if (wb_change) { |
| old_xres = pinfo->xres; |
| old_yres = pinfo->yres; |
| old_format = mfd->fb_imgType; |
| mdss_fb_update_resolution(mfd, |
| output_layer->buffer.width, |
| output_layer->buffer.height, |
| output_layer->buffer.format); |
| } |
| } |
| ret = mfd->mdp.atomic_validate(mfd, file, commit_v1); |
| if (!ret) |
| mfd->atomic_commit_pending = true; |
| } |
| goto end; |
| } else { |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_err("pan display idle call failed\n"); |
| goto end; |
| } |
| __ioctl_transition_dyn_mode_state(mfd, |
| MSMFB_ATOMIC_COMMIT, false, |
| (commit_v1->input_layer_cnt ? 0 : 1)); |
| |
| ret = mfd->mdp.pre_commit(mfd, file, commit_v1); |
| if (ret) { |
| pr_err("atomic pre commit failed\n"); |
| goto end; |
| } |
| } |
| |
| wait_for_finish = commit_v1->flags & MDP_COMMIT_WAIT_FOR_FINISH; |
| mfd->msm_fb_backup.atomic_commit = true; |
| mfd->msm_fb_backup.disp_commit.l_roi = commit_v1->left_roi; |
| mfd->msm_fb_backup.disp_commit.r_roi = commit_v1->right_roi; |
| |
| mutex_lock(&mfd->mdp_sync_pt_data.sync_mutex); |
| atomic_inc(&mfd->mdp_sync_pt_data.commit_cnt); |
| atomic_inc(&mfd->commits_pending); |
| atomic_inc(&mfd->kickoff_pending); |
| wake_up_all(&mfd->commit_wait_q); |
| mutex_unlock(&mfd->mdp_sync_pt_data.sync_mutex); |
| |
| if (wait_for_finish) |
| ret = mdss_fb_pan_idle(mfd); |
| |
| end: |
| if (ret && (mfd->panel.type == WRITEBACK_PANEL) && wb_change) |
| mdss_fb_update_resolution(mfd, old_xres, old_yres, old_format); |
| return ret; |
| } |
| |
| static int mdss_fb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct mdp_display_commit disp_commit; |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| /* |
| * during mode switch through mode sysfs node, it will trigger a |
| * pan_display after switch. This assumes that fb has been adjusted, |
| * however when using overlays we may not have the right size at this |
| * point, so it needs to go through PREPARE first. Abort pan_display |
| * operations until that happens |
| */ |
| if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) { |
| pr_debug("fb%d: pan_display skipped during switch\n", |
| mfd->index); |
| return 0; |
| } |
| |
| memset(&disp_commit, 0, sizeof(disp_commit)); |
| disp_commit.wait_for_finish = true; |
| memcpy(&disp_commit.var, var, sizeof(struct fb_var_screeninfo)); |
| return mdss_fb_pan_display_ex(info, &disp_commit); |
| } |
| |
| 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 ((mdss_fb_is_power_off(mfd)) && |
| !((mfd->dcm_state == DCM_ENTER) && |
| (mfd->panel.type == MIPI_CMD_PANEL))) |
| 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_grayscale_to_mdp_format(u32 grayscale) |
| { |
| switch (grayscale) { |
| case V4L2_PIX_FMT_RGB24: |
| return MDP_RGB_888; |
| case V4L2_PIX_FMT_NV12: |
| return MDP_Y_CBCR_H2V2; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static void mdss_fb_var_to_panelinfo(struct fb_var_screeninfo *var, |
| struct mdss_panel_info *pinfo) |
| { |
| int format = -EINVAL; |
| |
| 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; |
| |
| if (var->grayscale > 1) { |
| format = mdss_grayscale_to_mdp_format(var->grayscale); |
| if (!IS_ERR_VALUE(format)) |
| pinfo->out_format = format; |
| else |
| pr_warn("Failed to map grayscale value (%d) to an MDP format\n", |
| var->grayscale); |
| } |
| |
| /* |
| * if greater than 1M, then rate would fall below 1mhz which is not |
| * even supported. In this case it means clock rate is actually |
| * passed directly in hz. |
| */ |
| if (var->pixclock > SZ_1M) |
| pinfo->clk_rate = var->pixclock; |
| else |
| pinfo->clk_rate = PICOS2KHZ(var->pixclock) * 1000; |
| |
| /* |
| * if it is a DBA panel i.e. HDMI TV connected through |
| * DSI interface, then store the pixel clock value in |
| * DSI specific variable. |
| */ |
| if (pinfo->is_dba_panel) |
| pinfo->mipi.dsi_pclk_rate = pinfo->clk_rate; |
| } |
| |
| void mdss_panelinfo_to_fb_var(struct mdss_panel_info *pinfo, |
| struct fb_var_screeninfo *var) |
| { |
| u32 frame_rate; |
| |
| var->xres = mdss_fb_get_panel_xres(pinfo); |
| var->yres = pinfo->yres; |
| var->lower_margin = pinfo->lcdc.v_front_porch - |
| pinfo->prg_fet; |
| var->upper_margin = pinfo->lcdc.v_back_porch + |
| pinfo->prg_fet; |
| var->vsync_len = pinfo->lcdc.v_pulse_width; |
| var->right_margin = pinfo->lcdc.h_front_porch; |
| var->left_margin = pinfo->lcdc.h_back_porch; |
| var->hsync_len = pinfo->lcdc.h_pulse_width; |
| |
| frame_rate = mdss_panel_get_framerate(pinfo, |
| FPS_RESOLUTION_HZ); |
| if (frame_rate) { |
| unsigned long clk_rate, h_total, v_total; |
| |
| h_total = var->xres + var->left_margin |
| + var->right_margin + var->hsync_len; |
| v_total = var->yres + var->lower_margin |
| + var->upper_margin + var->vsync_len; |
| clk_rate = h_total * v_total * frame_rate; |
| var->pixclock = KHZ2PICOS(clk_rate / 1000); |
| } else if (pinfo->clk_rate) { |
| var->pixclock = KHZ2PICOS( |
| (unsigned long int) pinfo->clk_rate / 1000); |
| } |
| |
| if (pinfo->physical_width) |
| var->width = pinfo->physical_width; |
| if (pinfo->physical_height) |
| var->height = pinfo->physical_height; |
| |
| pr_debug("ScreenInfo: res=%dx%d [%d, %d] [%d, %d]\n", |
| var->xres, var->yres, var->left_margin, |
| var->right_margin, var->upper_margin, |
| var->lower_margin); |
| } |
| |
| /** |
| * __mdss_fb_perform_commit() - process a frame to display |
| * @mfd: Framebuffer data structure for display |
| * |
| * Processes all layers and buffers programmed and ensures all pending release |
| * fences are signaled once the buffer is transferred to display. |
| */ |
| static int __mdss_fb_perform_commit(struct msm_fb_data_type *mfd) |
| { |
| struct msm_sync_pt_data *sync_pt_data = &mfd->mdp_sync_pt_data; |
| struct msm_fb_backup_type *fb_backup = &mfd->msm_fb_backup; |
| int ret = -ENOTSUP; |
| u32 new_dsi_mode, dynamic_dsi_switch = 0; |
| |
| if (!sync_pt_data->async_wait_fences) |
| mdss_fb_wait_for_fence(sync_pt_data); |
| sync_pt_data->flushed = false; |
| |
| mutex_lock(&mfd->switch_lock); |
| if (mfd->switch_state == MDSS_MDP_WAIT_FOR_KICKOFF) { |
| dynamic_dsi_switch = 1; |
| new_dsi_mode = mfd->switch_new_mode; |
| } else if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) { |
| pr_err("invalid commit on fb%d with state = %d\n", |
| mfd->index, mfd->switch_state); |
| mutex_unlock(&mfd->switch_lock); |
| goto skip_commit; |
| } |
| mutex_unlock(&mfd->switch_lock); |
| if (dynamic_dsi_switch) { |
| MDSS_XLOG(mfd->index, mfd->split_mode, new_dsi_mode, |
| XLOG_FUNC_ENTRY); |
| pr_debug("Triggering dyn mode switch to %d\n", new_dsi_mode); |
| ret = mfd->mdp.mode_switch(mfd, new_dsi_mode); |
| if (ret) |
| pr_err("DSI mode switch has failed"); |
| else |
| mfd->pending_switch = false; |
| } |
| if (fb_backup->disp_commit.flags & MDP_DISPLAY_COMMIT_OVERLAY) { |
| if (mfd->mdp.kickoff_fnc) |
| ret = mfd->mdp.kickoff_fnc(mfd, |
| &fb_backup->disp_commit); |
| else |
| pr_warn("no kickoff function setup for fb%d\n", |
| mfd->index); |
| } else if (fb_backup->atomic_commit) { |
| if (mfd->mdp.kickoff_fnc) |
| ret = mfd->mdp.kickoff_fnc(mfd, |
| &fb_backup->disp_commit); |
| else |
| pr_warn("no kickoff function setup for fb%d\n", |
| mfd->index); |
| fb_backup->atomic_commit = false; |
| } else { |
| ret = mdss_fb_pan_display_sub(&fb_backup->disp_commit.var, |
| &fb_backup->info); |
| if (ret) |
| pr_err("pan display failed %x on fb%d\n", ret, |
| mfd->index); |
| } |
| |
| skip_commit: |
| if (!ret) |
| mdss_fb_update_backlight(mfd); |
| |
| if (IS_ERR_VALUE(ret) || !sync_pt_data->flushed) { |
| mdss_fb_release_kickoff(mfd); |
| mdss_fb_signal_timeline(sync_pt_data); |
| if ((mfd->panel.type == MIPI_CMD_PANEL) && |
| (mfd->mdp.signal_retire_fence)) |
| mfd->mdp.signal_retire_fence(mfd, 1); |
| } |
| |
| if (dynamic_dsi_switch) { |
| MDSS_XLOG(mfd->index, mfd->split_mode, new_dsi_mode, |
| XLOG_FUNC_EXIT); |
| mfd->mdp.mode_switch_post(mfd, new_dsi_mode); |
| mutex_lock(&mfd->switch_lock); |
| mfd->switch_state = MDSS_MDP_NO_UPDATE_REQUESTED; |
| mutex_unlock(&mfd->switch_lock); |
| if (new_dsi_mode != SWITCH_RESOLUTION) |
| mfd->panel.type = new_dsi_mode; |
| pr_debug("Dynamic mode switch completed\n"); |
| } |
| |
| return ret; |
| } |
| |
| static int __mdss_fb_display_thread(void *data) |
| { |
| struct msm_fb_data_type *mfd = data; |
| int ret; |
| struct sched_param param; |
| |
| /* |
| * this priority was found during empiric testing to have appropriate |
| * realtime scheduling to process display updates and interact with |
| * other real time and normal priority tasks |
| */ |
| param.sched_priority = 16; |
| ret = sched_setscheduler(current, SCHED_FIFO, ¶m); |
| if (ret) |
| pr_warn("set priority failed for fb%d display thread\n", |
| mfd->index); |
| |
| while (1) { |
| wait_event(mfd->commit_wait_q, |
| (atomic_read(&mfd->commits_pending) || |
| kthread_should_stop())); |
| |
| if (kthread_should_stop()) |
| break; |
| |
| MDSS_XLOG(mfd->index, XLOG_FUNC_ENTRY); |
| ret = __mdss_fb_perform_commit(mfd); |
| MDSS_XLOG(mfd->index, XLOG_FUNC_EXIT); |
| |
| atomic_dec(&mfd->commits_pending); |
| wake_up_all(&mfd->idle_wait_q); |
| } |
| |
| mdss_fb_release_kickoff(mfd); |
| atomic_set(&mfd->commits_pending, 0); |
| wake_up_all(&mfd->idle_wait_q); |
| |
| return ret; |
| } |
| |
| 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 && var->rotate != FB_ROTATE_UD) |
| 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 == 0) && |
| (var->blue.offset == 24) && |
| (var->green.offset == 16) && |
| (var->red.offset == 8)) && |
| !((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 ((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 (info->mode) { |
| const struct fb_videomode *mode; |
| |
| mode = fb_match_mode(var, &info->modelist); |
| if (mode == NULL) |
| return -EINVAL; |
| } else if (mfd->panel_info && !(var->activate & FB_ACTIVATE_TEST)) { |
| struct mdss_panel_info *panel_info; |
| int rc; |
| |
| panel_info = kzalloc(sizeof(struct mdss_panel_info), |
| GFP_KERNEL); |
| if (!panel_info) |
| return -ENOMEM; |
| |
| memcpy(panel_info, mfd->panel_info, |
| sizeof(struct mdss_panel_info)); |
| mdss_fb_var_to_panelinfo(var, panel_info); |
| rc = mdss_fb_send_panel_event(mfd, MDSS_EVENT_CHECK_PARAMS, |
| panel_info); |
| if (IS_ERR_VALUE(rc)) { |
| kfree(panel_info); |
| return rc; |
| } |
| mfd->panel_reconfig = rc; |
| kfree(panel_info); |
| } |
| |
| return 0; |
| } |
| |
| static int mdss_fb_videomode_switch(struct msm_fb_data_type *mfd, |
| const struct fb_videomode *mode) |
| { |
| int ret = 0; |
| struct mdss_panel_data *pdata, *tmp; |
| struct mdss_panel_timing *timing; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("no panel connected\n"); |
| return -ENODEV; |
| } |
| |
| /* make sure that we are idle while switching */ |
| mdss_fb_wait_for_kickoff(mfd); |
| |
| pr_debug("fb%d: changing display mode to %s\n", mfd->index, mode->name); |
| MDSS_XLOG(mfd->index, mode->name, |
| mdss_fb_get_panel_xres(mfd->panel_info), |
| mfd->panel_info->yres, mfd->split_mode, |
| XLOG_FUNC_ENTRY); |
| tmp = pdata; |
| do { |
| if (!tmp->event_handler) { |
| pr_warn("no event handler for panel\n"); |
| continue; |
| } |
| timing = mdss_panel_get_timing_by_name(tmp, mode->name); |
| ret = tmp->event_handler(tmp, |
| MDSS_EVENT_PANEL_TIMING_SWITCH, timing); |
| |
| tmp->active = timing != NULL; |
| tmp = tmp->next; |
| } while (tmp && !ret); |
| |
| if (!ret) |
| mdss_fb_set_split_mode(mfd, pdata); |
| |
| if (!ret && mfd->mdp.configure_panel) { |
| int dest_ctrl = 1; |
| |
| /* todo: currently assumes no changes in video/cmd mode */ |
| if (!mdss_fb_is_power_off(mfd)) { |
| mutex_lock(&mfd->switch_lock); |
| mfd->switch_state = MDSS_MDP_WAIT_FOR_VALIDATE; |
| mfd->switch_new_mode = SWITCH_RESOLUTION; |
| mutex_unlock(&mfd->switch_lock); |
| dest_ctrl = 0; |
| } |
| ret = mfd->mdp.configure_panel(mfd, |
| pdata->panel_info.mipi.mode, dest_ctrl); |
| } |
| |
| MDSS_XLOG(mfd->index, mode->name, |
| mdss_fb_get_panel_xres(mfd->panel_info), |
| mfd->panel_info->yres, mfd->split_mode, |
| XLOG_FUNC_EXIT); |
| pr_debug("fb%d: %s mode change complete\n", mfd->index, mode->name); |
| |
| return ret; |
| } |
| |
| 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_format; |
| int ret = 0; |
| |
| ret = mdss_fb_pan_idle(mfd); |
| if (ret) { |
| pr_err("mdss_fb_pan_idle failed. rc=%d\n", ret); |
| return ret; |
| } |
| |
| 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 (info->mode) { |
| const struct fb_videomode *mode; |
| |
| mode = fb_match_mode(var, &info->modelist); |
| if (!mode) |
| return -EINVAL; |
| |
| pr_debug("found mode: %s\n", mode->name); |
| |
| if (fb_mode_is_equal(mode, info->mode)) { |
| pr_debug("mode is equal to current mode\n"); |
| return 0; |
| } |
| |
| ret = mdss_fb_videomode_switch(mfd, mode); |
| if (ret) |
| return ret; |
| } |
| |
| 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; |
| |
| /* if memory is not allocated yet, change memory size for fb */ |
| if (!info->fix.smem_start) |
| mfd->fbi->fix.smem_len = PAGE_ALIGN(mfd->fbi->fix.line_length * |
| mfd->fbi->var.yres) * mfd->fb_page; |
| |
| old_format = mdss_grayscale_to_mdp_format(var->grayscale); |
| if (!IS_ERR_VALUE(old_format)) { |
| if (old_format != mfd->panel_info->out_format) |
| mfd->panel_reconfig = true; |
| } |
| |
| 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); |
| if (mfd->panel_info->is_dba_panel && |
| mdss_fb_send_panel_event(mfd, MDSS_EVENT_UPDATE_PARAMS, |
| mfd->panel_info)) |
| pr_debug("Failed to send panel event UPDATE_PARAMS\n"); |
| mdss_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable); |
| mfd->panel_reconfig = false; |
| } |
| |
| return ret; |
| } |
| |
| int mdss_fb_dcm(struct msm_fb_data_type *mfd, int req_state) |
| { |
| int ret = 0; |
| |
| if (req_state == mfd->dcm_state) { |
| pr_warn("Already in correct DCM/DTM state\n"); |
| return ret; |
| } |
| |
| switch (req_state) { |
| case DCM_UNBLANK: |
| if (mfd->dcm_state == DCM_UNINIT && |
| mdss_fb_is_power_off(mfd) && mfd->mdp.on_fnc) { |
| if (mfd->disp_thread == NULL) { |
| ret = mdss_fb_start_disp_thread(mfd); |
| if (ret < 0) |
| return ret; |
| } |
| ret = mfd->mdp.on_fnc(mfd); |
| if (ret == 0) { |
| mfd->panel_power_state = MDSS_PANEL_POWER_ON; |
| mfd->dcm_state = DCM_UNBLANK; |
| } |
| } |
| break; |
| case DCM_ENTER: |
| if (mfd->dcm_state == DCM_UNBLANK) { |
| /* |
| * Keep unblank path available for only |
| * DCM operation |
| */ |
| mfd->panel_power_state = MDSS_PANEL_POWER_OFF; |
| mfd->dcm_state = DCM_ENTER; |
| } |
| break; |
| case DCM_EXIT: |
| if (mfd->dcm_state == DCM_ENTER) { |
| /* Release the unblank path for exit */ |
| mfd->panel_power_state = MDSS_PANEL_POWER_ON; |
| mfd->dcm_state = DCM_EXIT; |
| } |
| break; |
| case DCM_BLANK: |
| if ((mfd->dcm_state == DCM_EXIT || |
| mfd->dcm_state == DCM_UNBLANK) && |
| mdss_fb_is_power_on(mfd) && mfd->mdp.off_fnc) { |
| mfd->panel_power_state = MDSS_PANEL_POWER_OFF; |
| ret = mfd->mdp.off_fnc(mfd); |
| if (ret == 0) |
| mfd->dcm_state = DCM_UNINIT; |
| else |
| pr_err("DCM_BLANK failed\n"); |
| |
| if (mfd->disp_thread) |
| mdss_fb_stop_disp_thread(mfd); |
| } |
| break; |
| case DTM_ENTER: |
| if (mfd->dcm_state == DCM_UNINIT) |
| mfd->dcm_state = DTM_ENTER; |
| break; |
| case DTM_EXIT: |
| if (mfd->dcm_state == DTM_ENTER) |
| mfd->dcm_state = DCM_UNINIT; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| 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->mdp.cursor_update) |
| return -ENODEV; |
| |
| ret = copy_from_user(&cursor, p, sizeof(cursor)); |
| if (ret) |
| return ret; |
| |
| return mfd->mdp.cursor_update(mfd, &cursor); |
| } |
| |
| int mdss_fb_async_position_update(struct fb_info *info, |
| struct mdp_position_update *update_pos) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if (!update_pos->input_layer_cnt) { |
| pr_err("no input layers for position update\n"); |
| return -EINVAL; |
| } |
| return mfd->mdp.async_position_update(mfd, update_pos); |
| } |
| |
| static int mdss_fb_async_position_update_ioctl(struct fb_info *info, |
| unsigned long *argp) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct mdp_position_update update_pos; |
| int ret, rc; |
| u32 buffer_size, layer_cnt; |
| struct mdp_async_layer *layer_list = NULL; |
| struct mdp_async_layer __user *input_layer_list; |
| |
| if (!mfd->mdp.async_position_update) |
| return -ENODEV; |
| |
| ret = copy_from_user(&update_pos, argp, sizeof(update_pos)); |
| if (ret) { |
| pr_err("copy from user failed\n"); |
| return ret; |
| } |
| input_layer_list = update_pos.input_layers; |
| |
| layer_cnt = update_pos.input_layer_cnt; |
| if ((!layer_cnt) || (layer_cnt > MAX_LAYER_COUNT)) { |
| pr_err("invalid async layers :%d to update\n", layer_cnt); |
| return -EINVAL; |
| } |
| |
| buffer_size = sizeof(struct mdp_async_layer) * layer_cnt; |
| layer_list = kmalloc(buffer_size, GFP_KERNEL); |
| if (!layer_list) { |
| pr_err("unable to allocate memory for layers\n"); |
| return -ENOMEM; |
| } |
| |
| ret = copy_from_user(layer_list, input_layer_list, buffer_size); |
| if (ret) { |
| pr_err("layer list copy from user failed\n"); |
| goto end; |
| } |
| update_pos.input_layers = layer_list; |
| |
| ret = mdss_fb_async_position_update(info, &update_pos); |
| if (ret) |
| pr_err("async position update failed ret:%d\n", ret); |
| |
| rc = copy_to_user(input_layer_list, layer_list, buffer_size); |
| if (rc) |
| pr_err("layer error code copy to user failed\n"); |
| |
| update_pos.input_layers = input_layer_list; |
| rc = copy_to_user(argp, &update_pos, |
| sizeof(struct mdp_position_update)); |
| if (rc) |
| pr_err("copy to user for layers failed"); |
| |
| end: |
| kfree(layer_list); |
| return ret; |
| } |
| |
| 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->mdp.lut_update) |
| return -ENODEV; |
| |
| ret = copy_from_user(&cmap, p, sizeof(cmap)); |
| if (ret) |
| return ret; |
| |
| mfd->mdp.lut_update(mfd, &cmap); |
| return 0; |
| } |
| |
| /** |
| * mdss_fb_sync_get_fence() - get fence from timeline |
| * @timeline: Timeline to create the fence on |
| * @fence_name: Name of the fence that will be created for debugging |
| * @val: Timeline value at which the fence will be signaled |
| * |
| * Function returns a fence on the timeline given with the name provided. |
| * The fence created will be signaled when the timeline is advanced. |
| */ |
| struct sync_fence *mdss_fb_sync_get_fence(struct sw_sync_timeline *timeline, |
| const char *fence_name, int val) |
| { |
| struct sync_pt *sync_pt; |
| struct sync_fence *fence; |
| |
| pr_debug("%s: buf sync fence timeline=%d\n", fence_name, val); |
| |
| sync_pt = sw_sync_pt_create(timeline, val); |
| if (sync_pt == NULL) { |
| pr_err("%s: cannot create sync point\n", fence_name); |
| return NULL; |
| } |
| |
| /* create fence */ |
| fence = sync_fence_create(fence_name, sync_pt); |
| if (fence == NULL) { |
| sync_pt_free(sync_pt); |
| pr_err("%s: cannot create fence\n", fence_name); |
| return NULL; |
| } |
| |
| return fence; |
| } |
| |
| static int mdss_fb_handle_buf_sync_ioctl(struct msm_sync_pt_data *sync_pt_data, |
| struct mdp_buf_sync *buf_sync) |
| { |
| int i, ret = 0; |
| int acq_fen_fd[MDP_MAX_FENCE_FD]; |
| struct sync_fence *fence, *rel_fence, *retire_fence; |
| int rel_fen_fd; |
| int retire_fen_fd; |
| int val; |
| |
| if ((buf_sync->acq_fen_fd_cnt > MDP_MAX_FENCE_FD) || |
| (sync_pt_data->timeline == NULL)) |
| return -EINVAL; |
| |
| if (buf_sync->acq_fen_fd_cnt) |
| ret = copy_from_user(acq_fen_fd, buf_sync->acq_fen_fd, |
| buf_sync->acq_fen_fd_cnt * sizeof(int)); |
| if (ret) { |
| pr_err("%s: copy_from_user failed\n", sync_pt_data->fence_name); |
| return ret; |
| } |
| |
| i = mdss_fb_wait_for_fence(sync_pt_data); |
| if (i > 0) |
| pr_warn("%s: waited on %d active fences\n", |
| sync_pt_data->fence_name, i); |
| |
| mutex_lock(&sync_pt_data->sync_mutex); |
| for (i = 0; i < buf_sync->acq_fen_fd_cnt; i++) { |
| fence = sync_fence_fdget(acq_fen_fd[i]); |
| if (fence == NULL) { |
| pr_err("%s: null fence! i=%d fd=%d\n", |
| sync_pt_data->fence_name, i, |
| acq_fen_fd[i]); |
| ret = -EINVAL; |
| break; |
| } |
| sync_pt_data->acq_fen[i] = fence; |
| } |
| sync_pt_data->acq_fen_cnt = i; |
| if (ret) |
| goto buf_sync_err_1; |
| |
| val = sync_pt_data->timeline_value + sync_pt_data->threshold + |
| atomic_read(&sync_pt_data->commit_cnt); |
| |
| MDSS_XLOG(sync_pt_data->timeline_value, val, |
| atomic_read(&sync_pt_data->commit_cnt)); |
| pr_debug("%s: fence CTL%d Commit_cnt%d\n", sync_pt_data->fence_name, |
| sync_pt_data->timeline_value, |
| atomic_read(&sync_pt_data->commit_cnt)); |
| /* Set release fence */ |
| rel_fence = mdss_fb_sync_get_fence(sync_pt_data->timeline, |
| sync_pt_data->fence_name, val); |
| if (IS_ERR_OR_NULL(rel_fence)) { |
| pr_err("%s: unable to retrieve release fence\n", |
| sync_pt_data->fence_name); |
| ret = rel_fence ? PTR_ERR(rel_fence) : -ENOMEM; |
| goto buf_sync_err_1; |
| } |
| |
| /* create fd */ |
| rel_fen_fd = get_unused_fd_flags(0); |
| if (rel_fen_fd < 0) { |
| pr_err("%s: get_unused_fd_flags failed error:0x%x\n", |
| sync_pt_data->fence_name, rel_fen_fd); |
| ret = rel_fen_fd; |
| goto buf_sync_err_2; |
| } |
| |
| ret = copy_to_user(buf_sync->rel_fen_fd, &rel_fen_fd, sizeof(int)); |
| if (ret) { |
| pr_err("%s: copy_to_user failed\n", sync_pt_data->fence_name); |
| goto buf_sync_err_3; |
| } |
| |
| if (!(buf_sync->flags & MDP_BUF_SYNC_FLAG_RETIRE_FENCE)) |
| goto skip_retire_fence; |
| |
| if (sync_pt_data->get_retire_fence) |
| retire_fence = sync_pt_data->get_retire_fence(sync_pt_data); |
| else |
| retire_fence = NULL; |
| |
| if (IS_ERR_OR_NULL(retire_fence)) { |
| val += sync_pt_data->retire_threshold; |
| retire_fence = mdss_fb_sync_get_fence( |
| sync_pt_data->timeline, "mdp-retire", val); |
| } |
| |
| if (IS_ERR_OR_NULL(retire_fence)) { |
| pr_err("%s: unable to retrieve retire fence\n", |
| sync_pt_data->fence_name); |
| ret = retire_fence ? PTR_ERR(rel_fence) : -ENOMEM; |
| goto buf_sync_err_3; |
| } |
| retire_fen_fd = get_unused_fd_flags(0); |
| |
| if (retire_fen_fd < 0) { |
| pr_err("%s: get_unused_fd_flags failed for retire fence error:0x%x\n", |
| sync_pt_data->fence_name, retire_fen_fd); |
| ret = retire_fen_fd; |
| sync_fence_put(retire_fence); |
| goto buf_sync_err_3; |
| } |
| |
| ret = copy_to_user(buf_sync->retire_fen_fd, &retire_fen_fd, |
| sizeof(int)); |
| if (ret) { |
| pr_err("%s: copy_to_user failed for retire fence\n", |
| sync_pt_data->fence_name); |
| put_unused_fd(retire_fen_fd); |
| sync_fence_put(retire_fence); |
| goto buf_sync_err_3; |
| } |
| |
| sync_fence_install(retire_fence, retire_fen_fd); |
| |
| skip_retire_fence: |
| sync_fence_install(rel_fence, rel_fen_fd); |
| mutex_unlock(&sync_pt_data->sync_mutex); |
| |
| if (buf_sync->flags & MDP_BUF_SYNC_FLAG_WAIT) |
| mdss_fb_wait_for_fence(sync_pt_data); |
| |
| return ret; |
| buf_sync_err_3: |
| put_unused_fd(rel_fen_fd); |
| buf_sync_err_2: |
| sync_fence_put(rel_fence); |
| buf_sync_err_1: |
| for (i = 0; i < sync_pt_data->acq_fen_cnt; i++) |
| sync_fence_put(sync_pt_data->acq_fen[i]); |
| sync_pt_data->acq_fen_cnt = 0; |
| mutex_unlock(&sync_pt_data->sync_mutex); |
| return ret; |
| } |
| static int mdss_fb_display_commit(struct fb_info *info, |
| unsigned long *argp) |
| { |
| int ret; |
| struct mdp_display_commit disp_commit; |
| |
| ret = copy_from_user(&disp_commit, argp, |
| sizeof(disp_commit)); |
| if (ret) { |
| pr_err("%s:copy_from_user failed\n", __func__); |
| return ret; |
| } |
| ret = mdss_fb_pan_display_ex(info, &disp_commit); |
| return ret; |
| } |
| |
| /** |
| * __mdss_fb_copy_pixel_ext() - copy pixel extension payload |
| * @src: pixel extn structure |
| * @dest: Qseed3/pixel extn common payload |
| * |
| * Function copies the pixel extension parameters into the scale data structure, |
| * this is required to allow using the scale_v2 data structure for both |
| * QSEED2 and QSEED3 |
| */ |
| static void __mdss_fb_copy_pixel_ext(struct mdp_scale_data *src, |
| struct mdp_scale_data_v2 *dest) |
| { |
| if (!src || !dest) |
| return; |
| memcpy(dest->init_phase_x, src->init_phase_x, |
| sizeof(src->init_phase_x)); |
| memcpy(dest->phase_step_x, src->phase_step_x, |
| sizeof(src->init_phase_x)); |
| memcpy(dest->init_phase_y, src->init_phase_y, |
| sizeof(src->init_phase_x)); |
| memcpy(dest->phase_step_y, src->phase_step_y, |
| sizeof(src->init_phase_x)); |
| |
| memcpy(dest->num_ext_pxls_left, src->num_ext_pxls_left, |
| sizeof(src->num_ext_pxls_left)); |
| memcpy(dest->num_ext_pxls_right, src->num_ext_pxls_right, |
| sizeof(src->num_ext_pxls_right)); |
| memcpy(dest->num_ext_pxls_top, src->num_ext_pxls_top, |
| sizeof(src->num_ext_pxls_top)); |
| memcpy(dest->num_ext_pxls_btm, src->num_ext_pxls_btm, |
| sizeof(src->num_ext_pxls_btm)); |
| |
| memcpy(dest->left_ftch, src->left_ftch, sizeof(src->left_ftch)); |
| memcpy(dest->left_rpt, src->left_rpt, sizeof(src->left_rpt)); |
| memcpy(dest->right_ftch, src->right_ftch, sizeof(src->right_ftch)); |
| memcpy(dest->right_rpt, src->right_rpt, sizeof(src->right_rpt)); |
| |
| |
| memcpy(dest->top_rpt, src->top_rpt, sizeof(src->top_rpt)); |
| memcpy(dest->btm_rpt, src->btm_rpt, sizeof(src->btm_rpt)); |
| memcpy(dest->top_ftch, src->top_ftch, sizeof(src->top_ftch)); |
| memcpy(dest->btm_ftch, src->btm_ftch, sizeof(src->btm_ftch)); |
| |
| memcpy(dest->roi_w, src->roi_w, sizeof(src->roi_w)); |
| } |
| |
| static int __mdss_fb_scaler_handler(struct mdp_input_layer *layer) |
| { |
| int ret = 0; |
| struct mdp_scale_data *pixel_ext = NULL; |
| struct mdp_scale_data_v2 *scale = NULL; |
| |
| if ((layer->flags & MDP_LAYER_ENABLE_PIXEL_EXT) && |
| (layer->flags & MDP_LAYER_ENABLE_QSEED3_SCALE)) { |
| pr_err("Invalid flag configuration for scaler, %x\n", |
| layer->flags); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| if (layer->flags & MDP_LAYER_ENABLE_PIXEL_EXT) { |
| scale = kzalloc(sizeof(struct mdp_scale_data_v2), |
| GFP_KERNEL); |
| pixel_ext = kzalloc(sizeof(struct mdp_scale_data), |
| GFP_KERNEL); |
| if (!scale || !pixel_ext) { |
| mdss_mdp_free_layer_pp_info(layer); |
| ret = -ENOMEM; |
| goto err; |
| } |
| ret = copy_from_user(pixel_ext, layer->scale, |
| sizeof(struct mdp_scale_data)); |
| if (ret) { |
| mdss_mdp_free_layer_pp_info(layer); |
| ret = -EFAULT; |
| goto err; |
| } |
| __mdss_fb_copy_pixel_ext(pixel_ext, scale); |
| layer->scale = scale; |
| } else if (layer->flags & MDP_LAYER_ENABLE_QSEED3_SCALE) { |
| scale = kzalloc(sizeof(struct mdp_scale_data_v2), |
| GFP_KERNEL); |
| if (!scale) { |
| mdss_mdp_free_layer_pp_info(layer); |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| ret = copy_from_user(scale, layer->scale, |
| sizeof(struct mdp_scale_data_v2)); |
| if (ret) { |
| mdss_mdp_free_layer_pp_info(layer); |
| ret = -EFAULT; |
| goto err; |
| } |
| layer->scale = scale; |
| } else { |
| layer->scale = NULL; |
| } |
| kfree(pixel_ext); |
| return ret; |
| err: |
| kfree(pixel_ext); |
| kfree(scale); |
| layer->scale = NULL; |
| return ret; |
| } |
| |
| static int mdss_fb_atomic_commit_ioctl(struct fb_info *info, |
| unsigned long *argp, struct file *file) |
| { |
| int ret, i = 0, j = 0, rc; |
| struct mdp_layer_commit commit; |
| u32 buffer_size, layer_count; |
| struct mdp_input_layer *layer, *layer_list = NULL; |
| struct mdp_input_layer __user *input_layer_list; |
| struct mdp_output_layer *output_layer = NULL; |
| struct mdp_output_layer __user *output_layer_user; |
| struct mdp_frc_info *frc_info = NULL; |
| struct mdp_frc_info __user *frc_info_user; |
| struct msm_fb_data_type *mfd; |
| struct mdss_overlay_private *mdp5_data = NULL; |
| |
| ret = copy_from_user(&commit, argp, sizeof(struct mdp_layer_commit)); |
| if (ret) { |
| pr_err("%s:copy_from_user failed\n", __func__); |
| return ret; |
| } |
| |
| mfd = (struct msm_fb_data_type *)info->par; |
| if (!mfd) |
| return -EINVAL; |
| |
| mdp5_data = mfd_to_mdp5_data(mfd); |
| |
| if (mfd->panel_info->panel_dead) { |
| pr_debug("early commit return\n"); |
| MDSS_XLOG(mfd->panel_info->panel_dead); |
| /* |
| * In case of an ESD attack, since we early return from the |
| * commits, we need to signal the outstanding fences. |
| */ |
| mdss_fb_release_fences(mfd); |
| if ((mfd->panel.type == MIPI_CMD_PANEL) && |
| mfd->mdp.signal_retire_fence && mdp5_data) |
| mfd->mdp.signal_retire_fence(mfd, |
| mdp5_data->retire_cnt); |
| return 0; |
| } |
| |
| output_layer_user = commit.commit_v1.output_layer; |
| if (output_layer_user) { |
| buffer_size = sizeof(struct mdp_output_layer); |
| output_layer = kzalloc(buffer_size, GFP_KERNEL); |
| if (!output_layer) { |
| pr_err("unable to allocate memory for output layer\n"); |
| return -ENOMEM; |
| } |
| |
| ret = copy_from_user(output_layer, |
| output_layer_user, buffer_size); |
| if (ret) { |
| pr_err("layer list copy from user failed\n"); |
| goto err; |
| } |
| commit.commit_v1.output_layer = output_layer; |
| } |
| |
| layer_count = commit.commit_v1.input_layer_cnt; |
| input_layer_list = commit.commit_v1.input_layers; |
| |
| if (layer_count > MAX_LAYER_COUNT) { |
| ret = -EINVAL; |
| goto err; |
| } else if (layer_count) { |
| buffer_size = sizeof(struct mdp_input_layer) * layer_count; |
| layer_list = kzalloc(buffer_size, GFP_KERNEL); |
| if (!layer_list) { |
| pr_err("unable to allocate memory for layers\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| ret = copy_from_user(layer_list, input_layer_list, buffer_size); |
| if (ret) { |
| pr_err("layer list copy from user failed\n"); |
| goto err; |
| } |
| |
| commit.commit_v1.input_layers = layer_list; |
| |
| for (i = 0; i < layer_count; i++) { |
| layer = &layer_list[i]; |
| |
| if (!(layer->flags & MDP_LAYER_PP)) { |
| layer->pp_info = NULL; |
| } else { |
| ret = mdss_mdp_copy_layer_pp_info(layer); |
| if (ret) { |
| pr_err("failure to copy pp_info data for layer %d, ret = %d\n", |
| i, ret); |
| goto err; |
| } |
| } |
| |
| if ((layer->flags & MDP_LAYER_ENABLE_PIXEL_EXT) || |
| (layer->flags & |
| MDP_LAYER_ENABLE_QSEED3_SCALE)) { |
| ret = __mdss_fb_scaler_handler(layer); |
| if (ret) { |
| pr_err("failure to copy scale params for layer %d, ret = %d\n", |
| i, ret); |
| goto err; |
| } |
| } else { |
| layer->scale = NULL; |
| } |
| } |
| } |
| |
| /* Copy Deterministic Frame Rate Control info from userspace */ |
| frc_info_user = commit.commit_v1.frc_info; |
| if (frc_info_user) { |
| frc_info = kzalloc(sizeof(struct mdp_frc_info), GFP_KERNEL); |
| if (!frc_info) { |
| pr_err("unable to allocate memory for frc\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| ret = copy_from_user(frc_info, frc_info_user, |
| sizeof(struct mdp_frc_info)); |
| if (ret) { |
| pr_err("frc info copy from user failed\n"); |
| goto frc_err; |
| } |
| |
| commit.commit_v1.frc_info = frc_info; |
| } |
| |
| ATRACE_BEGIN("ATOMIC_COMMIT"); |
| ret = mdss_fb_atomic_commit(info, &commit, file); |
| if (ret) |
| pr_err("atomic commit failed ret:%d\n", ret); |
| ATRACE_END("ATOMIC_COMMIT"); |
| |
| if (layer_count) { |
| for (j = 0; j < layer_count; j++) { |
| rc = copy_to_user(&input_layer_list[j].error_code, |
| &layer_list[j].error_code, sizeof(int)); |
| if (rc) |
| pr_err("layer error code copy to user failed\n"); |
| } |
| |
| commit.commit_v1.input_layers = input_layer_list; |
| commit.commit_v1.output_layer = output_layer_user; |
| commit.commit_v1.frc_info = frc_info_user; |
| rc = copy_to_user(argp, &commit, |
| sizeof(struct mdp_layer_commit)); |
| if (rc) |
| pr_err("copy to user for release & retire fence failed\n"); |
| } |
| |
| frc_err: |
| kfree(frc_info); |
| err: |
| for (i--; i >= 0; i--) { |
| kfree(layer_list[i].scale); |
| layer_list[i].scale = NULL; |
| mdss_mdp_free_layer_pp_info(&layer_list[i]); |
| } |
| kfree(layer_list); |
| kfree(output_layer); |
| |
| return ret; |
| } |
| |
| int mdss_fb_switch_check(struct msm_fb_data_type *mfd, u32 mode) |
| { |
| struct mdss_panel_info *pinfo = NULL; |
| int panel_type; |
| |
| if (!mfd || !mfd->panel_info) |
| return -EINVAL; |
| |
| pinfo = mfd->panel_info; |
| |
| if ((!mfd->op_enable) || (mdss_fb_is_power_off(mfd))) |
| return -EPERM; |
| |
| if (pinfo->mipi.dms_mode != DYNAMIC_MODE_SWITCH_IMMEDIATE) { |
| pr_warn("Panel does not support immediate dynamic switch!\n"); |
| return -EPERM; |
| } |
| |
| if (mfd->dcm_state != DCM_UNINIT) { |
| pr_warn("Switch not supported during DCM!\n"); |
| return -EPERM; |
| } |
| |
| mutex_lock(&mfd->switch_lock); |
| if (mode == pinfo->type) { |
| pr_debug("Already in requested mode!\n"); |
| mutex_unlock(&mfd->switch_lock); |
| return -EPERM; |
| } |
| mutex_unlock(&mfd->switch_lock); |
| |
| panel_type = mfd->panel.type; |
| if (panel_type != MIPI_VIDEO_PANEL && panel_type != MIPI_CMD_PANEL) { |
| pr_debug("Panel not in mipi video or cmd mode, cannot change\n"); |
| return -EPERM; |
| } |
| |
| return 0; |
| } |
| |
| static int mdss_fb_immediate_mode_switch(struct msm_fb_data_type *mfd, u32 mode) |
| { |
| int ret; |
| u32 tranlated_mode; |
| |
| if (mode) |
| tranlated_mode = MIPI_CMD_PANEL; |
| else |
| tranlated_mode = MIPI_VIDEO_PANEL; |
| |
| pr_debug("%s: Request to switch to %d,", __func__, tranlated_mode); |
| |
| ret = mdss_fb_switch_check(mfd, tranlated_mode); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&mfd->switch_lock); |
| if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) { |
| pr_err("%s: Mode switch already in progress\n", __func__); |
| ret = -EAGAIN; |
| goto exit; |
| } |
| mfd->switch_state = MDSS_MDP_WAIT_FOR_VALIDATE; |
| mfd->switch_new_mode = tranlated_mode; |
| |
| exit: |
| mutex_unlock(&mfd->switch_lock); |
| return ret; |
| } |
| |
| /* |
| * mdss_fb_mode_switch() - Function to change DSI mode |
| * @mfd: Framebuffer data structure for display |
| * @mode: Enabled/Disable LowPowerMode |
| * 1: Switch to Command Mode |
| * 0: Switch to video Mode |
| * |
| * This function is used to change from DSI mode based on the |
| * argument @mode on the next frame to be displayed. |
| */ |
| static int mdss_fb_mode_switch(struct msm_fb_data_type *mfd, u32 mode) |
| { |
| struct mdss_panel_info *pinfo = NULL; |
| int ret = 0; |
| |
| if (!mfd || !mfd->panel_info) |
| return -EINVAL; |
| |
| pinfo = mfd->panel_info; |
| if (pinfo->mipi.dms_mode == DYNAMIC_MODE_SWITCH_SUSPEND_RESUME) { |
| ret = mdss_fb_blanking_mode_switch(mfd, mode); |
| } else if (pinfo->mipi.dms_mode == DYNAMIC_MODE_SWITCH_IMMEDIATE) { |
| ret = mdss_fb_immediate_mode_switch(mfd, mode); |
| } else { |
| pr_warn("Panel does not support dynamic mode switch!\n"); |
| ret = -EPERM; |
| } |
| |
| return ret; |
| } |
| |
| static int __ioctl_wait_idle(struct msm_fb_data_type *mfd, u32 cmd) |
| { |
| int ret = 0; |
| |
| if (mfd->wait_for_kickoff && |
| ((cmd == MSMFB_OVERLAY_PREPARE) || |
| (cmd == MSMFB_BUFFER_SYNC) || |
| (cmd == MSMFB_OVERLAY_PLAY) || |
| (cmd == MSMFB_CURSOR) || |
| (cmd == MSMFB_METADATA_GET) || |
| (cmd == MSMFB_METADATA_SET) || |
| (cmd == MSMFB_OVERLAY_GET) || |
| (cmd == MSMFB_OVERLAY_UNSET) || |
| (cmd == MSMFB_OVERLAY_SET))) { |
| ret = mdss_fb_wait_for_kickoff(mfd); |
| } |
| |
| if (ret && (ret != -ESHUTDOWN)) |
| pr_err("wait_idle failed. cmd=0x%x rc=%d\n", cmd, ret); |
| |
| return ret; |
| } |
| |
| #ifdef TARGET_HW_MDSS_MDP3 |
| static bool check_not_supported_ioctl(u32 cmd) |
| { |
| return false; |
| } |
| #else |
| static bool check_not_supported_ioctl(u32 cmd) |
| { |
| return((cmd == MSMFB_OVERLAY_SET) || (cmd == MSMFB_OVERLAY_UNSET) || |
| (cmd == MSMFB_OVERLAY_GET) || (cmd == MSMFB_OVERLAY_PREPARE) || |
| (cmd == MSMFB_DISPLAY_COMMIT) || (cmd == MSMFB_OVERLAY_PLAY) || |
| (cmd == MSMFB_BUFFER_SYNC) || (cmd == MSMFB_OVERLAY_QUEUE) || |
| (cmd == MSMFB_NOTIFY_UPDATE)); |
| } |
| #endif |
| |
| /* |
| * mdss_fb_do_ioctl() - MDSS Framebuffer ioctl function |
| * @info: pointer to framebuffer info |
| * @cmd: ioctl command |
| * @arg: argument to ioctl |
| * |
| * This function provides an architecture agnostic implementation |
| * of the mdss framebuffer ioctl. This function can be called |
| * by compat ioctl or regular ioctl to handle the supported commands. |
| */ |
| int mdss_fb_do_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg, struct file *file) |
| { |
| struct msm_fb_data_type *mfd; |
| void __user *argp = (void __user *)arg; |
| int ret = -ENOTSUP; |
| struct mdp_buf_sync buf_sync; |
| unsigned int dsi_mode = 0; |
| struct mdss_panel_data *pdata = NULL; |
| |
| if (!info || !info->par) |
| return -EINVAL; |
| |
| mfd = (struct msm_fb_data_type *)info->par; |
| if (!mfd) |
| return -EINVAL; |
| |
| if (mfd->shutdown_pending) |
| return -ESHUTDOWN; |
| |
| pdata = dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata || pdata->panel_info.dynamic_switch_pending) |
| return -EPERM; |
| |
| if (check_not_supported_ioctl(cmd)) { |
| pr_err("Unsupported ioctl\n"); |
| return -EINVAL; |
| } |
| |
| atomic_inc(&mfd->ioctl_ref_cnt); |
| |
| mdss_fb_power_setting_idle(mfd); |
| |
| ret = __ioctl_wait_idle(mfd, cmd); |
| if (ret) |
| goto exit; |
| |
| 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_BUFFER_SYNC: |
| ret = copy_from_user(&buf_sync, argp, sizeof(buf_sync)); |
| if (ret) |
| goto exit; |
| |
| if ((!mfd->op_enable) || (mdss_fb_is_power_off(mfd))) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| ret = mdss_fb_handle_buf_sync_ioctl(&mfd->mdp_sync_pt_data, |
| &buf_sync); |
| if (!ret) |
| ret = copy_to_user(argp, &buf_sync, sizeof(buf_sync)); |
| break; |
| |
| case MSMFB_NOTIFY_UPDATE: |
| ret = mdss_fb_notify_update(mfd, argp); |
| break; |
| |
| case MSMFB_DISPLAY_COMMIT: |
| ret = mdss_fb_display_commit(info, argp); |
| break; |
| |
| case MSMFB_LPM_ENABLE: |
| ret = copy_from_user(&dsi_mode, argp, sizeof(dsi_mode)); |
| if (ret) { |
| pr_err("%s: MSMFB_LPM_ENABLE ioctl failed\n", __func__); |
| goto exit; |
| } |
| |
| ret = mdss_fb_mode_switch(mfd, dsi_mode); |
| break; |
| case MSMFB_ATOMIC_COMMIT: |
| ret = mdss_fb_atomic_commit_ioctl(info, argp, file); |
| break; |
| |
| case MSMFB_ASYNC_POSITION_UPDATE: |
| ret = mdss_fb_async_position_update_ioctl(info, argp); |
| break; |
| |
| default: |
| if (mfd->mdp.ioctl_handler) |
| ret = mfd->mdp.ioctl_handler(mfd, cmd, argp); |
| break; |
| } |
| |
| if (ret == -ENOTSUP) |
| pr_err("unsupported ioctl (%x)\n", cmd); |
| |
| exit: |
| if (!atomic_dec_return(&mfd->ioctl_ref_cnt)) |
| wake_up_all(&mfd->ioctl_q); |
| |
| return ret; |
| } |
| |
| static int mdss_fb_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg, struct file *file) |
| { |
| if (!info || !info->par) |
| return -EINVAL; |
| |
| return mdss_fb_do_ioctl(info, cmd, arg, file); |
| } |
| |
| 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 = NULL; |
| 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; |
| } |
| |
| if (pdata->get_fb_node) |
| node = pdata->get_fb_node(pdev); |
| |
| if (!node) { |
| node = of_parse_phandle(pdev->dev.of_node, |
| "qcom,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; |
| } |
| |
| pdata->active = true; |
| 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; |
| |
| if (mfd->iova) |
| *start = mfd->iova; |
| else |
| *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 (fb_get_options("msmfb", NULL)) |
| return rc; |
| |
| if (platform_driver_register(&mdss_fb_driver)) |
| return rc; |
| |
| return 0; |
| } |
| |
| module_init(mdss_fb_init); |
| |
| int mdss_fb_suspres_panel(struct device *dev, void *data) |
| { |
| struct msm_fb_data_type *mfd; |
| int rc = 0; |
| u32 event; |
| |
| if (!data) { |
| pr_err("Device state not defined\n"); |
| return -EINVAL; |
| } |
| mfd = dev_get_drvdata(dev); |
| if (!mfd) |
| return 0; |
| |
| event = *((bool *) data) ? MDSS_EVENT_RESUME : MDSS_EVENT_SUSPEND; |
| |
| /* Do not send runtime suspend/resume for HDMI primary */ |
| if (!mdss_fb_is_hdmi_primary(mfd)) { |
| rc = mdss_fb_send_panel_event(mfd, event, NULL); |
| if (rc) |
| pr_warn("unable to %s fb%d (%d)\n", |
| event == MDSS_EVENT_RESUME ? |
| "resume" : "suspend", |
| mfd->index, rc); |
| } |
| return rc; |
| } |
| |
| /* |
| * mdss_fb_report_panel_dead() - Sends the PANEL_ALIVE=0 status to HAL layer. |
| * @mfd : frame buffer structure associated with fb device. |
| * |
| * This function is called if the panel fails to respond as expected to |
| * the register read/BTA or if the TE signal is not coming as expected |
| * from the panel. The function sends the PANEL_ALIVE=0 status to HAL |
| * layer. |
| */ |
| void mdss_fb_report_panel_dead(struct msm_fb_data_type *mfd) |
| { |
| char *envp[2] = {"PANEL_ALIVE=0", NULL}; |
| struct mdss_panel_data *pdata = |
| dev_get_platdata(&mfd->pdev->dev); |
| if (!pdata) { |
| pr_err("Panel data not available\n"); |
| return; |
| } |
| |
| pdata->panel_info.panel_dead = true; |
| kobject_uevent_env(&mfd->fbi->dev->kobj, |
| KOBJ_CHANGE, envp); |
| pr_err("Panel has gone bad, sending uevent - %s\n", envp[0]); |
| } |
| |
| |
| /* |
| * mdss_fb_calc_fps() - Calculates fps value. |
| * @mfd : frame buffer structure associated with fb device. |
| * |
| * This function is called at frame done. It counts the number |
| * of frames done for every 1 sec. Stores the value in measured_fps. |
| * measured_fps value is 10 times the calculated fps value. |
| * For example, measured_fps= 594 for calculated fps of 59.4 |
| */ |
| void mdss_fb_calc_fps(struct msm_fb_data_type *mfd) |
| { |
| ktime_t current_time_us; |
| u64 fps, diff_us; |
| |
| current_time_us = ktime_get(); |
| diff_us = (u64)ktime_us_delta(current_time_us, |
| mfd->fps_info.last_sampled_time_us); |
| mfd->fps_info.frame_count++; |
| |
| if (diff_us >= MDP_TIME_PERIOD_CALC_FPS_US) { |
| fps = ((u64)mfd->fps_info.frame_count) * 10000000; |
| do_div(fps, diff_us); |
| mfd->fps_info.measured_fps = (unsigned int)fps; |
| pr_debug(" MDP_FPS for fb%d is %d.%d\n", |
| mfd->index, (unsigned int)fps/10, (unsigned int)fps%10); |
| mfd->fps_info.last_sampled_time_us = current_time_us; |
| mfd->fps_info.frame_count = 0; |
| } |
| } |