| /* |
| * |
| * Core MSM framebuffer driver. |
| * |
| * Copyright (C) 2007 Google Incorporated |
| * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/mm.h> |
| #include <linux/fb.h> |
| #include "msm_mdp.h" |
| #include <linux/init.h> |
| #include <linux/ioport.h> |
| #include <linux/device.h> |
| #include <linux/dma-mapping.h> |
| #include <mach/board.h> |
| #include <linux/uaccess.h> |
| |
| #include <linux/workqueue.h> |
| #include <linux/string.h> |
| #include <linux/version.h> |
| #include <linux/proc_fs.h> |
| #include <linux/vmalloc.h> |
| #include <linux/debugfs.h> |
| #include <linux/console.h> |
| #include <linux/leds.h> |
| #include <asm/dma-mapping.h> |
| |
| |
| #define MSM_FB_C |
| #include "msm_fb.h" |
| #include "mddihosti.h" |
| #include "tvenc.h" |
| #include "mdp.h" |
| #include "mdp4.h" |
| |
| #ifdef CONFIG_FB_MSM_LOGO |
| #define INIT_IMAGE_FILE "/logo.rle" |
| extern int load_565rle_image(char *filename); |
| #endif |
| |
| |
| #define pgprot_noncached(prot) \ |
| __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED) |
| #define pgprot_writecombine(prot) \ |
| __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE) |
| #define pgprot_device(prot) \ |
| __pgprot_modify(prot, L_PTE_MT_MASK|L_PTE_EXEC, L_PTE_MT_DEV_NONSHARED) |
| #define pgprot_writethroughcache(prot) \ |
| __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITETHROUGH) |
| #define pgprot_writebackcache(prot) \ |
| __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITEBACK) |
| #define pgprot_writebackwacache(prot) \ |
| __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_WRITEALLOC) |
| |
| static unsigned char *fbram; |
| static unsigned char *fbram_phys; |
| static int fbram_size; |
| |
| static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; |
| static int pdev_list_cnt; |
| |
| int vsync_mode = 1; |
| |
| #define MAX_FBI_LIST 32 |
| static struct fb_info *fbi_list[MAX_FBI_LIST]; |
| static int fbi_list_index; |
| |
| static struct msm_fb_data_type *mfd_list[MAX_FBI_LIST]; |
| static int mfd_list_index; |
| |
| static u32 msm_fb_pseudo_palette[16] = { |
| 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff |
| }; |
| |
| u32 msm_fb_debug_enabled; |
| /* Setting msm_fb_msg_level to 8 prints out ALL messages */ |
| u32 msm_fb_msg_level = 7; |
| |
| /* Setting mddi_msg_level to 8 prints out ALL messages */ |
| u32 mddi_msg_level = 5; |
| |
| extern int32 mdp_block_power_cnt[MDP_MAX_BLOCK]; |
| extern unsigned long mdp_timer_duration; |
| |
| static int msm_fb_register(struct msm_fb_data_type *mfd); |
| static int msm_fb_open(struct fb_info *info, int user); |
| static int msm_fb_release(struct fb_info *info, int user); |
| static int msm_fb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info); |
| static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd); |
| int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd); |
| static int msm_fb_check_var(struct fb_var_screeninfo *var, |
| struct fb_info *info); |
| static int msm_fb_set_par(struct fb_info *info); |
| static int msm_fb_blank_sub(int blank_mode, struct fb_info *info, |
| boolean op_enable); |
| static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd); |
| static int msm_fb_resume_sub(struct msm_fb_data_type *mfd); |
| static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg); |
| static int msm_fb_mmap(struct fb_info *info, struct vm_area_struct * vma); |
| |
| #ifdef MSM_FB_ENABLE_DBGFS |
| |
| #define MSM_FB_MAX_DBGFS 1024 |
| #define MAX_BACKLIGHT_BRIGHTNESS 255 |
| |
| int msm_fb_debugfs_file_index; |
| struct dentry *msm_fb_debugfs_root; |
| struct dentry *msm_fb_debugfs_file[MSM_FB_MAX_DBGFS]; |
| |
| struct dentry *msm_fb_get_debugfs_root(void) |
| { |
| if (msm_fb_debugfs_root == NULL) |
| msm_fb_debugfs_root = debugfs_create_dir("msm_fb", NULL); |
| |
| return msm_fb_debugfs_root; |
| } |
| |
| void msm_fb_debugfs_file_create(struct dentry *root, const char *name, |
| u32 *var) |
| { |
| if (msm_fb_debugfs_file_index >= MSM_FB_MAX_DBGFS) |
| return; |
| |
| msm_fb_debugfs_file[msm_fb_debugfs_file_index++] = |
| debugfs_create_u32(name, S_IRUGO | S_IWUSR, root, var); |
| } |
| #endif |
| |
| int msm_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if (!mfd->cursor_update) |
| return -ENODEV; |
| |
| return mfd->cursor_update(info, cursor); |
| } |
| |
| static int msm_fb_resource_initialized; |
| |
| #ifndef CONFIG_FB_BACKLIGHT |
| static int lcd_backlight_registered; |
| |
| static void msm_fb_set_bl_brightness(struct led_classdev *led_cdev, |
| enum led_brightness value) |
| { |
| struct msm_fb_data_type *mfd = dev_get_drvdata(led_cdev->dev->parent); |
| int bl_lvl; |
| |
| if (value > MAX_BACKLIGHT_BRIGHTNESS) |
| value = MAX_BACKLIGHT_BRIGHTNESS; |
| |
| /* This maps android backlight level 0 to 255 into |
| driver backlight level 0 to bl_max with rounding */ |
| bl_lvl = (2 * value * mfd->panel_info.bl_max + MAX_BACKLIGHT_BRIGHTNESS) |
| /(2 * MAX_BACKLIGHT_BRIGHTNESS); |
| |
| if (!bl_lvl && value) |
| bl_lvl = 1; |
| |
| msm_fb_set_backlight(mfd, bl_lvl, 1); |
| } |
| |
| static struct led_classdev backlight_led = { |
| .name = "lcd-backlight", |
| .brightness = MAX_BACKLIGHT_BRIGHTNESS, |
| .brightness_set = msm_fb_set_bl_brightness, |
| }; |
| #endif |
| |
| static struct msm_fb_platform_data *msm_fb_pdata; |
| |
| int msm_fb_detect_client(const char *name) |
| { |
| int ret = -EPERM; |
| #ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT |
| u32 id; |
| #endif |
| |
| if (msm_fb_pdata && msm_fb_pdata->detect_client) { |
| ret = msm_fb_pdata->detect_client(name); |
| |
| /* if it's non mddi panel, we need to pre-scan |
| mddi client to see if we can disable mddi host */ |
| |
| #ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT |
| if (!ret && msm_fb_pdata->mddi_prescan) |
| id = mddi_get_client_id(); |
| #endif |
| } |
| |
| return ret; |
| } |
| |
| static int msm_fb_probe(struct platform_device *pdev) |
| { |
| struct msm_fb_data_type *mfd; |
| int rc; |
| |
| MSM_FB_DEBUG("msm_fb_probe\n"); |
| |
| if ((pdev->id == 0) && (pdev->num_resources > 0)) { |
| msm_fb_pdata = pdev->dev.platform_data; |
| fbram_size = |
| pdev->resource[0].end - pdev->resource[0].start + 1; |
| fbram_phys = (char *)pdev->resource[0].start; |
| fbram = ioremap((unsigned long)fbram_phys, fbram_size); |
| |
| if (!fbram) { |
| printk(KERN_ERR "fbram ioremap failed!\n"); |
| return -ENOMEM; |
| } |
| MSM_FB_INFO("msm_fb_probe: phy_Addr = 0x%x virt = 0x%x\n", |
| (int)fbram_phys, (int)fbram); |
| |
| msm_fb_resource_initialized = 1; |
| return 0; |
| } |
| |
| if (!msm_fb_resource_initialized) |
| return -EPERM; |
| |
| mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); |
| |
| if (!mfd) |
| return -ENODEV; |
| |
| if (mfd->key != MFD_KEY) |
| return -EINVAL; |
| |
| if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) |
| return -ENOMEM; |
| |
| mfd->panel_info.frame_count = 0; |
| mfd->bl_level = mfd->panel_info.bl_max; |
| |
| if (mfd->panel_info.type == LCDC_PANEL) |
| mfd->allow_set_offset = |
| msm_fb_pdata->allow_set_offset != NULL ? |
| msm_fb_pdata->allow_set_offset() : 0; |
| else |
| mfd->allow_set_offset = 0; |
| |
| rc = msm_fb_register(mfd); |
| if (rc) |
| return rc; |
| |
| #ifdef CONFIG_FB_BACKLIGHT |
| msm_fb_config_backlight(mfd); |
| #else |
| /* android supports only one lcd-backlight/lcd for now */ |
| if (!lcd_backlight_registered) { |
| if (led_classdev_register(&pdev->dev, &backlight_led)) |
| printk(KERN_ERR "led_classdev_register failed\n"); |
| else |
| lcd_backlight_registered = 1; |
| } |
| #endif |
| |
| pdev_list[pdev_list_cnt++] = pdev; |
| return 0; |
| } |
| |
| static int msm_fb_remove(struct platform_device *pdev) |
| { |
| struct msm_fb_data_type *mfd; |
| |
| MSM_FB_DEBUG("msm_fb_remove\n"); |
| |
| mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); |
| |
| if (!mfd) |
| return -ENODEV; |
| |
| if (mfd->key != MFD_KEY) |
| return -EINVAL; |
| |
| if (msm_fb_suspend_sub(mfd)) |
| printk(KERN_ERR "msm_fb_remove: can't stop the device %d\n", mfd->index); |
| |
| if (mfd->channel_irq != 0) |
| free_irq(mfd->channel_irq, (void *)mfd); |
| |
| if (mfd->vsync_width_boundary) |
| vfree(mfd->vsync_width_boundary); |
| |
| if (mfd->vsync_resync_timer.function) |
| del_timer(&mfd->vsync_resync_timer); |
| |
| if (mfd->refresh_timer.function) |
| del_timer(&mfd->refresh_timer); |
| |
| if (mfd->dma_hrtimer.function) |
| hrtimer_cancel(&mfd->dma_hrtimer); |
| |
| /* remove /dev/fb* */ |
| unregister_framebuffer(mfd->fbi); |
| |
| #ifdef CONFIG_FB_BACKLIGHT |
| /* remove /sys/class/backlight */ |
| backlight_device_unregister(mfd->fbi->bl_dev); |
| #else |
| if (lcd_backlight_registered) { |
| lcd_backlight_registered = 0; |
| led_classdev_unregister(&backlight_led); |
| } |
| #endif |
| |
| #ifdef MSM_FB_ENABLE_DBGFS |
| if (mfd->sub_dir) |
| debugfs_remove(mfd->sub_dir); |
| #endif |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) |
| static int msm_fb_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct msm_fb_data_type *mfd; |
| int ret = 0; |
| |
| MSM_FB_DEBUG("msm_fb_suspend\n"); |
| |
| mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); |
| |
| if ((!mfd) || (mfd->key != MFD_KEY)) |
| return 0; |
| |
| acquire_console_sem(); |
| fb_set_suspend(mfd->fbi, 1); |
| |
| ret = msm_fb_suspend_sub(mfd); |
| if (ret != 0) { |
| printk(KERN_ERR "msm_fb: failed to suspend! %d\n", ret); |
| fb_set_suspend(mfd->fbi, 0); |
| } else { |
| pdev->dev.power.power_state = state; |
| } |
| |
| release_console_sem(); |
| return ret; |
| } |
| #else |
| #define msm_fb_suspend NULL |
| #endif |
| |
| static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| if ((!mfd) || (mfd->key != MFD_KEY)) |
| return 0; |
| |
| /* |
| * suspend this channel |
| */ |
| mfd->suspend.sw_refreshing_enable = mfd->sw_refreshing_enable; |
| mfd->suspend.op_enable = mfd->op_enable; |
| mfd->suspend.panel_power_on = mfd->panel_power_on; |
| |
| if (mfd->op_enable) { |
| ret = |
| msm_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi, |
| mfd->suspend.op_enable); |
| if (ret) { |
| MSM_FB_INFO |
| ("msm_fb_suspend: can't turn off display!\n"); |
| return ret; |
| } |
| mfd->op_enable = FALSE; |
| } |
| /* |
| * try to power down |
| */ |
| mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); |
| |
| /* |
| * detach display channel irq if there's any |
| * or wait until vsync-resync completes |
| */ |
| if ((mfd->dest == DISPLAY_LCD)) { |
| if (mfd->panel_info.lcd.vsync_enable) { |
| if (mfd->panel_info.lcd.hw_vsync_mode) { |
| if (mfd->channel_irq != 0) |
| disable_irq(mfd->channel_irq); |
| } else { |
| volatile boolean vh_pending; |
| do { |
| vh_pending = mfd->vsync_handler_pending; |
| } while (vh_pending); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) |
| static int msm_fb_resume(struct platform_device *pdev) |
| { |
| /* This resume function is called when interrupt is enabled. |
| */ |
| int ret = 0; |
| struct msm_fb_data_type *mfd; |
| |
| MSM_FB_DEBUG("msm_fb_resume\n"); |
| |
| mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); |
| |
| if ((!mfd) || (mfd->key != MFD_KEY)) |
| return 0; |
| |
| acquire_console_sem(); |
| ret = msm_fb_resume_sub(mfd); |
| pdev->dev.power.power_state = PMSG_ON; |
| fb_set_suspend(mfd->fbi, 1); |
| release_console_sem(); |
| |
| return ret; |
| } |
| #else |
| #define msm_fb_resume NULL |
| #endif |
| |
| static int msm_fb_resume_sub(struct msm_fb_data_type *mfd) |
| { |
| int ret = 0; |
| |
| if ((!mfd) || (mfd->key != MFD_KEY)) |
| return 0; |
| |
| /* attach display channel irq if there's any */ |
| if (mfd->channel_irq != 0) |
| enable_irq(mfd->channel_irq); |
| |
| /* resume state var recover */ |
| mfd->sw_refreshing_enable = mfd->suspend.sw_refreshing_enable; |
| mfd->op_enable = mfd->suspend.op_enable; |
| |
| if (mfd->suspend.panel_power_on) { |
| ret = |
| msm_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi, |
| mfd->op_enable); |
| if (ret) |
| MSM_FB_INFO("msm_fb_resume: can't turn on display!\n"); |
| } |
| |
| return ret; |
| } |
| |
| static struct platform_driver msm_fb_driver = { |
| .probe = msm_fb_probe, |
| .remove = msm_fb_remove, |
| #ifndef CONFIG_HAS_EARLYSUSPEND |
| .suspend = msm_fb_suspend, |
| .resume = msm_fb_resume, |
| #endif |
| .shutdown = NULL, |
| .driver = { |
| /* Driver name must match the device name added in platform.c. */ |
| .name = "msm_fb", |
| }, |
| }; |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void msmfb_early_suspend(struct early_suspend *h) |
| { |
| struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, |
| early_suspend); |
| msm_fb_suspend_sub(mfd); |
| } |
| |
| static void msmfb_early_resume(struct early_suspend *h) |
| { |
| struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, |
| early_suspend); |
| msm_fb_resume_sub(mfd); |
| } |
| #endif |
| |
| void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl, u32 save) |
| { |
| struct msm_fb_panel_data *pdata; |
| |
| pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; |
| |
| if ((pdata) && (pdata->set_backlight)) { |
| down(&mfd->sem); |
| if ((bkl_lvl != mfd->bl_level) || (!save)) { |
| u32 old_lvl; |
| |
| old_lvl = mfd->bl_level; |
| mfd->bl_level = bkl_lvl; |
| pdata->set_backlight(mfd); |
| |
| if (!save) |
| mfd->bl_level = old_lvl; |
| } |
| up(&mfd->sem); |
| } |
| } |
| |
| static int msm_fb_blank_sub(int blank_mode, struct fb_info *info, |
| boolean op_enable) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct msm_fb_panel_data *pdata = NULL; |
| int ret = 0; |
| |
| if (!op_enable) |
| return -EPERM; |
| |
| pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; |
| if ((!pdata) || (!pdata->on) || (!pdata->off)) { |
| printk(KERN_ERR "msm_fb_blank_sub: no panel operation detected!\n"); |
| return -ENODEV; |
| } |
| |
| switch (blank_mode) { |
| case FB_BLANK_UNBLANK: |
| if (!mfd->panel_power_on) { |
| mdelay(100); |
| ret = pdata->on(mfd->pdev); |
| if (ret == 0) { |
| mfd->panel_power_on = TRUE; |
| |
| msm_fb_set_backlight(mfd, |
| mfd->bl_level, 0); |
| |
| /* ToDo: possible conflict with android which doesn't expect sw refresher */ |
| /* |
| if (!mfd->hw_refresh) |
| { |
| if ((ret = msm_fb_resume_sw_refresher(mfd)) != 0) |
| { |
| MSM_FB_INFO("msm_fb_blank_sub: msm_fb_resume_sw_refresher failed = %d!\n",ret); |
| } |
| } |
| */ |
| } |
| } |
| break; |
| |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| case FB_BLANK_POWERDOWN: |
| default: |
| if (mfd->panel_power_on) { |
| int curr_pwr_state; |
| |
| mfd->op_enable = FALSE; |
| curr_pwr_state = mfd->panel_power_on; |
| mfd->panel_power_on = FALSE; |
| |
| mdelay(100); |
| ret = pdata->off(mfd->pdev); |
| if (ret) |
| mfd->panel_power_on = curr_pwr_state; |
| |
| msm_fb_set_backlight(mfd, 0, 0); |
| mfd->op_enable = TRUE; |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void msm_fb_fillrect(struct fb_info *info, |
| const struct fb_fillrect *rect) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| cfb_fillrect(info, rect); |
| if (!mfd->hw_refresh && (info->var.yoffset == 0) && |
| !mfd->sw_currently_refreshing) { |
| struct fb_var_screeninfo var; |
| |
| var = info->var; |
| var.reserved[0] = 0x54445055; |
| var.reserved[1] = (rect->dy << 16) | (rect->dx); |
| var.reserved[2] = ((rect->dy + rect->height) << 16) | |
| (rect->dx + rect->width); |
| |
| msm_fb_pan_display(&var, info); |
| } |
| } |
| |
| static void msm_fb_copyarea(struct fb_info *info, |
| const struct fb_copyarea *area) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| cfb_copyarea(info, area); |
| if (!mfd->hw_refresh && (info->var.yoffset == 0) && |
| !mfd->sw_currently_refreshing) { |
| struct fb_var_screeninfo var; |
| |
| var = info->var; |
| var.reserved[0] = 0x54445055; |
| var.reserved[1] = (area->dy << 16) | (area->dx); |
| var.reserved[2] = ((area->dy + area->height) << 16) | |
| (area->dx + area->width); |
| |
| msm_fb_pan_display(&var, info); |
| } |
| } |
| |
| static void msm_fb_imageblit(struct fb_info *info, const struct fb_image *image) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| cfb_imageblit(info, image); |
| if (!mfd->hw_refresh && (info->var.yoffset == 0) && |
| !mfd->sw_currently_refreshing) { |
| struct fb_var_screeninfo var; |
| |
| var = info->var; |
| var.reserved[0] = 0x54445055; |
| var.reserved[1] = (image->dy << 16) | (image->dx); |
| var.reserved[2] = ((image->dy + image->height) << 16) | |
| (image->dx + image->width); |
| |
| msm_fb_pan_display(&var, info); |
| } |
| } |
| |
| static int msm_fb_blank(int blank_mode, struct fb_info *info) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| return msm_fb_blank_sub(blank_mode, info, mfd->op_enable); |
| } |
| |
| static int msm_fb_set_lut(struct fb_cmap *cmap, struct fb_info *info) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if (!mfd->lut_update) |
| return -ENODEV; |
| |
| mfd->lut_update(info, cmap); |
| return 0; |
| } |
| |
| /* |
| * Custom Framebuffer mmap() function for MSM driver. |
| * Differs from standard mmap() function by allowing for customized |
| * page-protection. |
| */ |
| static int msm_fb_mmap(struct fb_info *info, struct vm_area_struct * vma) |
| { |
| /* Get frame buffer memory range. */ |
| unsigned long start = info->fix.smem_start; |
| u32 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); |
| unsigned long off = vma->vm_pgoff << PAGE_SHIFT; |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| if (off >= len) { |
| /* memory mapped io */ |
| off -= len; |
| if (info->var.accel_flags) { |
| mutex_unlock(&info->lock); |
| return -EINVAL; |
| } |
| start = info->fix.mmio_start; |
| len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); |
| } |
| |
| /* Set VM flags. */ |
| start &= PAGE_MASK; |
| if ((vma->vm_end - vma->vm_start + off) > len) |
| return -EINVAL; |
| off += start; |
| vma->vm_pgoff = off >> PAGE_SHIFT; |
| /* This is an IO map - tell maydump to skip this VMA */ |
| vma->vm_flags |= VM_IO | VM_RESERVED; |
| |
| /* Set VM page protection */ |
| if (mfd->mdp_fb_page_protection == MDP_FB_PAGE_PROTECTION_WRITECOMBINE) |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| else if (mfd->mdp_fb_page_protection == |
| MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE) |
| vma->vm_page_prot = pgprot_writethroughcache(vma->vm_page_prot); |
| else if (mfd->mdp_fb_page_protection == |
| MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE) |
| vma->vm_page_prot = pgprot_writebackcache(vma->vm_page_prot); |
| else if (mfd->mdp_fb_page_protection == |
| MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE) |
| vma->vm_page_prot = pgprot_writebackwacache(vma->vm_page_prot); |
| else |
| vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| |
| /* Remap the frame buffer I/O range */ |
| if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, |
| vma->vm_end - vma->vm_start, |
| vma->vm_page_prot)) |
| return -EAGAIN; |
| |
| return 0; |
| } |
| |
| static struct fb_ops msm_fb_ops = { |
| .owner = THIS_MODULE, |
| .fb_open = msm_fb_open, |
| .fb_release = msm_fb_release, |
| .fb_read = NULL, |
| .fb_write = NULL, |
| .fb_cursor = NULL, |
| .fb_check_var = msm_fb_check_var, /* vinfo check */ |
| .fb_set_par = msm_fb_set_par, /* set the video mode according to info->var */ |
| .fb_setcolreg = NULL, /* set color register */ |
| .fb_blank = msm_fb_blank, /* blank display */ |
| .fb_pan_display = msm_fb_pan_display, /* pan display */ |
| .fb_fillrect = msm_fb_fillrect, /* Draws a rectangle */ |
| .fb_copyarea = msm_fb_copyarea, /* Copy data from area to another */ |
| .fb_imageblit = msm_fb_imageblit, /* Draws a image to the display */ |
| .fb_rotate = NULL, |
| .fb_sync = NULL, /* wait for blit idle, optional */ |
| .fb_ioctl = msm_fb_ioctl, /* perform fb specific ioctl (optional) */ |
| .fb_mmap = msm_fb_mmap, |
| }; |
| |
| static int msm_fb_register(struct msm_fb_data_type *mfd) |
| { |
| int ret = -ENODEV; |
| int bpp; |
| struct msm_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; |
| int fbram_offset; |
| |
| /* |
| * fb info initialization |
| */ |
| fix = &fbi->fix; |
| var = &fbi->var; |
| |
| fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */ |
| fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */ |
| fix->ywrapstep = 0; /* No support */ |
| fix->mmio_start = 0; /* No MMIO Address */ |
| fix->mmio_len = 0; /* No MMIO Address */ |
| fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */ |
| |
| var->xoffset = 0, /* Offset from virtual to visible */ |
| var->yoffset = 0, /* resolution */ |
| var->grayscale = 0, /* No graylevels */ |
| var->nonstd = 0, /* standard pixel format */ |
| var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */ |
| var->height = -1, /* height of picture in mm */ |
| var->width = -1, /* width of picture in mm */ |
| var->accel_flags = 0, /* acceleration flags */ |
| var->sync = 0, /* see FB_SYNC_* */ |
| var->rotate = 0, /* angle we rotate counter clockwise */ |
| mfd->op_enable = FALSE; |
| |
| switch (mfd->fb_imgType) { |
| case MDP_RGB_565: |
| fix->type = FB_TYPE_PACKED_PIXELS; |
| fix->xpanstep = 1; |
| fix->ypanstep = 1; |
| var->vmode = FB_VMODE_NONINTERLACED; |
| var->blue.offset = 0; |
| var->green.offset = 5; |
| var->red.offset = 11; |
| var->blue.length = 5; |
| var->green.length = 6; |
| var->red.length = 5; |
| var->blue.msb_right = 0; |
| var->green.msb_right = 0; |
| var->red.msb_right = 0; |
| var->transp.offset = 0; |
| var->transp.length = 0; |
| bpp = 2; |
| break; |
| |
| case MDP_RGB_888: |
| fix->type = FB_TYPE_PACKED_PIXELS; |
| fix->xpanstep = 1; |
| fix->ypanstep = 1; |
| var->vmode = FB_VMODE_NONINTERLACED; |
| var->blue.offset = 0; |
| var->green.offset = 8; |
| var->red.offset = 16; |
| var->blue.length = 8; |
| var->green.length = 8; |
| var->red.length = 8; |
| var->blue.msb_right = 0; |
| var->green.msb_right = 0; |
| var->red.msb_right = 0; |
| var->transp.offset = 0; |
| var->transp.length = 0; |
| bpp = 3; |
| break; |
| |
| case MDP_ARGB_8888: |
| fix->type = FB_TYPE_PACKED_PIXELS; |
| fix->xpanstep = 1; |
| fix->ypanstep = 1; |
| var->vmode = FB_VMODE_NONINTERLACED; |
| var->blue.offset = 0; |
| var->green.offset = 8; |
| var->red.offset = 16; |
| var->blue.length = 8; |
| var->green.length = 8; |
| var->red.length = 8; |
| var->blue.msb_right = 0; |
| var->green.msb_right = 0; |
| var->red.msb_right = 0; |
| var->transp.offset = 24; |
| var->transp.length = 8; |
| bpp = 3; |
| break; |
| |
| case MDP_YCRYCB_H2V1: |
| /* ToDo: need to check TV-Out YUV422i framebuffer format */ |
| /* we might need to create new type define */ |
| 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: |
| MSM_FB_ERR("msm_fb_init: fb %d unkown image type!\n", |
| mfd->index); |
| return ret; |
| } |
| |
| /* The adreno GPU hardware requires that the pitch be aligned to |
| 32 pixels for color buffers, so for the cases where the GPU |
| is writing directly to fb0, the framebuffer pitch |
| also needs to be 32 pixel aligned */ |
| |
| if (mfd->index == 0) |
| fix->line_length = ALIGN(panel_info->xres * bpp, 32); |
| else |
| fix->line_length = panel_info->xres * bpp; |
| |
| fix->smem_len = fix->line_length * panel_info->yres * mfd->fb_page; |
| |
| mfd->var_xres = panel_info->xres; |
| mfd->var_yres = panel_info->yres; |
| |
| var->pixclock = mfd->panel_info.clk_rate; |
| mfd->var_pixclock = var->pixclock; |
| |
| var->xres = panel_info->xres; |
| var->yres = panel_info->yres; |
| var->xres_virtual = panel_info->xres; |
| var->yres_virtual = panel_info->yres * mfd->fb_page; |
| var->bits_per_pixel = bpp * 8, /* FrameBuffer color depth */ |
| /* |
| * id field for fb app |
| */ |
| id = (int *)&mfd->panel; |
| |
| #if defined(CONFIG_FB_MSM_MDP22) |
| snprintf(fix->id, sizeof(fix->id), "msmfb22_%x", (__u32) *id); |
| #elif defined(CONFIG_FB_MSM_MDP30) |
| snprintf(fix->id, sizeof(fix->id), "msmfb30_%x", (__u32) *id); |
| #elif defined(CONFIG_FB_MSM_MDP31) |
| snprintf(fix->id, sizeof(fix->id), "msmfb31_%x", (__u32) *id); |
| #elif defined(CONFIG_FB_MSM_MDP40) |
| snprintf(fix->id, sizeof(fix->id), "msmfb40_%x", (__u32) *id); |
| #else |
| error CONFIG_FB_MSM_MDP undefined ! |
| #endif |
| fbi->fbops = &msm_fb_ops; |
| fbi->flags = FBINFO_FLAG_DEFAULT; |
| fbi->pseudo_palette = msm_fb_pseudo_palette; |
| |
| mfd->ref_cnt = 0; |
| mfd->sw_currently_refreshing = FALSE; |
| mfd->sw_refreshing_enable = TRUE; |
| mfd->panel_power_on = FALSE; |
| |
| mfd->pan_waiting = FALSE; |
| init_completion(&mfd->pan_comp); |
| init_completion(&mfd->refresher_comp); |
| init_MUTEX(&mfd->sem); |
| |
| fbram_offset = PAGE_ALIGN((int)fbram)-(int)fbram; |
| fbram += fbram_offset; |
| fbram_phys += fbram_offset; |
| fbram_size -= fbram_offset; |
| |
| if (fbram_size < fix->smem_len) { |
| printk(KERN_ERR "error: no more framebuffer memory!\n"); |
| return -ENOMEM; |
| } |
| |
| fbi->screen_base = fbram; |
| fbi->fix.smem_start = (unsigned long)fbram_phys; |
| |
| memset(fbi->screen_base, 0x0, fix->smem_len); |
| |
| mfd->op_enable = TRUE; |
| mfd->panel_power_on = FALSE; |
| |
| /* cursor memory allocation */ |
| if (mfd->cursor_update) { |
| mfd->cursor_buf = dma_alloc_coherent(NULL, |
| MDP_CURSOR_SIZE, |
| (dma_addr_t *) &mfd->cursor_buf_phys, |
| GFP_KERNEL); |
| if (!mfd->cursor_buf) |
| mfd->cursor_update = 0; |
| } |
| |
| if (mfd->lut_update) { |
| ret = fb_alloc_cmap(&fbi->cmap, 256, 0); |
| if (ret) |
| printk(KERN_ERR "%s: fb_alloc_cmap() failed!\n", |
| __func__); |
| } |
| |
| if (register_framebuffer(fbi) < 0) { |
| if (mfd->lut_update) |
| fb_dealloc_cmap(&fbi->cmap); |
| |
| if (mfd->cursor_buf) |
| dma_free_coherent(NULL, |
| MDP_CURSOR_SIZE, |
| mfd->cursor_buf, |
| (dma_addr_t) mfd->cursor_buf_phys); |
| |
| mfd->op_enable = FALSE; |
| return -EPERM; |
| } |
| |
| fbram += fix->smem_len; |
| fbram_phys += fix->smem_len; |
| fbram_size -= fix->smem_len; |
| |
| MSM_FB_INFO |
| ("FrameBuffer[%d] %dx%d size=%d bytes is registered successfully!\n", |
| mfd->index, fbi->var.xres, fbi->var.yres, fbi->fix.smem_len); |
| |
| #ifdef CONFIG_FB_MSM_LOGO |
| if (!load_565rle_image(INIT_IMAGE_FILE)) ; /* Flip buffer */ |
| #endif |
| ret = 0; |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| mfd->early_suspend.suspend = msmfb_early_suspend; |
| mfd->early_suspend.resume = msmfb_early_resume; |
| mfd->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 2; |
| register_early_suspend(&mfd->early_suspend); |
| #endif |
| |
| #ifdef MSM_FB_ENABLE_DBGFS |
| { |
| struct dentry *root; |
| struct dentry *sub_dir; |
| char sub_name[2]; |
| |
| root = msm_fb_get_debugfs_root(); |
| if (root != NULL) { |
| sub_name[0] = (char)(mfd->index + 0x30); |
| sub_name[1] = '\0'; |
| sub_dir = debugfs_create_dir(sub_name, root); |
| } else { |
| sub_dir = NULL; |
| } |
| |
| mfd->sub_dir = sub_dir; |
| |
| if (sub_dir) { |
| msm_fb_debugfs_file_create(sub_dir, "op_enable", |
| (u32 *) &mfd->op_enable); |
| msm_fb_debugfs_file_create(sub_dir, "panel_power_on", |
| (u32 *) &mfd-> |
| panel_power_on); |
| msm_fb_debugfs_file_create(sub_dir, "ref_cnt", |
| (u32 *) &mfd->ref_cnt); |
| msm_fb_debugfs_file_create(sub_dir, "fb_imgType", |
| (u32 *) &mfd->fb_imgType); |
| msm_fb_debugfs_file_create(sub_dir, |
| "sw_currently_refreshing", |
| (u32 *) &mfd-> |
| sw_currently_refreshing); |
| msm_fb_debugfs_file_create(sub_dir, |
| "sw_refreshing_enable", |
| (u32 *) &mfd-> |
| sw_refreshing_enable); |
| |
| msm_fb_debugfs_file_create(sub_dir, "xres", |
| (u32 *) &mfd->panel_info. |
| xres); |
| msm_fb_debugfs_file_create(sub_dir, "yres", |
| (u32 *) &mfd->panel_info. |
| yres); |
| msm_fb_debugfs_file_create(sub_dir, "bpp", |
| (u32 *) &mfd->panel_info. |
| bpp); |
| msm_fb_debugfs_file_create(sub_dir, "type", |
| (u32 *) &mfd->panel_info. |
| type); |
| msm_fb_debugfs_file_create(sub_dir, "wait_cycle", |
| (u32 *) &mfd->panel_info. |
| wait_cycle); |
| msm_fb_debugfs_file_create(sub_dir, "pdest", |
| (u32 *) &mfd->panel_info. |
| pdest); |
| msm_fb_debugfs_file_create(sub_dir, "backbuff", |
| (u32 *) &mfd->panel_info. |
| fb_num); |
| msm_fb_debugfs_file_create(sub_dir, "clk_rate", |
| (u32 *) &mfd->panel_info. |
| clk_rate); |
| msm_fb_debugfs_file_create(sub_dir, "frame_count", |
| (u32 *) &mfd->panel_info. |
| frame_count); |
| |
| |
| switch (mfd->dest) { |
| case DISPLAY_LCD: |
| msm_fb_debugfs_file_create(sub_dir, |
| "vsync_enable", |
| (u32 *)&mfd->panel_info.lcd.vsync_enable); |
| msm_fb_debugfs_file_create(sub_dir, |
| "refx100", |
| (u32 *) &mfd->panel_info.lcd. refx100); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_back_porch", |
| (u32 *) &mfd->panel_info.lcd.v_back_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_front_porch", |
| (u32 *) &mfd->panel_info.lcd.v_front_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_pulse_width", |
| (u32 *) &mfd->panel_info.lcd.v_pulse_width); |
| msm_fb_debugfs_file_create(sub_dir, |
| "hw_vsync_mode", |
| (u32 *) &mfd->panel_info.lcd.hw_vsync_mode); |
| msm_fb_debugfs_file_create(sub_dir, |
| "vsync_notifier_period", (u32 *) |
| &mfd->panel_info.lcd.vsync_notifier_period); |
| break; |
| |
| case DISPLAY_LCDC: |
| msm_fb_debugfs_file_create(sub_dir, |
| "h_back_porch", |
| (u32 *) &mfd->panel_info.lcdc.h_back_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "h_front_porch", |
| (u32 *) &mfd->panel_info.lcdc.h_front_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "h_pulse_width", |
| (u32 *) &mfd->panel_info.lcdc.h_pulse_width); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_back_porch", |
| (u32 *) &mfd->panel_info.lcdc.v_back_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_front_porch", |
| (u32 *) &mfd->panel_info.lcdc.v_front_porch); |
| msm_fb_debugfs_file_create(sub_dir, |
| "v_pulse_width", |
| (u32 *) &mfd->panel_info.lcdc.v_pulse_width); |
| msm_fb_debugfs_file_create(sub_dir, |
| "border_clr", |
| (u32 *) &mfd->panel_info.lcdc.border_clr); |
| msm_fb_debugfs_file_create(sub_dir, |
| "underflow_clr", |
| (u32 *) &mfd->panel_info.lcdc.underflow_clr); |
| msm_fb_debugfs_file_create(sub_dir, |
| "hsync_skew", |
| (u32 *) &mfd->panel_info.lcdc.hsync_skew); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| #endif /* MSM_FB_ENABLE_DBGFS */ |
| |
| return ret; |
| } |
| |
| static int msm_fb_open(struct fb_info *info, int user) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if (!mfd->ref_cnt) { |
| mdp_set_dma_pan_info(info, NULL, TRUE); |
| |
| if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) { |
| printk(KERN_ERR "msm_fb_open: can't turn on display!\n"); |
| return -1; |
| } |
| } |
| |
| mfd->ref_cnt++; |
| return 0; |
| } |
| |
| static int msm_fb_release(struct fb_info *info, int user) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| int ret = 0; |
| |
| if (!mfd->ref_cnt) { |
| MSM_FB_INFO("msm_fb_release: try to close unopened fb %d!\n", |
| mfd->index); |
| return -EINVAL; |
| } |
| |
| mfd->ref_cnt--; |
| |
| if (!mfd->ref_cnt) { |
| if ((ret = |
| msm_fb_blank_sub(FB_BLANK_POWERDOWN, info, |
| mfd->op_enable)) != 0) { |
| printk(KERN_ERR "msm_fb_release: can't turn off display!\n"); |
| return ret; |
| } |
| } |
| |
| return ret; |
| } |
| |
| DECLARE_MUTEX(msm_fb_pan_sem); |
| |
| static int msm_fb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct mdp_dirty_region dirty; |
| struct mdp_dirty_region *dirtyPtr = NULL; |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if ((!mfd->op_enable) || (!mfd->panel_power_on)) |
| return -EPERM; |
| |
| if (var->xoffset > (info->var.xres_virtual - info->var.xres)) |
| return -EINVAL; |
| |
| if (var->yoffset > (info->var.yres_virtual - info->var.yres)) |
| return -EINVAL; |
| |
| if (info->fix.xpanstep) |
| info->var.xoffset = |
| (var->xoffset / info->fix.xpanstep) * info->fix.xpanstep; |
| |
| if (info->fix.ypanstep) |
| info->var.yoffset = |
| (var->yoffset / info->fix.ypanstep) * info->fix.ypanstep; |
| |
| /* "UPDT" */ |
| if (var->reserved[0] == 0x54445055) { |
| dirty.xoffset = var->reserved[1] & 0xffff; |
| dirty.yoffset = (var->reserved[1] >> 16) & 0xffff; |
| |
| if ((var->reserved[2] & 0xffff) <= dirty.xoffset) |
| return -EINVAL; |
| if (((var->reserved[2] >> 16) & 0xffff) <= dirty.yoffset) |
| return -EINVAL; |
| |
| dirty.width = (var->reserved[2] & 0xffff) - dirty.xoffset; |
| dirty.height = |
| ((var->reserved[2] >> 16) & 0xffff) - dirty.yoffset; |
| info->var.yoffset = var->yoffset; |
| |
| if (dirty.xoffset < 0) |
| return -EINVAL; |
| |
| if (dirty.yoffset < 0) |
| return -EINVAL; |
| |
| if ((dirty.xoffset + dirty.width) > info->var.xres) |
| return -EINVAL; |
| |
| if ((dirty.yoffset + dirty.height) > info->var.yres) |
| return -EINVAL; |
| |
| if ((dirty.width <= 0) || (dirty.height <= 0)) |
| return -EINVAL; |
| |
| dirtyPtr = &dirty; |
| } |
| |
| /* Flip */ |
| /* A constant value is used to indicate that we should change the DMA |
| output buffer instead of just panning */ |
| |
| if (var->reserved[0] == 0x466c6970) { |
| unsigned long length, address; |
| struct file *p_src_file; |
| struct mdp_img imgdata; |
| int bpp; |
| |
| if (mfd->allow_set_offset) { |
| imgdata.memory_id = var->reserved[1]; |
| imgdata.priv = var->reserved[2]; |
| |
| /* If there is no memory ID then we want to reset back |
| to the original fb visibility */ |
| if (var->reserved[1]) { |
| if (var->reserved[4] == MDP_BLIT_SRC_GEM) { |
| panic("waaaaaaaaaaaaaah"); |
| if ( /*get_gem_img(&imgdata, |
| (unsigned long *) &address, |
| &length)*/ -1 < 0) { |
| return -1; |
| } |
| } else { |
| /*get_img(&imgdata, info, &address, |
| &length, &p_src_file);*/ |
| panic("waaaaaah"); |
| } |
| mfd->ibuf.visible_swapped = TRUE; |
| } else { |
| /* Flip back to the original address |
| adjusted for xoffset and yoffset */ |
| |
| bpp = info->var.bits_per_pixel / 8; |
| address = (unsigned long) info->fix.smem_start; |
| address += info->var.xoffset * bpp + |
| info->var.yoffset * info->fix.line_length; |
| |
| mfd->ibuf.visible_swapped = FALSE; |
| } |
| |
| mdp_set_offset_info(info, address, |
| (var->activate == FB_ACTIVATE_VBL)); |
| |
| mfd->dma_fnc(mfd); |
| return 0; |
| } else |
| return -EINVAL; |
| } |
| |
| down(&msm_fb_pan_sem); |
| mdp_set_dma_pan_info(info, dirtyPtr, |
| (var->activate == FB_ACTIVATE_VBL)); |
| mdp_dma_pan_update(info); |
| up(&msm_fb_pan_sem); |
| |
| ++mfd->panel_info.frame_count; |
| return 0; |
| } |
| |
| static int msm_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| |
| if (var->rotate != FB_ROTATE_UR) |
| return -EINVAL; |
| if (var->grayscale != info->var.grayscale) |
| return -EINVAL; |
| |
| switch (var->bits_per_pixel) { |
| case 16: |
| if ((var->green.offset != 5) || |
| !((var->blue.offset == 11) |
| || (var->blue.offset == 0)) || |
| !((var->red.offset == 11) |
| || (var->red.offset == 0)) || |
| (var->blue.length != 5) || |
| (var->green.length != 6) || |
| (var->red.length != 5) || |
| (var->blue.msb_right != 0) || |
| (var->green.msb_right != 0) || |
| (var->red.msb_right != 0) || |
| (var->transp.offset != 0) || |
| (var->transp.length != 0)) |
| return -EINVAL; |
| break; |
| |
| case 24: |
| if ((var->blue.offset != 0) || |
| (var->green.offset != 8) || |
| (var->red.offset != 16) || |
| (var->blue.length != 8) || |
| (var->green.length != 8) || |
| (var->red.length != 8) || |
| (var->blue.msb_right != 0) || |
| (var->green.msb_right != 0) || |
| (var->red.msb_right != 0) || |
| !(((var->transp.offset == 0) && |
| (var->transp.length == 0)) || |
| ((var->transp.offset == 24) && |
| (var->transp.length == 8)))) |
| return -EINVAL; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if ((var->xres_virtual <= 0) || (var->yres_virtual <= 0)) |
| return -EINVAL; |
| |
| if (info->fix.smem_len < |
| (var->xres_virtual*var->yres_virtual*(var->bits_per_pixel/8))) |
| return -EINVAL; |
| |
| if ((var->xres == 0) || (var->yres == 0)) |
| return -EINVAL; |
| |
| if ((var->xres > mfd->panel_info.xres) || |
| (var->yres > mfd->panel_info.yres)) |
| return -EINVAL; |
| |
| if (var->xoffset > (var->xres_virtual - var->xres)) |
| return -EINVAL; |
| |
| if (var->yoffset > (var->yres_virtual - var->yres)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int msm_fb_set_par(struct fb_info *info) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| struct fb_var_screeninfo *var = &info->var; |
| int old_imgType; |
| int blank = 0; |
| |
| old_imgType = mfd->fb_imgType; |
| switch (var->bits_per_pixel) { |
| case 16: |
| if (var->red.offset == 0) |
| mfd->fb_imgType = MDP_BGR_565; |
| else |
| mfd->fb_imgType = MDP_RGB_565; |
| break; |
| |
| case 24: |
| if ((var->transp.offset == 0) && (var->transp.length == 0)) |
| mfd->fb_imgType = MDP_RGB_888; |
| else if ((var->transp.offset == 24) && |
| (var->transp.length == 8)) { |
| mfd->fb_imgType = MDP_ARGB_8888; |
| info->var.bits_per_pixel = 32; |
| } |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if ((mfd->var_pixclock != var->pixclock) || |
| (mfd->hw_refresh && ((mfd->fb_imgType != old_imgType) || |
| (mfd->var_pixclock != var->pixclock) || |
| (mfd->var_xres != var->xres) || |
| (mfd->var_yres != var->yres)))) { |
| mfd->var_xres = var->xres; |
| mfd->var_yres = var->yres; |
| mfd->var_pixclock = var->pixclock; |
| blank = 1; |
| } |
| |
| if (blank) { |
| msm_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable); |
| msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable); |
| } |
| |
| return 0; |
| } |
| |
| static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd) |
| { |
| if (mfd->hw_refresh) |
| return -EPERM; |
| |
| if (mfd->sw_currently_refreshing) { |
| down(&mfd->sem); |
| mfd->sw_currently_refreshing = FALSE; |
| up(&mfd->sem); |
| |
| /* wait until the refresher finishes the last job */ |
| wait_for_completion_killable(&mfd->refresher_comp); |
| } |
| |
| return 0; |
| } |
| |
| int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd) |
| { |
| boolean do_refresh; |
| |
| if (mfd->hw_refresh) |
| return -EPERM; |
| |
| down(&mfd->sem); |
| if ((!mfd->sw_currently_refreshing) && (mfd->sw_refreshing_enable)) { |
| do_refresh = TRUE; |
| mfd->sw_currently_refreshing = TRUE; |
| } else { |
| do_refresh = FALSE; |
| } |
| up(&mfd->sem); |
| |
| if (do_refresh) |
| mdp_refresh_screen((unsigned long)mfd); |
| |
| return 0; |
| } |
| |
| void mdp_ppp_put_img(struct file *p_src_file, struct file *p_dst_file) |
| { |
| #ifdef CONFIG_ANDROID_PMEM |
| if (p_src_file) |
| put_pmem_file(p_src_file); |
| if (p_dst_file) |
| put_pmem_file(p_dst_file); |
| #endif |
| } |
| |
| int mdp_blit(struct fb_info *info, struct mdp_blit_req *req) |
| { |
| int ret; |
| struct file *p_src_file = 0, *p_dst_file = 0; |
| if (unlikely(req->src_rect.h == 0 || req->src_rect.w == 0)) { |
| printk(KERN_ERR "mpd_ppp: src img of zero size!\n"); |
| return -EINVAL; |
| } |
| if (unlikely(req->dst_rect.h == 0 || req->dst_rect.w == 0)) |
| return 0; |
| |
| ret = mdp_ppp_blit(info, req, &p_src_file, &p_dst_file); |
| mdp_ppp_put_img(p_src_file, p_dst_file); |
| return ret; |
| } |
| |
| typedef void (*msm_dma_barrier_function_pointer) (void *, size_t); |
| |
| static inline void msm_fb_dma_barrier_for_rect(struct fb_info *info, |
| struct mdp_img *img, struct mdp_rect *rect, |
| msm_dma_barrier_function_pointer dma_barrier_fp |
| ) |
| { |
| /* |
| * Compute the start and end addresses of the rectangles. |
| * NOTE: As currently implemented, the data between |
| * the end of one row and the start of the next is |
| * included in the address range rather than |
| * doing multiple calls for each row. |
| */ |
| |
| char * const pmem_start = info->screen_base; |
| /* int bytes_per_pixel = mdp_get_bytes_per_pixel(img->format); |
| unsigned long start = (unsigned long)pmem_start + img->offset + |
| (img->width * rect->y + rect->x) * bytes_per_pixel; |
| size_t size = ((rect->h - 1) * img->width + rect->w) * bytes_per_pixel; |
| (*dma_barrier_fp) ((void *) start, size); |
| */ |
| panic("waaaaah"); |
| } |
| |
| static inline void msm_dma_nc_pre(void) |
| { |
| dmb(); |
| } |
| static inline void msm_dma_wt_pre(void) |
| { |
| dmb(); |
| } |
| static inline void msm_dma_todevice_wb_pre(void *start, size_t size) |
| { |
| #warning this |
| // dma_cache_pre_ops(start, size, DMA_TO_DEVICE); |
| } |
| |
| static inline void msm_dma_fromdevice_wb_pre(void *start, size_t size) |
| { |
| #warning this |
| // dma_cache_pre_ops(start, size, DMA_FROM_DEVICE); |
| } |
| |
| static inline void msm_dma_nc_post(void) |
| { |
| dmb(); |
| } |
| |
| static inline void msm_dma_fromdevice_wt_post(void *start, size_t size) |
| { |
| #warning this |
| // dma_cache_post_ops(start, size, DMA_FROM_DEVICE); |
| } |
| |
| static inline void msm_dma_todevice_wb_post(void *start, size_t size) |
| { |
| #warning this |
| // dma_cache_post_ops(start, size, DMA_TO_DEVICE); |
| } |
| |
| static inline void msm_dma_fromdevice_wb_post(void *start, size_t size) |
| { |
| #warning this |
| // dma_cache_post_ops(start, size, DMA_FROM_DEVICE); |
| } |
| |
| /* |
| * Do the write barriers required to guarantee data is committed to RAM |
| * (from CPU cache or internal buffers) before a DMA operation starts. |
| * NOTE: As currently implemented, the data between |
| * the end of one row and the start of the next is |
| * included in the address range rather than |
| * doing multiple calls for each row. |
| */ |
| static void msm_fb_ensure_memory_coherency_before_dma(struct fb_info *info, |
| struct mdp_blit_req *req_list, |
| int req_list_count) |
| { |
| #ifdef CONFIG_ARCH_QSD8X50 |
| int i; |
| |
| /* |
| * Normally, do the requested barriers for each address |
| * range that corresponds to a rectangle. |
| * |
| * But if at least one write barrier is requested for data |
| * going to or from the device but no address range is |
| * needed for that barrier, then do the barrier, but do it |
| * only once, no matter how many requests there are. |
| */ |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| switch (mfd->mdp_fb_page_protection) { |
| default: |
| case MDP_FB_PAGE_PROTECTION_NONCACHED: |
| case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: |
| /* |
| * The following barrier is only done at most once, |
| * since further calls would be redundant. |
| */ |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags |
| & MDP_NO_DMA_BARRIER_START)) { |
| msm_dma_nc_pre(); |
| break; |
| } |
| } |
| break; |
| |
| case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: |
| /* |
| * The following barrier is only done at most once, |
| * since further calls would be redundant. |
| */ |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags |
| & MDP_NO_DMA_BARRIER_START)) { |
| msm_dma_wt_pre(); |
| break; |
| } |
| } |
| break; |
| |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags & |
| MDP_NO_DMA_BARRIER_START)) { |
| |
| msm_fb_dma_barrier_for_rect(info, |
| &(req_list[i].src), |
| &(req_list[i].src_rect), |
| msm_dma_todevice_wb_pre |
| ); |
| |
| msm_fb_dma_barrier_for_rect(info, |
| &(req_list[i].dst), |
| &(req_list[i].dst_rect), |
| msm_dma_todevice_wb_pre |
| ); |
| } |
| } |
| break; |
| } |
| #else |
| dmb(); |
| #endif |
| } |
| |
| |
| /* |
| * Do the write barriers required to guarantee data will be re-read from RAM by |
| * the CPU after a DMA operation ends. |
| * NOTE: As currently implemented, the data between |
| * the end of one row and the start of the next is |
| * included in the address range rather than |
| * doing multiple calls for each row. |
| */ |
| static void msm_fb_ensure_memory_coherency_after_dma(struct fb_info *info, |
| struct mdp_blit_req *req_list, |
| int req_list_count) |
| { |
| #ifdef CONFIG_ARCH_QSD8X50 |
| int i; |
| |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| switch (mfd->mdp_fb_page_protection) { |
| default: |
| case MDP_FB_PAGE_PROTECTION_NONCACHED: |
| case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: |
| /* |
| * The following barrier is only done at most once, |
| * since further calls would be redundant. |
| */ |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags |
| & MDP_NO_DMA_BARRIER_END)) { |
| msm_dma_nc_post(); |
| break; |
| } |
| } |
| break; |
| |
| case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags & |
| MDP_NO_DMA_BARRIER_END)) { |
| |
| msm_fb_dma_barrier_for_rect(info, |
| &(req_list[i].dst), |
| &(req_list[i].dst_rect), |
| msm_dma_fromdevice_wt_post |
| ); |
| } |
| } |
| break; |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags & |
| MDP_NO_DMA_BARRIER_END)) { |
| |
| msm_fb_dma_barrier_for_rect(info, |
| &(req_list[i].dst), |
| &(req_list[i].dst_rect), |
| msm_dma_fromdevice_wb_post |
| ); |
| } |
| } |
| break; |
| } |
| #else |
| dmb(); |
| #endif |
| } |
| |
| #ifdef CONFIG_MDP_PPP_ASYNC_OP |
| void msm_fb_ensure_mem_coherency_after_dma(struct fb_info *info, |
| struct mdp_blit_req *req_list, int req_list_count) |
| { |
| BUG_ON(!info); |
| |
| /* |
| * Ensure that CPU cache and other internal CPU state is |
| * updated to reflect any change in memory modified by MDP blit |
| * DMA. |
| */ |
| msm_fb_ensure_memory_coherency_after_dma(info, |
| req_list, req_list_count); |
| } |
| |
| static int msmfb_async_blit(struct fb_info *info, void __user *p) |
| { |
| /* |
| * CAUTION: The names of the struct types intentionally *DON'T* match |
| * the names of the variables declared -- they appear to be swapped. |
| * Read the code carefully and you should see that the variable names |
| * make sense. |
| */ |
| const int MAX_LIST_WINDOW = 16; |
| struct mdp_blit_req req_list[MAX_LIST_WINDOW]; |
| struct mdp_blit_req_list req_list_header; |
| |
| int count, i, req_list_count; |
| |
| /* Get the count size for the total BLIT request. */ |
| if (copy_from_user(&req_list_header, p, sizeof(req_list_header))) |
| return -EFAULT; |
| p += sizeof(req_list_header); |
| count = req_list_header.count; |
| while (count > 0) { |
| /* |
| * Access the requests through a narrow window to decrease copy |
| * overhead and make larger requests accessible to the |
| * coherency management code. |
| * NOTE: The window size is intended to be larger than the |
| * typical request size, but not require more than 2 |
| * kbytes of stack storage. |
| */ |
| req_list_count = count; |
| if (req_list_count > MAX_LIST_WINDOW) |
| req_list_count = MAX_LIST_WINDOW; |
| if (copy_from_user(&req_list, p, |
| sizeof(struct mdp_blit_req)*req_list_count)) |
| return -EFAULT; |
| |
| /* |
| * Ensure that any data CPU may have previously written to |
| * internal state (but not yet committed to memory) is |
| * guaranteed to be committed to memory now. |
| */ |
| msm_fb_ensure_memory_coherency_before_dma(info, |
| req_list, req_list_count); |
| |
| /* |
| * Do the blit DMA, if required -- returning early only if |
| * there is a failure. |
| */ |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags & MDP_NO_BLIT)) { |
| int ret = 0; |
| struct mdp_ppp_djob *job = NULL; |
| |
| if (unlikely(req_list[i].src_rect.h == 0 || |
| req_list[i].src_rect.w == 0)) { |
| MSM_FB_ERR("mpd_ppp: " |
| "src img of zero size!\n"); |
| return -EINVAL; |
| } |
| |
| if (unlikely(req_list[i].dst_rect.h == 0 || |
| req_list[i].dst_rect.w == 0)) |
| continue; |
| |
| /* create a new display job */ |
| job = mdp_ppp_new_djob(); |
| if (unlikely(!job)) |
| return -ENOMEM; |
| |
| job->info = info; |
| memcpy(&job->req, &req_list[i], |
| sizeof(struct mdp_blit_req)); |
| |
| /* Do the actual blit. */ |
| ret = mdp_ppp_blit(info, &job->req, |
| &job->p_src_file, &job->p_dst_file); |
| |
| /* |
| * Note that early returns don't guarantee |
| * memory coherency. |
| */ |
| if (ret || mdp_ppp_get_ret_code()) { |
| mdp_ppp_clear_curr_djob(); |
| return ret; |
| } |
| } |
| } |
| |
| /* Go to next window of requests. */ |
| count -= req_list_count; |
| p += sizeof(struct mdp_blit_req)*req_list_count; |
| } |
| return 0; |
| } |
| #else |
| |
| /* |
| * NOTE: The userspace issues blit operations in a sequence, the sequence |
| * start with a operation marked START and ends in an operation marked |
| * END. It is guranteed by the userspace that all the blit operations |
| * between START and END are only within the regions of areas designated |
| * by the START and END operations and that the userspace doesnt modify |
| * those areas. Hence it would be enough to perform barrier/cache operations |
| * only on the START and END operations. |
| */ |
| static int msmfb_blit(struct fb_info *info, void __user *p) |
| { |
| /* |
| * CAUTION: The names of the struct types intentionally *DON'T* match |
| * the names of the variables declared -- they appear to be swapped. |
| * Read the code carefully and you should see that the variable names |
| * make sense. |
| */ |
| const int MAX_LIST_WINDOW = 16; |
| struct mdp_blit_req req_list[MAX_LIST_WINDOW]; |
| struct mdp_blit_req_list req_list_header; |
| |
| int count, i, req_list_count; |
| |
| /* Get the count size for the total BLIT request. */ |
| if (copy_from_user(&req_list_header, p, sizeof(req_list_header))) |
| return -EFAULT; |
| p += sizeof(req_list_header); |
| count = req_list_header.count; |
| while (count > 0) { |
| /* |
| * Access the requests through a narrow window to decrease copy |
| * overhead and make larger requests accessible to the |
| * coherency management code. |
| * NOTE: The window size is intended to be larger than the |
| * typical request size, but not require more than 2 |
| * kbytes of stack storage. |
| */ |
| req_list_count = count; |
| if (req_list_count > MAX_LIST_WINDOW) |
| req_list_count = MAX_LIST_WINDOW; |
| if (copy_from_user(&req_list, p, |
| sizeof(struct mdp_blit_req)*req_list_count)) |
| return -EFAULT; |
| |
| /* |
| * Ensure that any data CPU may have previously written to |
| * internal state (but not yet committed to memory) is |
| * guaranteed to be committed to memory now. |
| */ |
| msm_fb_ensure_memory_coherency_before_dma(info, |
| req_list, req_list_count); |
| |
| /* |
| * Do the blit DMA, if required -- returning early only if |
| * there is a failure. |
| */ |
| for (i = 0; i < req_list_count; i++) { |
| if (!(req_list[i].flags & MDP_NO_BLIT)) { |
| /* Do the actual blit. */ |
| int ret = mdp_blit(info, &(req_list[i])); |
| |
| /* |
| * Note that early returns don't guarantee |
| * memory coherency. |
| */ |
| if (ret) |
| return ret; |
| } |
| } |
| |
| /* |
| * Ensure that CPU cache and other internal CPU state is |
| * updated to reflect any change in memory modified by MDP blit |
| * DMA. |
| */ |
| msm_fb_ensure_memory_coherency_after_dma(info, |
| req_list, |
| req_list_count); |
| |
| /* Go to next window of requests. */ |
| count -= req_list_count; |
| p += sizeof(struct mdp_blit_req)*req_list_count; |
| } |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_FB_MSM_OVERLAY |
| static int msmfb_overlay_get(struct fb_info *info, void __user *p) |
| { |
| struct mdp_overlay req; |
| int ret; |
| |
| if (copy_from_user(&req, p, sizeof(req))) |
| return -EFAULT; |
| |
| ret = mdp4_overlay_get(info, &req); |
| if (ret) { |
| printk(KERN_ERR "%s: ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| if (copy_to_user(p, &req, sizeof(req))) { |
| printk(KERN_ERR "%s: copy2user failed \n", |
| __func__); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int msmfb_overlay_set(struct fb_info *info, void __user *p) |
| { |
| struct mdp_overlay req; |
| int ret; |
| |
| if (copy_from_user(&req, p, sizeof(req))) |
| return -EFAULT; |
| |
| ret = mdp4_overlay_set(info, &req); |
| if (ret) { |
| printk(KERN_ERR "%s:ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| |
| if (copy_to_user(p, &req, sizeof(req))) { |
| printk(KERN_ERR "%s: copy2user failed \n", |
| __func__); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int msmfb_overlay_unset(struct fb_info *info, unsigned long *argp) |
| { |
| int ret, ndx; |
| |
| ret = copy_from_user(&ndx, argp, sizeof(ndx)); |
| if (ret) { |
| printk(KERN_ERR "%s:msmfb_overlay_unset ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| |
| return mdp4_overlay_unset(info, ndx); |
| } |
| |
| static int msmfb_overlay_play(struct fb_info *info, unsigned long *argp) |
| { |
| int ret; |
| struct msmfb_overlay_data req; |
| struct file *p_src_file = 0; |
| |
| ret = copy_from_user(&req, argp, sizeof(req)); |
| if (ret) { |
| printk(KERN_ERR "%s:msmfb_overlay_play ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| |
| ret = mdp4_overlay_play(info, &req, &p_src_file); |
| |
| if (p_src_file) |
| put_pmem_file(p_src_file); |
| |
| return ret; |
| } |
| |
| #endif |
| |
| DECLARE_MUTEX(msm_fb_ioctl_ppp_sem); |
| DEFINE_MUTEX(msm_fb_ioctl_lut_sem); |
| DEFINE_MUTEX(msm_fb_ioctl_hist_sem); |
| |
| /* Set color conversion matrix from user space */ |
| |
| #ifndef CONFIG_FB_MSM_MDP40 |
| static void msmfb_set_color_conv(struct mdp_ccs *p) |
| { |
| int i; |
| |
| if (p->direction == MDP_CCS_RGB2YUV) { |
| /* MDP cmd block enable */ |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); |
| |
| /* RGB->YUV primary forward matrix */ |
| for (i = 0; i < MDP_CCS_SIZE; i++) |
| writel(p->ccs[i], MDP_CSC_PFMVn(i)); |
| |
| #ifdef CONFIG_FB_MSM_MDP31 |
| for (i = 0; i < MDP_BV_SIZE; i++) |
| writel(p->bv[i], MDP_CSC_POST_BV2n(i)); |
| #endif |
| |
| /* MDP cmd block disable */ |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); |
| } else { |
| /* MDP cmd block enable */ |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); |
| |
| /* YUV->RGB primary reverse matrix */ |
| for (i = 0; i < MDP_CCS_SIZE; i++) |
| writel(p->ccs[i], MDP_CSC_PRMVn(i)); |
| for (i = 0; i < MDP_BV_SIZE; i++) |
| writel(p->bv[i], MDP_CSC_PRE_BV1n(i)); |
| |
| /* MDP cmd block disable */ |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); |
| } |
| } |
| #endif |
| |
| |
| static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; |
| void __user *argp = (void __user *)arg; |
| struct fb_cursor cursor; |
| struct fb_cmap cmap; |
| struct mdp_histogram hist; |
| #ifndef CONFIG_FB_MSM_MDP40 |
| struct mdp_ccs ccs_matrix; |
| #endif |
| struct mdp_page_protection fb_page_protection; |
| int ret = 0; |
| |
| if (!mfd->op_enable) |
| return -EPERM; |
| |
| switch (cmd) { |
| #ifdef CONFIG_FB_MSM_OVERLAY |
| case MSMFB_OVERLAY_GET: |
| down(&msm_fb_ioctl_ppp_sem); |
| ret = msmfb_overlay_get(info, argp); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| case MSMFB_OVERLAY_SET: |
| down(&msm_fb_ioctl_ppp_sem); |
| ret = msmfb_overlay_set(info, argp); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| case MSMFB_OVERLAY_UNSET: |
| down(&msm_fb_ioctl_ppp_sem); |
| ret = msmfb_overlay_unset(info, argp); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| case MSMFB_OVERLAY_PLAY: |
| down(&msm_fb_ioctl_ppp_sem); |
| ret = msmfb_overlay_play(info, argp); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| #endif |
| case MSMFB_BLIT: |
| down(&msm_fb_ioctl_ppp_sem); |
| #ifdef CONFIG_MDP_PPP_ASYNC_OP |
| ret = msmfb_async_blit(info, argp); |
| mdp_ppp_wait(); /* Wait for all blits to be finished. */ |
| #else |
| ret = msmfb_blit(info, argp); |
| #endif |
| up(&msm_fb_ioctl_ppp_sem); |
| |
| break; |
| |
| /* Ioctl for setting ccs matrix from user space */ |
| case MSMFB_SET_CCS_MATRIX: |
| #ifndef CONFIG_FB_MSM_MDP40 |
| ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix)); |
| if (ret) { |
| printk(KERN_ERR |
| "%s:MSMFB_SET_CCS_MATRIX ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| |
| down(&msm_fb_ioctl_ppp_sem); |
| if (ccs_matrix.direction == MDP_CCS_RGB2YUV) |
| mdp_ccs_rgb2yuv = ccs_matrix; |
| else |
| mdp_ccs_yuv2rgb = ccs_matrix; |
| |
| msmfb_set_color_conv(&ccs_matrix) ; |
| up(&msm_fb_ioctl_ppp_sem); |
| #else |
| ret = -EINVAL; |
| #endif |
| |
| break; |
| |
| /* Ioctl for getting ccs matrix to user space */ |
| case MSMFB_GET_CCS_MATRIX: |
| #ifndef CONFIG_FB_MSM_MDP40 |
| ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix)) ; |
| if (ret) { |
| printk(KERN_ERR |
| "%s:MSMFB_GET_CCS_MATRIX ioctl failed \n", |
| __func__); |
| return ret; |
| } |
| |
| down(&msm_fb_ioctl_ppp_sem); |
| if (ccs_matrix.direction == MDP_CCS_RGB2YUV) |
| ccs_matrix = mdp_ccs_rgb2yuv; |
| else |
| ccs_matrix = mdp_ccs_yuv2rgb; |
| |
| ret = copy_to_user(argp, &ccs_matrix, sizeof(ccs_matrix)); |
| |
| if (ret) { |
| printk(KERN_ERR |
| "%s:MSMFB_GET_CCS_MATRIX ioctl failed \n", |
| __func__); |
| return ret ; |
| } |
| up(&msm_fb_ioctl_ppp_sem); |
| #else |
| ret = -EINVAL; |
| #endif |
| |
| break; |
| |
| #ifdef CONFIG_MDP_PPP_ASYNC_OP |
| case MSMFB_ASYNC_BLIT: |
| down(&msm_fb_ioctl_ppp_sem); |
| ret = msmfb_async_blit(info, argp); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| |
| case MSMFB_BLIT_FLUSH: |
| down(&msm_fb_ioctl_ppp_sem); |
| mdp_ppp_wait(); |
| up(&msm_fb_ioctl_ppp_sem); |
| break; |
| #endif |
| |
| case MSMFB_GRP_DISP: |
| #ifdef CONFIG_FB_MSM_MDP22 |
| { |
| unsigned long grp_id; |
| |
| ret = copy_from_user(&grp_id, argp, sizeof(grp_id)); |
| if (ret) |
| return ret; |
| |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); |
| writel(grp_id, MDP_FULL_BYPASS_WORD43); |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, |
| FALSE); |
| break; |
| } |
| #else |
| return -EFAULT; |
| #endif |
| case MSMFB_SUSPEND_SW_REFRESHER: |
| if (!mfd->panel_power_on) |
| return -EPERM; |
| |
| mfd->sw_refreshing_enable = FALSE; |
| ret = msm_fb_stop_sw_refresher(mfd); |
| break; |
| |
| case MSMFB_RESUME_SW_REFRESHER: |
| if (!mfd->panel_power_on) |
| return -EPERM; |
| |
| mfd->sw_refreshing_enable = TRUE; |
| ret = msm_fb_resume_sw_refresher(mfd); |
| break; |
| |
| case MSMFB_CURSOR: |
| ret = copy_from_user(&cursor, argp, sizeof(cursor)); |
| if (ret) |
| return ret; |
| |
| ret = msm_fb_cursor(info, &cursor); |
| break; |
| |
| case MSMFB_SET_LUT: |
| ret = copy_from_user(&cmap, argp, sizeof(cmap)); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&msm_fb_ioctl_lut_sem); |
| ret = msm_fb_set_lut(&cmap, info); |
| mutex_unlock(&msm_fb_ioctl_lut_sem); |
| break; |
| |
| case MSMFB_HISTOGRAM: |
| if (!mfd->do_histogram) |
| return -ENODEV; |
| |
| ret = copy_from_user(&hist, argp, sizeof(hist)); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&msm_fb_ioctl_hist_sem); |
| ret = mfd->do_histogram(info, &hist); |
| mutex_unlock(&msm_fb_ioctl_hist_sem); |
| break; |
| |
| case MSMFB_GET_PAGE_PROTECTION: |
| fb_page_protection.page_protection |
| = mfd->mdp_fb_page_protection; |
| ret = copy_to_user(argp, &fb_page_protection, |
| sizeof(fb_page_protection)); |
| if (ret) |
| return ret; |
| break; |
| |
| case MSMFB_SET_PAGE_PROTECTION: |
| #ifdef CONFIG_ARCH_QSD8X50 |
| ret = copy_from_user(&fb_page_protection, argp, |
| sizeof(fb_page_protection)); |
| if (ret) |
| return ret; |
| |
| /* Validate the proposed page protection settings. */ |
| switch (fb_page_protection.page_protection) { |
| case MDP_FB_PAGE_PROTECTION_NONCACHED: |
| case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: |
| case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: |
| /* Write-back cache (read allocate) */ |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: |
| /* Write-back cache (write allocate) */ |
| case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: |
| mfd->mdp_fb_page_protection = |
| fb_page_protection.page_protection; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| #else |
| /* |
| * Don't allow caching until 7k DMA cache operations are |
| * available. |
| */ |
| ret = -EINVAL; |
| #endif |
| break; |
| |
| default: |
| MSM_FB_INFO("MDP: unknown ioctl (cmd=%d) received!\n", cmd); |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int msm_fb_register_driver(void) |
| { |
| return platform_driver_register(&msm_fb_driver); |
| } |
| |
| void msm_fb_add_device(struct platform_device *pdev) |
| { |
| struct msm_fb_panel_data *pdata; |
| struct platform_device *this_dev = NULL; |
| struct fb_info *fbi; |
| struct msm_fb_data_type *mfd = NULL; |
| u32 type, id, fb_num; |
| |
| if (!pdev) |
| return; |
| id = pdev->id; |
| |
| pdata = pdev->dev.platform_data; |
| if (!pdata) |
| return; |
| type = pdata->panel_info.type; |
| fb_num = pdata->panel_info.fb_num; |
| |
| if (fb_num <= 0) |
| return; |
| |
| if (fbi_list_index >= MAX_FBI_LIST) { |
| printk(KERN_ERR "msm_fb: no more framebuffer info list!\n"); |
| return; |
| } |
| /* |
| * alloc panel device data |
| */ |
| this_dev = msm_fb_device_alloc(pdata, type, id); |
| |
| if (!this_dev) { |
| printk(KERN_ERR |
| "%s: msm_fb_device_alloc failed!\n", __func__); |
| return; |
| } |
| |
| /* |
| * alloc framebuffer info + par data |
| */ |
| fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL); |
| if (fbi == NULL) { |
| platform_device_put(this_dev); |
| printk(KERN_ERR "msm_fb: can't alloca framebuffer info data!\n"); |
| return; |
| } |
| |
| mfd = (struct msm_fb_data_type *)fbi->par; |
| mfd->key = MFD_KEY; |
| mfd->fbi = fbi; |
| mfd->panel.type = type; |
| mfd->panel.id = id; |
| mfd->fb_page = fb_num; |
| mfd->index = fbi_list_index; |
| mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE; |
| |
| /* link to the latest pdev */ |
| mfd->pdev = this_dev; |
| |
| mfd_list[mfd_list_index++] = mfd; |
| fbi_list[fbi_list_index++] = fbi; |
| |
| /* |
| * set driver data |
| */ |
| platform_set_drvdata(this_dev, mfd); |
| |
| if (platform_device_add(this_dev)) { |
| printk(KERN_ERR "msm_fb: platform_device_add failed!\n"); |
| platform_device_put(this_dev); |
| framebuffer_release(fbi); |
| fbi_list_index--; |
| return; |
| } |
| } |
| EXPORT_SYMBOL(msm_fb_add_device); |
| |
| int __init msm_fb_init(void) |
| { |
| int rc = -ENODEV; |
| |
| if (msm_fb_register_driver()) |
| return rc; |
| |
| #ifdef MSM_FB_ENABLE_DBGFS |
| { |
| struct dentry *root; |
| |
| if ((root = msm_fb_get_debugfs_root()) != NULL) { |
| msm_fb_debugfs_file_create(root, |
| "msm_fb_msg_printing_level", |
| (u32 *) &msm_fb_msg_level); |
| msm_fb_debugfs_file_create(root, |
| "mddi_msg_printing_level", |
| (u32 *) &mddi_msg_level); |
| msm_fb_debugfs_file_create(root, "msm_fb_debug_enabled", |
| (u32 *) &msm_fb_debug_enabled); |
| } |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| module_init(msm_fb_init); |