| /**************************************************************************** |
| * |
| * Copyright (c) 1999-2008 Broadcom Corporation |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2, available |
| * at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html (the "GPL"). |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a |
| * license other than the GPL, without Broadcom's express prior written |
| * consent. |
| * |
| ****************************************************************************/ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/time.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/acct.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/mm.h> |
| #include <linux/fb.h> |
| #include <linux/init.h> |
| #include <linux/kthread.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioport.h> |
| #include <linux/platform_device.h> |
| #include <linux/uaccess.h> |
| #include <linux/proc_fs.h> |
| #include <linux/ipc/ipc.h> |
| #include <linux/clk.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/vt_kern.h> |
| #include <linux/gpio.h> |
| #include <video/kona_fb.h> |
| #include <mach/io.h> |
| #include <linux/delay.h> |
| #include <mach/memory.h> |
| #include <mach/io_map.h> |
| #include <plat/reg_axipv.h> |
| #include <asm/io.h> |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| #include <linux/fb_fps.h> |
| #endif |
| #include <linux/clk.h> |
| #include <plat/pi_mgr.h> |
| #include <linux/broadcom/mobcom_types.h> |
| #include <linux/reboot.h> |
| #include <linux/kdebug.h> |
| #include <linux/notifier.h> |
| #include "kona_fb.h" |
| #include "lcd/display_drv.h" |
| #include "lcd/lcd.h" |
| |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #include <linux/of_gpio.h> |
| #include <linux/wakelock.h> |
| |
| #ifdef CONFIG_DEBUG_FS |
| #include <linux/debugfs.h> |
| #include <linux/kobject.h> |
| #include <linux/seq_file.h> |
| #include <linux/ctype.h> |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| #include <video/kona_fb_image_dump.h> |
| #include "lcd/dump_start_img.h" |
| #include "lcd/dump_end_img.h" |
| #include "lcd/ap_start_dump_img.h" |
| #include "lcd/cp_start_dump_img.h" |
| #include "lcd/ap_ramdump_start_img.h" |
| #include "lcd/cp_ramdump_start_img.h" |
| #endif |
| #ifdef CONFIG_IOMMU_API |
| #include <linux/iommu.h> |
| #include <plat/bcm_iommu.h> |
| #endif |
| |
| #ifdef CONFIG_DEBUG_FS |
| #define DBGFS_MAX_BUFF_SIZE 200 |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| /*#define KONA_FB_DEBUG */ |
| /*#define PARTIAL_UPDATE_SUPPORT */ |
| |
| #define KONA_IOCTL_SET_BUFFER_AND_UPDATE _IO('F', 0x80) |
| #define KONA_IOCTL_GET_FB_IOVA _IOR('F', 0x81, u32) |
| |
| #define SUSPEND_LINK_DELAY_MS 100 |
| |
| static struct pi_mgr_qos_node g_mm_qos_node; |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| static DEFINE_SPINLOCK(g_fb_crash_spin_lock); |
| #endif |
| |
| enum lcd_state { |
| KONA_FB_UNBLANK, |
| KONA_FB_BLANK, |
| }; |
| |
| enum link_ctrl { |
| RESUME_LINK, |
| SUSPEND_LINK, |
| }; |
| |
| struct kona_fb { |
| spinlock_t lock; |
| struct mutex update_sem; |
| struct completion prev_buf_done_sem; |
| atomic_t buff_idx; |
| atomic_t is_fb_registered; |
| atomic_t is_graphics_started; |
| int rotate; |
| int is_display_found; |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| struct fb_fps_info *fps_info; |
| #endif |
| struct fb_info fb; |
| u32 cmap[16]; |
| DISPDRV_T *display_ops; |
| DISPDRV_INFO_T *display_info; |
| DISPDRV_HANDLE_T display_hdl; |
| struct pi_mgr_dfs_node dfs_node; |
| int g_stop_drawing; |
| struct proc_dir_entry *proc_entry; |
| enum lcd_state blank_state; |
| void *buff0; |
| void *buff1; |
| atomic_t force_update; |
| struct notifier_block reboot_nb; |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| struct notifier_block die_nb; |
| #endif |
| struct work_struct vsync_smart; |
| |
| size_t framesize_alloc; |
| struct iommu_domain *direct_domain; |
| struct sg_table *iovmm_table; |
| struct kona_fb_platform_data *fb_data; |
| struct platform_device *pdev; |
| |
| /* ESD check routine */ |
| struct workqueue_struct *esd_check_wq; |
| struct delayed_work esd_check_work; |
| struct completion tectl_gpio_done_sem; |
| int esd_failure_cnt; |
| /* user count */ |
| unsigned short open_count; |
| |
| bool link_suspended; |
| bool suspend_link; |
| struct delayed_work link_work; |
| struct wake_lock wlock; |
| |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *dbgfs_dir; |
| #endif |
| }; |
| |
| static struct completion vsync_event; |
| static struct kona_fb *g_kona_fb; |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| static void kona_fb_unpack_888rle(void *, void *, uint32_t, uint32_t, uint32_t); |
| static void kona_fb_unpack_565rle(void *, void *, uint32_t, uint32_t, uint32_t); |
| #endif |
| |
| static int kona_fb_reboot_cb(struct notifier_block *, unsigned long, void *); |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| static int kona_fb_die_cb(struct notifier_block *, unsigned long, void *); |
| #endif |
| |
| static int lcd_boot_mode(char *); |
| static int lcd_panel_setup(char *); |
| static int kona_fb_blank(int blank_mode, struct fb_info *info); |
| |
| static int need_page_alignment = 1; |
| static char g_disp_str[DISPDRV_NAME_SZ]; |
| int g_display_enabled; |
| |
| #ifdef KONA_FB_DEBUG |
| #define KONA_PROF_N_RECORDS 50 |
| static volatile struct { |
| struct timeval curr_time; |
| struct timeval prev_time; |
| int is_late; |
| int use_te; |
| } kona_fb_profile[KONA_PROF_N_RECORDS]; |
| |
| static volatile u32 kona_fb_profile_cnt; |
| |
| void kona_fb_profile_record(struct timeval prev_timeval, |
| struct timeval curr_timeval, int is_too_late, int do_vsync) |
| { |
| kona_fb_profile[kona_fb_profile_cnt].curr_time = curr_timeval; |
| kona_fb_profile[kona_fb_profile_cnt].prev_time = prev_timeval; |
| kona_fb_profile[kona_fb_profile_cnt].is_late = is_too_late; |
| kona_fb_profile[kona_fb_profile_cnt].use_te = do_vsync; |
| kona_fb_profile_cnt = (kona_fb_profile_cnt+1) % KONA_PROF_N_RECORDS; |
| } |
| |
| static int |
| proc_write_fb_test(struct file *file, const char __user *buffer, |
| size_t count, loff_t *data) |
| { |
| int len, cnt, i, num; |
| char value[20]; |
| |
| if (count > 19) |
| len = 19; |
| else |
| len = count; |
| |
| if (copy_from_user(value, buffer, len)) |
| return -EFAULT; |
| |
| value[len] = '\0'; |
| |
| num = simple_strtoul(value, NULL, 0); |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| if (num == 1) |
| kona_display_crash_image(0); |
| else |
| kona_display_crash_image(1); |
| #endif |
| |
| i = kona_fb_profile_cnt; |
| for (cnt = 0; cnt < KONA_PROF_N_RECORDS; cnt++) { |
| if (i != 0) |
| printk(KERN_ERR "%8lu,%8lu,%8lu,%8lu,%8lu,%d, %d", |
| kona_fb_profile[i].prev_time.tv_sec, |
| kona_fb_profile[i].prev_time.tv_usec, |
| kona_fb_profile[i].curr_time.tv_sec, |
| kona_fb_profile[i].curr_time.tv_usec, |
| (kona_fb_profile[i].prev_time.tv_sec - |
| kona_fb_profile[i-1].curr_time.tv_sec) * 1000000 + |
| (kona_fb_profile[i].prev_time.tv_usec - |
| kona_fb_profile[i-1].curr_time.tv_usec), |
| kona_fb_profile[i].is_late, |
| kona_fb_profile[i].use_te); |
| i = (i + 1) % KONA_PROF_N_RECORDS; |
| } |
| |
| return len; |
| } |
| #else |
| #define kona_fb_profile_record(prev_timeval, curr_timeval, is_too_late, \ |
| do_vsync) do { } while (0) |
| #define proc_write_fb_test NULL |
| #endif /* KONA_FB_DEBUG */ |
| |
| static inline u32 convert_bitfield(int val, struct fb_bitfield *bf) |
| { |
| unsigned int mask = (1 << bf->length) - 1; |
| |
| return (val >> (16 - bf->length) & mask) << bf->offset; |
| } |
| |
| static int |
| kona_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, |
| unsigned int blue, unsigned int transp, struct fb_info *info) |
| { |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| |
| konafb_debug("kona regno = %d r=%d g=%d b=%d\n", regno, red, green, |
| blue); |
| |
| if (regno < 16) { |
| fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) | |
| convert_bitfield(blue, &fb->fb.var.blue) | |
| convert_bitfield(green, &fb->fb.var.green) | |
| convert_bitfield(red, &fb->fb.var.red); |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
| |
| static int kona_fb_check_var(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| konafb_debug("kona %s\n", __func__); |
| |
| if ((var->rotate & 1) != (info->var.rotate & 1)) { |
| if ((var->xres != info->var.yres) || |
| (var->yres != info->var.xres) || |
| (var->xres_virtual != info->var.yres) || |
| (var->yres_virtual > info->var.xres * 2) || |
| (var->yres_virtual < info->var.xres)) { |
| konafb_error("fb_check_var_failed\n"); |
| return -EINVAL; |
| } |
| } else { |
| if ((var->xres != info->var.xres) || |
| (var->yres != info->var.yres) || |
| (var->xres_virtual != info->var.xres) || |
| (var->yres_virtual > info->var.yres * 2) || |
| (var->yres_virtual < info->var.yres)) { |
| konafb_error("fb_check_var_failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int kona_fb_set_par(struct fb_info *info) |
| { |
| #if 0 |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| |
| konafb_debug("kona %s\n", __func__); |
| |
| if (fb->rotation != fb->fb.var.rotate) { |
| konafb_warning("Rotation is not supported yet !\n"); |
| return -EINVAL; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static inline void kona_clock_start(struct kona_fb *fb) |
| { |
| fb->display_ops->start(fb->display_hdl, &fb->dfs_node); |
| } |
| |
| static inline void kona_clock_stop(struct kona_fb *fb) |
| { |
| fb->display_ops->stop(fb->display_hdl, &fb->dfs_node); |
| } |
| |
| static void fb_suspend_link_work(struct work_struct *work) |
| { |
| struct delayed_work *dw = container_of(work, struct delayed_work, work); |
| struct kona_fb *fb = container_of(dw, struct kona_fb, link_work); |
| |
| if (mutex_trylock(&fb->update_sem)) { |
| /* We might have exited special mode */ |
| if (!fb->suspend_link) { |
| konafb_info("do not suspend\n"); |
| } else if (fb->link_suspended) { |
| konafb_info("link already suspended\n"); |
| } else { |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| fb->display_ops->suspend_link(fb->display_hdl); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| pi_mgr_qos_request_update(&g_mm_qos_node, |
| PI_MGR_QOS_DEFAULT_VALUE); |
| fb->link_suspended = true; |
| } |
| mutex_unlock(&fb->update_sem); |
| } else { |
| konafb_info("mutex locked! link not closed\n"); |
| } |
| wake_unlock(&fb->wlock); |
| } |
| |
| static void link_control(struct kona_fb *fb, enum link_ctrl link_ctrl) |
| { |
| if (link_ctrl == RESUME_LINK) { |
| if (!fb->link_suspended) { |
| konafb_warning("link already active\n"); |
| return; |
| } |
| cancel_delayed_work_sync(&fb->link_work); |
| wake_unlock(&fb->wlock); |
| pi_mgr_qos_request_update(&g_mm_qos_node, 10); |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| fb->display_ops->resume_link(fb->display_hdl); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| fb->link_suspended = false; |
| } else { |
| cancel_delayed_work_sync(&fb->link_work); |
| wake_lock(&fb->wlock); |
| schedule_delayed_work(&fb->link_work, |
| msecs_to_jiffies(SUSPEND_LINK_DELAY_MS)); |
| } |
| } |
| |
| #ifdef CONFIG_IOMMU_API |
| static int kona_fb_direct_map(struct kona_fb *fb, size_t framesize_alloc, |
| dma_addr_t phys_fbbase, dma_addr_t dma_addr) |
| { |
| struct iommu_domain *domain = NULL; |
| int ret = -ENXIO; |
| struct kona_fb_platform_data *fb_data = fb->fb_data; |
| struct platform_device *pdev = fb->pdev; |
| |
| /* 1-to-1 mapping */ |
| domain = iommu_domain_alloc(&platform_bus_type); |
| if (NULL == domain) |
| goto fail_alloc; |
| |
| if (iommu_attach_device(domain, &pdev->dev)) { |
| ret = -EINVAL; |
| pr_err("%s Attaching dev(%p) to iommu dev(%p) failed\n", |
| "framebuffer", &pdev->dev, |
| &fb_data->pdev_iommu->dev); |
| goto fail_attach; |
| } |
| |
| if (iommu_map(domain, dma_addr, phys_fbbase, framesize_alloc, |
| 0)) { |
| ret = -EINVAL; |
| pr_err("%s iommu mapping domain(%p) da(%#x) to pa(%#x) size(%#08x) failed\n", |
| "framebuffer", domain, dma_addr, |
| phys_fbbase, framesize_alloc); |
| goto fail_map; |
| } |
| |
| fb->direct_domain = domain; |
| pr_info("%s succ\n", __func__); |
| return 0; |
| |
| fail_map: |
| iommu_detach_device(domain, &pdev->dev); |
| fail_attach: |
| iommu_domain_free(domain); |
| fail_alloc: |
| pr_err("%s failed ret = %d\n", __func__, ret); |
| return ret; |
| } |
| |
| static int kona_fb_direct_unmap(struct kona_fb *fb, |
| size_t framesize_alloc, dma_addr_t dma_addr) |
| { |
| struct iommu_domain *domain = fb->direct_domain; |
| struct platform_device *pdev = fb->pdev; |
| |
| if (NULL == domain) { |
| pr_err("%s fail, direct_domain NULL\n", __func__); |
| return -EIO; |
| } |
| |
| iommu_unmap(domain, dma_addr, framesize_alloc); |
| |
| iommu_detach_device(domain, &pdev->dev); |
| |
| iommu_domain_free(domain); |
| |
| fb->direct_domain = NULL; |
| pr_info("%s succ\n", __func__); |
| return 0; |
| } |
| |
| #ifdef CONFIG_BCM_IOVMM |
| static int kona_fb_iovmm_map(struct kona_fb *fb, size_t framesize_alloc, |
| dma_addr_t phys_fbbase, dma_addr_t *p_dma_addr) |
| { |
| int n_pages, i; |
| struct scatterlist *sg; |
| struct sg_table *table; |
| int ret = -ENXIO; |
| struct dma_iommu_mapping *mapping = NULL; |
| struct kona_fb_platform_data *fb_data = fb->fb_data; |
| struct platform_device *pdev = fb->pdev; |
| dma_addr_t dma_addr; |
| |
| if (!fb_data->pdev_iovmm) { |
| ret = -EINVAL; |
| pr_err("%s: iovmm device not set\n", "framebuffer"); |
| goto fail; |
| } |
| mapping = platform_get_drvdata(fb_data->pdev_iovmm); |
| arm_iommu_attach_device(&pdev->dev, mapping); |
| pr_info("%s iommu-mapping(%p)\n", "framebuffer", mapping); |
| |
| table = kmalloc(sizeof(struct sg_table), GFP_KERNEL); |
| if (!table) { |
| ret = -ENOMEM; |
| pr_err("Unable to allocate fb sgtable\n"); |
| goto fail; |
| } |
| n_pages = framesize_alloc >> PAGE_SHIFT; |
| |
| i = sg_alloc_table(table, 1, GFP_KERNEL); |
| if (i) { |
| ret = -ENOMEM; |
| pr_err("sg_alloc_table failed\n"); |
| goto fail_sg_alloc; |
| } |
| for_each_sg(table->sgl, sg, table->nents, i) { |
| struct page *page = |
| phys_to_page(phys_fbbase); |
| sg_set_page(sg, page, PAGE_SIZE * n_pages, 0); |
| } |
| dma_addr = arm_iommu_map_sgt(&pdev->dev, table, 0); |
| if (dma_addr == DMA_ERROR_CODE) { |
| ret = -EINVAL; |
| pr_err("%16s: Failed iommu map da(%#x) pa(%#x) size(%#x)\n", |
| "framebuffer", dma_addr, phys_fbbase, |
| framesize_alloc); |
| goto fail_map_sgt; |
| } |
| |
| fb->iovmm_table = table; |
| *p_dma_addr = dma_addr; |
| pr_info("%s succ\n", __func__); |
| return 0; |
| |
| fail_map_sgt: |
| sg_free_table(table); |
| fail_sg_alloc: |
| kfree(table); |
| fail: |
| pr_err("%s failed ret = %d\n", __func__, ret); |
| return ret; |
| } |
| |
| static int kona_fb_iovmm_unmap(struct kona_fb *fb, |
| size_t framesize_alloc, dma_addr_t dma_addr) |
| { |
| struct platform_device *pdev = fb->pdev; |
| |
| arm_iommu_unmap(&pdev->dev, dma_addr, framesize_alloc); |
| |
| if (fb->iovmm_table) { |
| sg_free_table(fb->iovmm_table); |
| kfree(fb->iovmm_table); |
| fb->iovmm_table = NULL; |
| } |
| |
| pr_info("%s succ\n", __func__); |
| return 0; |
| } |
| #endif |
| #endif |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| static void kona_fb_unpack_565rle(void *dst, void *src, uint32_t image_size, |
| uint32_t img_w, uint32_t img_h) |
| { |
| unsigned long count, index, len, data, pos; |
| u16 *lcd_buf, *image_buf; |
| |
| image_buf = (u16 *)src; |
| lcd_buf = (u16 *)dst; |
| for (count = 0, pos = 0; count < image_size / 2; count += 2) { |
| len = image_buf[count]; |
| data = image_buf[count + 1]; |
| for (index = 0; index < len; index++) { |
| lcd_buf[pos++] = data; |
| if (pos > g_kona_fb->fb.fix.smem_len / 4) |
| printk(KERN_ERR "Wrong image size!"); |
| } |
| } |
| } |
| |
| static void kona_fb_unpack_888rle(void *dst, void *src, uint32_t image_size, |
| uint32_t img_w, uint32_t img_h) |
| { |
| unsigned long count, len, data, pos, pix_left_curr_line; |
| unsigned long x_margin, y_margin; |
| u32 *lcd_buf; |
| u16 *image_buf; |
| int dir; |
| |
| struct fb_info *fb = &g_kona_fb->fb; |
| |
| image_buf = (u16 *)src; |
| lcd_buf = (u32 *)dst; |
| x_margin = (fb->var.xres - img_w) / 2; |
| y_margin = (fb->var.yres - img_h) / 2; |
| |
| /*If rotation is enabled, move to the end of buffer*/ |
| if (g_kona_fb->rotate == FB_ROTATE_UD) { |
| pos = (fb->var.xres * fb->var.yres - 1); |
| dir = -1; |
| } else { |
| pos = 0; |
| dir = 1; |
| } |
| pix_left_curr_line = img_w; |
| pos += y_margin * fb->var.xres * dir; |
| pos += x_margin * dir; |
| for (count = 0; count < image_size; count += 8) { |
| len = *image_buf++; |
| len |= (*image_buf++) << 16; |
| data = *image_buf++; |
| data |= (*image_buf++ << 16); |
| while (len) { |
| while (pix_left_curr_line) { |
| lcd_buf[pos] = data; |
| pos += dir; |
| --len; |
| --pix_left_curr_line; |
| if (pos > fb->fix.smem_len / 8) |
| printk(KERN_ERR "Wrong image size!"); |
| if (!len) |
| break; |
| } |
| if (!pix_left_curr_line) { |
| pos += x_margin * 2 * dir; |
| pix_left_curr_line = img_w; |
| } |
| } |
| } |
| } |
| |
| void kona_display_crash_image(enum crash_dump_image_idx image_idx) |
| { |
| unsigned long flags, image_size, img_w, img_h; |
| void *image_buf; |
| static bool crash_displayed; |
| |
| if (panic_timeout == 1) |
| return; |
| |
| pr_err("%s:%d image_idx=%d\n", __func__, __LINE__, image_idx); |
| atomic_set(&g_kona_fb->force_update, 1); |
| kona_clock_start(g_kona_fb); |
| spin_lock_irqsave(&g_fb_crash_spin_lock, flags); |
| |
| switch (image_idx) { |
| case GENERIC_DUMP_START: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &dump_start_img_565[0]; |
| image_size = sizeof(dump_start_img_565); |
| } else { |
| image_buf = (void *) &dump_start_img_888[0]; |
| image_size = sizeof(dump_start_img_888); |
| } |
| img_w = dump_start_img_w; |
| img_h = dump_start_img_h; |
| break; |
| case CP_CRASH_DUMP_START: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &cp_dump_start_img_565[0]; |
| image_size = sizeof(cp_dump_start_img_565); |
| } else { |
| image_buf = (void *) &cp_dump_start_img_888[0]; |
| image_size = sizeof(cp_dump_start_img_888); |
| } |
| img_w = dump_cp_start_img_w; |
| img_h = dump_cp_start_img_h; |
| break; |
| case AP_CRASH_DUMP_START: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &ap_dump_start_img_565[0]; |
| image_size = sizeof(ap_dump_start_img_565); |
| } else { |
| image_buf = (void *) &ap_dump_start_img_888[0]; |
| image_size = sizeof(ap_dump_start_img_888); |
| } |
| img_w = dump_ap_start_img_w; |
| img_h = dump_ap_start_img_h; |
| break; |
| case GENERIC_DUMP_END: |
| case CP_CRASH_DUMP_END: |
| case AP_CRASH_DUMP_END: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &dump_end_img_565[0]; |
| image_size = sizeof(dump_end_img_565); |
| } else { |
| image_buf = (void *) &dump_end_img_888[0]; |
| image_size = sizeof(dump_end_img_888); |
| } |
| img_w = dump_end_img_w; |
| img_h = dump_end_img_h; |
| break; |
| case CP_RAM_DUMP_START: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &cp_ramdump_start_img_565[0]; |
| image_size = sizeof(cp_ramdump_start_img_565); |
| } else { |
| image_buf = (void *) &cp_ramdump_start_img_888[0]; |
| image_size = sizeof(cp_ramdump_start_img_888); |
| } |
| img_w = cp_ramdump_start_img_w; |
| img_h = cp_ramdump_start_img_h; |
| break; |
| case AP_RAM_DUMP_START: |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) { |
| image_buf = (void *) &ap_ramdump_start_img_565[0]; |
| image_size = sizeof(ap_ramdump_start_img_565); |
| } else { |
| image_buf = (void *) &ap_ramdump_start_img_888[0]; |
| image_size = sizeof(ap_ramdump_start_img_888); |
| } |
| img_w = ap_ramdump_start_img_w; |
| img_h = ap_ramdump_start_img_h; |
| break; |
| default: |
| pr_err("Invalid image index passed\n"); |
| goto err_idx; |
| } |
| |
| if (16 == g_kona_fb->fb.var.bits_per_pixel) |
| kona_fb_unpack_565rle((void *)g_kona_fb->fb.screen_base, |
| image_buf, image_size, img_w, img_h); |
| else |
| kona_fb_unpack_888rle((void *)g_kona_fb->fb.screen_base, |
| image_buf, image_size, img_w, img_h); |
| |
| /* For video mode, it is sufficient if we draw on the active buffer*/ |
| if (!g_kona_fb->display_info->vmode || (false == crash_displayed)) { |
| if (g_kona_fb->display_ops->update_no_os) |
| g_kona_fb->display_ops->update_no_os( |
| g_kona_fb->display_hdl, g_kona_fb->buff0, NULL); |
| } |
| |
| crash_displayed = true; |
| err_idx: |
| g_kona_fb->g_stop_drawing = 1; |
| spin_unlock_irqrestore(&g_fb_crash_spin_lock, flags); |
| kona_clock_stop(g_kona_fb); |
| } |
| #endif |
| |
| static void kona_display_done_cb(int status) |
| { |
| (void)status; |
| if (!g_kona_fb->display_info->vmode) |
| kona_clock_stop(g_kona_fb); |
| konafb_debug("kona_fb release called\n"); |
| complete(&g_kona_fb->prev_buf_done_sem); |
| } |
| |
| /* This function peforms the in-place rotation of the buffer */ |
| int kona_fb_rotate_buffer(void *buffer, int deg, unsigned width, |
| unsigned height, unsigned char bytes_per_pixel) |
| { |
| int i = 0; |
| unsigned int total_num_pixels = width * height; |
| unsigned short *Bottom_2Bpp; |
| unsigned short *Top_2Bpp; |
| unsigned short temp_2Bpp; |
| unsigned int *Bottom_4Bpp; |
| unsigned int *Top_4Bpp; |
| unsigned int temp_4Bpp; |
| |
| if (bytes_per_pixel == 2) { |
| /* point to the first pixel */ |
| Top_2Bpp = buffer; |
| /* point to the last pixel */ |
| Bottom_2Bpp = Top_2Bpp + (width * height) - 1; |
| |
| for (i = 0; i < total_num_pixels / 2; ++i) { |
| /* swap pixels from top-left in forward direction |
| to pixels in bottom-right in reverse direction */ |
| temp_2Bpp = *Top_2Bpp; |
| *Top_2Bpp++ = *Bottom_2Bpp; |
| *Bottom_2Bpp-- = temp_2Bpp; |
| } |
| } else { |
| /* if Bpp = 4 */ |
| /* point to the first pixel */ |
| Top_4Bpp = buffer; |
| /* point to the last pixel */ |
| Bottom_4Bpp = Top_4Bpp + (width * height) - 1; |
| |
| for (i = 0; i < total_num_pixels / 2; ++i) { |
| /* swap pixels from top-left in forward direction |
| to pixels in bottom-right in reverse direction */ |
| temp_4Bpp = *Top_4Bpp; |
| *Top_4Bpp++ = *Bottom_4Bpp; |
| *Bottom_4Bpp-- = temp_4Bpp; |
| } |
| } |
| return 0; |
| } |
| |
| static int kona_fb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| int ret = 0; |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| uint32_t buff_idx; |
| int bytes_per_pixel = 0; |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| void *dst; |
| #endif |
| DISPDRV_WIN_t region, *p_region; |
| |
| buff_idx = var->yoffset ? 1 : 0; |
| |
| if (var->rotate == FB_ROTATE_UD) { |
| bytes_per_pixel = var->bits_per_pixel / 8; |
| konafb_debug("Rotating inside kernel\n"); |
| |
| if (buff_idx == 1) { |
| if (kona_fb_rotate_buffer(fb->fb.screen_base + |
| (fb->buff1 - fb->buff0), FB_ROTATE_UD , |
| var->xres, var->yres, bytes_per_pixel)) |
| konafb_error("Unable to rotate !!\n"); |
| } else { |
| if (kona_fb_rotate_buffer(fb->fb.screen_base, |
| FB_ROTATE_UD, var->xres, var->yres, |
| bytes_per_pixel)) |
| konafb_error("Unable to rotate !!\n"); |
| } |
| } |
| konafb_debug("kona %s with buff_idx =%d\n", __func__, buff_idx); |
| |
| if (mutex_lock_killable(&fb->update_sem)) |
| return -EINTR; |
| |
| if (1 == fb->g_stop_drawing) { |
| konafb_debug( |
| "kona FB/LCd is in the early suspend state and stops drawing now!"); |
| goto skip_drawing; |
| } |
| |
| atomic_set(&fb->buff_idx, buff_idx); |
| |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| dst = (fb->fb.screen_base) + |
| (buff_idx * fb->fb.var.xres * fb->fb.var.yres * |
| (fb->fb.var.bits_per_pixel / 8)); |
| fb_fps_display(fb->fps_info, dst, 5, 2, 0); |
| #endif |
| |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| |
| if (!atomic_read(&fb->is_fb_registered)) { |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| ret = |
| fb->display_ops->update(fb->display_hdl, |
| buff_idx ? fb->buff1 : fb->buff0, |
| NULL, NULL); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| } else { |
| atomic_set(&fb->is_graphics_started, 1); |
| if (var->reserved[0] == 0x54445055) { |
| region.t = var->reserved[1] >> 16; |
| region.l = (u16) var->reserved[1]; |
| region.b = (var->reserved[2] >> 16) - 1; |
| region.r = (u16) var->reserved[2] - 1; |
| region.w = region.r - region.l + 1; |
| region.h = region.b - region.t + 1; |
| region.mode = 0; |
| p_region = ®ion; |
| } else { |
| region.t = 0; |
| region.l = 0; |
| region.b = fb->fb.var.height - 1; |
| region.r = fb->fb.var.width - 1; |
| region.w = fb->fb.var.width; |
| region.h = fb->fb.var.height; |
| region.mode = 1; |
| p_region = NULL; |
| } |
| if (!fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| kona_clock_start(fb); |
| } |
| ret = |
| fb->display_ops->update(fb->display_hdl, |
| buff_idx ? fb->buff1 : fb->buff0, |
| p_region, |
| (DISPDRV_CB_T)kona_display_done_cb); |
| |
| if (fb->display_info->vmode) { |
| konafb_debug("waiting for release of 0x%x\n", |
| buff_idx ? fb->buff0 : fb->buff1); |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| } |
| } |
| |
| if (fb->suspend_link) |
| link_control(fb, SUSPEND_LINK); |
| |
| skip_drawing: |
| mutex_unlock(&fb->update_sem); |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_MACH_BCM_FPGA_E) || defined(CONFIG_MACH_BCM_FPGA) |
| static int kona_fb_sync(struct fb_info *info) |
| { |
| pr_info("[KONA_FB]: HW Composer not enabled on Java Eve, return\n"); |
| return 0; |
| } |
| #else |
| static int kona_fb_sync(struct fb_info *info) |
| { |
| wait_for_completion_interruptible(&vsync_event); |
| return 0; |
| } |
| #endif |
| static void konafb_vsync_cb(void) |
| { |
| if (g_kona_fb && g_kona_fb->display_info->vmode) |
| complete(&vsync_event); |
| } |
| |
| static void vsync_work_smart(struct work_struct *work) |
| { |
| struct kona_fb *fb = container_of(work, struct kona_fb, |
| vsync_smart); |
| |
| complete(&vsync_event); |
| /* 16ms ~ 60HZ */ |
| usleep_range(16000, 16010); |
| schedule_work(&fb->vsync_smart); |
| } |
| |
| static ssize_t kona_fb_panel_name_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct kona_fb *fb = dev_get_drvdata(dev); |
| char const *name = fb->fb_data->name ? |
| fb->fb_data->name : "NoName"; |
| return scnprintf(buf, PAGE_SIZE, "Panel: %s\n", name); |
| } |
| |
| static ssize_t kona_fb_panel_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct kona_fb *fb = dev_get_drvdata(dev); |
| uint8_t res_da = 0; |
| uint8_t res_db = 0; |
| uint8_t res_dc = 0; |
| |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| panel_read(0xDA, &res_da, 1); |
| panel_read(0xDB, &res_db, 1); |
| panel_read(0xDC, &res_dc, 1); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| |
| return scnprintf(buf, PAGE_SIZE, "Panel ID: 0x%.2x 0x%.2x 0x%.2x\n", |
| res_da, res_db, res_dc); |
| } |
| |
| static ssize_t kona_fb_panel_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct kona_fb *fb = dev_get_drvdata(dev); |
| if (fb->display_info->special_mode_panel) |
| return scnprintf(buf, PAGE_SIZE, "%d\n", |
| fb->display_info->special_mode_on); |
| else |
| return scnprintf(buf, PAGE_SIZE, "%s\n", "Not supported"); |
| } |
| |
| static ssize_t kona_fb_panel_mode_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int ret = count; |
| struct kona_fb *fb = dev_get_drvdata(dev); |
| uint32_t val; |
| |
| if (fb->display_info->vmode) { |
| konafb_error("Only command mode supported\n"); |
| ret = -EOPNOTSUPP; |
| goto exit; |
| } |
| |
| if (!fb->display_info->special_mode_panel) { |
| konafb_error("Not supported\n"); |
| ret = -EOPNOTSUPP; |
| goto exit; |
| } |
| |
| if (sscanf(buf, "%i", &val) != 1) { |
| konafb_error("Error, buf = %s\n", buf); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| konafb_info("panel mode %i\n", val); |
| mutex_lock(&fb->update_sem); |
| if (val) { |
| /* Enter special mode */ |
| if (fb->blank_state != KONA_FB_UNBLANK) { |
| /* If panel is off, don't do anything with the hw */ |
| konafb_debug("Not in UNBLANK state (%d)\n", |
| fb->blank_state); |
| fb->display_info->special_mode_on = true; |
| fb->suspend_link = true; |
| goto exit_unlock; |
| } |
| if (fb->fb_data->esdcheck) |
| cancel_delayed_work(&fb->esd_check_work); |
| cancel_work_sync(&fb->vsync_smart); |
| |
| if (wait_for_completion_timeout(&fb->prev_buf_done_sem, |
| msecs_to_jiffies(10000)) <= 0) |
| konafb_error("timed out waiting for completion\n"); |
| if (fb->link_suspended) { |
| konafb_debug("Link suspended when entering special " |
| "mode\n"); |
| link_control(fb, RESUME_LINK); |
| } |
| kona_clock_start(fb); |
| if (fb->display_info->cabc_enabled) |
| panel_write(fb->display_info->cabc_off_seq); |
| panel_write(fb->display_info->special_mode_on_seq); |
| kona_clock_stop(fb); |
| fb->suspend_link = true; |
| link_control(fb, SUSPEND_LINK); |
| complete(&g_kona_fb->prev_buf_done_sem); |
| fb->display_info->special_mode_on = true; |
| konafb_debug("Special mode ON\n"); |
| } else { |
| /* Exit special mode */ |
| if (fb->blank_state == KONA_FB_BLANK) { |
| /* If powerHAL exits special mode before system has |
| has sent KONA_FB_UNBLANK we will end up here. In |
| this state the display is already powered off */ |
| konafb_debug("Exit black mode\n"); |
| fb->display_info->special_mode_on = false; |
| fb->suspend_link = false; |
| goto exit_unlock; |
| } |
| cancel_delayed_work_sync(&fb->link_work); |
| wake_unlock(&fb->wlock); |
| fb->suspend_link = false; |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| complete(&g_kona_fb->prev_buf_done_sem); |
| kona_clock_start(fb); |
| panel_write(fb->display_info->special_mode_off_seq); |
| if (fb->display_info->cabc_enabled) |
| panel_write(fb->display_info->cabc_on_seq); |
| kona_clock_stop(fb); |
| schedule_work(&fb->vsync_smart); |
| if (fb->fb_data->esdcheck) |
| queue_delayed_work(fb->esd_check_wq, |
| &fb->esd_check_work, |
| msecs_to_jiffies( |
| fb->fb_data->esdcheck_period_ms)); |
| |
| fb->display_ops->enable_send_first_frame_event(fb->display_hdl); |
| fb->display_info->special_mode_on = false; |
| konafb_debug("Special mode OFF\n"); |
| } |
| exit_unlock: |
| mutex_unlock(&fb->update_sem); |
| exit: |
| return ret; |
| } |
| |
| static struct device_attribute panel_attributes[] = { |
| __ATTR(panel_name, S_IRUGO, kona_fb_panel_name_show, NULL), |
| __ATTR(panel_id, S_IRUGO, kona_fb_panel_id_show, NULL), |
| __ATTR(panel_mode, S_IRUGO|S_IWUSR|S_IWGRP, |
| kona_fb_panel_mode_show, |
| kona_fb_panel_mode_store), |
| }; |
| |
| static int register_attributes(struct device *dev) |
| { |
| int i; |
| for (i = 0; i < ARRAY_SIZE(panel_attributes); i++) |
| if (device_create_file(dev, panel_attributes + i)) |
| goto error; |
| return 0; |
| error: |
| dev_err(dev, "%s: Unable to create interface\n", __func__); |
| for (--i; i >= 0; i--) |
| device_remove_file(dev, panel_attributes + i); |
| return -ENODEV; |
| } |
| |
| static void remove_attributes(struct device *dev) |
| { |
| int i; |
| for (i = 0; i < ARRAY_SIZE(panel_attributes); i++) |
| device_remove_file(dev, panel_attributes + i); |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| /* |
| * Usage: |
| * echo <reg> <nbr_bytes> > panel_read |
| * reg in hex |
| * nbr_bytes in decimal |
| * example: echo 0xDA 1 > panel_read |
| */ |
| static ssize_t dbgfs_reg_read(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| struct seq_file *s = file->private_data; |
| struct kona_fb *fb = s->private; |
| int len; |
| UInt8 *buff; |
| uint8_t reg; |
| char *kbuf = NULL; |
| char *p; |
| int i; |
| |
| mutex_lock(&fb->update_sem); |
| kbuf = kzalloc(sizeof(char) * count, GFP_KERNEL); |
| if (!kbuf) { |
| pr_err("%s: Failed to allocate buffer\n", __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| if (copy_from_user(kbuf, ubuf, count)) { |
| pr_err("%s: Failed to copy from user\n", __func__); |
| ret = -EFAULT; |
| goto fail_exit; |
| } |
| |
| p = kbuf; |
| p = p + 2; |
| if (sscanf(p, "%2hhx", ®) != 1) { |
| pr_err("%s: Parameter error\n", __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| p = p + 2; |
| if (sscanf(p, "%d", &len) != 1) { |
| pr_err("%s: Parameter error\n", __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| |
| if (len == 0) { |
| pr_err("%s: Parameter error (len = 0)\n", __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| |
| buff = kzalloc(sizeof(char) * len, GFP_KERNEL); |
| if (!buff) { |
| pr_err("%s: Failed to allocate buffer\n", __func__); |
| ret = -ENOMEM; |
| goto fail_exit; |
| } |
| |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| |
| panel_read(reg, buff, len); |
| |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| |
| if (fb->suspend_link) |
| link_control(fb, SUSPEND_LINK); |
| |
| pr_info("%s: reg 0x%.2x, len %d\n", __func__, reg, len); |
| for (i = 0; i < len; i++) |
| pr_info("%s: buff[%i] 0x%.2x\n", __func__, i, buff[i]); |
| |
| kfree(buff); |
| fail_exit: |
| kfree(kbuf); |
| exit: |
| mutex_unlock(&fb->update_sem); |
| return count; |
| } |
| |
| static int dbgfs_read_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, NULL, inode->i_private); |
| } |
| |
| static const struct file_operations dbgfs_read_fops = { |
| .owner = THIS_MODULE, |
| .open = dbgfs_read_open, |
| .write = dbgfs_reg_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| /* |
| * Usage: |
| * echo <type> <reg> <data, data, ...> > panel_write |
| * type: dcs gen |
| * reg in hex |
| * data in hex |
| * example: echo dcs 0x29 0 > panel_write |
| */ |
| static ssize_t dbgfs_reg_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| u8 reg; |
| char *kbuf = NULL; |
| char *p; |
| u8 rec[DBGFS_MAX_BUFF_SIZE]; |
| int rec_len = 0; |
| u8 data; |
| struct seq_file *s = file->private_data; |
| struct kona_fb *fb = s->private; |
| |
| mutex_lock(&fb->update_sem); |
| kbuf = kzalloc(sizeof(char) * count, GFP_KERNEL); |
| if (!kbuf) { |
| pr_err("%s: Failed to allocate buffer\n", __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| if (copy_from_user(kbuf, ubuf, count)) { |
| pr_err("%s: Failed to copy from user\n", __func__); |
| ret = -EFAULT; |
| goto fail_exit; |
| } |
| |
| p = kbuf; |
| |
| /* Get type of command */ |
| if (!strncmp(kbuf, "dcs", 3)) { |
| rec[0] = DISPCTRL_WR_CMND; |
| rec_len = 1; |
| } else if (!strncmp(kbuf, "gen", 3)) { |
| rec[0] = DISPCTRL_TAG_GEN_WR; |
| rec_len = 2; /* save one slot for length */ |
| } else { |
| pr_err("%s: Parameter error\n", __func__); |
| ret = -EFAULT; |
| goto fail_exit; |
| } |
| p = p + 3; |
| |
| /* Get to next parameter */ |
| while (isspace(*p) || *p == '0' || (*p == 'x')) { |
| p++; |
| } |
| |
| if (iscntrl(*p)) { /* end of string? */ |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| |
| /* Get register */ |
| if (sscanf(p, "%4hhx", ®) != 1) { |
| pr_err("%s: Parameter error\n", __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| rec[rec_len] = reg; |
| rec_len++; |
| |
| while (!isspace(*p) && !(*p == ',') && !iscntrl(*p)) |
| p++; |
| |
| /* Get data */ |
| while (!iscntrl(*p)) { |
| if (isspace(*p) || (*p == ',')) { |
| p++; |
| } else if (isxdigit(*p)) { |
| if (sscanf(p, "%4hhx", &data) == 1) { |
| rec[rec_len] = data; |
| rec_len++; |
| if (rec_len >= DBGFS_MAX_BUFF_SIZE) { |
| pr_info("%s: too many parameters\n", |
| __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| } |
| while (!isspace(*p) && !(*p == ',') && !iscntrl(*p)) |
| p++; |
| } else { |
| pr_info("%s: parameter parsing error\n", __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| } |
| |
| rec[rec_len] = 0; /* record must end with null */ |
| if (rec[0] == DISPCTRL_WR_CMND) { |
| /* DCS has length in idx 0. command is not included */ |
| rec[0] = rec_len - 1; |
| } else { |
| if (rec_len <= 3) { |
| pr_info("%s: generic command must have one parameter\n", |
| __func__); |
| ret = -EINVAL; |
| goto fail_exit; |
| } |
| /* GEN has command in idx 0 and length in idx 1 */ |
| rec[1] = rec_len - 2; |
| } |
| |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| |
| panel_write(rec); |
| |
| if (fb->suspend_link) |
| link_control(fb, SUSPEND_LINK); |
| |
| fail_exit: |
| kfree(kbuf); |
| exit: |
| mutex_unlock(&fb->update_sem); |
| return count; |
| } |
| |
| static int dbgfs_write_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, NULL, inode->i_private); |
| } |
| |
| static const struct file_operations dbgfs_write_fops = { |
| .owner = THIS_MODULE, |
| .open = dbgfs_write_open, |
| .write = dbgfs_reg_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static ssize_t dbgfs_cabc_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| char *kbuf = NULL; |
| struct seq_file *s = file->private_data; |
| struct kona_fb *fb = s->private; |
| |
| mutex_lock(&fb->update_sem); |
| kbuf = kzalloc(sizeof(char) * count, GFP_KERNEL); |
| if (!kbuf) { |
| pr_err("%s: Failed to allocate buffer\n", __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| if (copy_from_user(kbuf, ubuf, count)) { |
| pr_err("%s: Failed to copy from user\n", __func__); |
| ret = -EFAULT; |
| goto fail_exit; |
| } |
| |
| if (!strncmp(kbuf, "0", 1)) { |
| fb->display_info->cabc_enabled = false; |
| fb->display_info->clear_panel_ram = false; |
| if (fb->blank_state != KONA_FB_BLANK) { |
| (void)kona_fb_blank(FB_BLANK_NORMAL, &fb->fb); |
| (void)kona_fb_blank(FB_BLANK_UNBLANK, &fb->fb); |
| } |
| pr_info("%s: CABC turned off\n", __func__); |
| } else { |
| fb->display_info->cabc_enabled = true; |
| fb->display_info->clear_panel_ram = true; |
| if (fb->blank_state != KONA_FB_BLANK) { |
| (void)kona_fb_blank(FB_BLANK_NORMAL, &fb->fb); |
| (void)kona_fb_blank(FB_BLANK_UNBLANK, &fb->fb); |
| } |
| pr_info("%s: CABC turned on\n", __func__); |
| } |
| fail_exit: |
| kfree(kbuf); |
| exit: |
| mutex_unlock(&fb->update_sem); |
| return count; |
| } |
| |
| static int dbgfs_cabc_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, NULL, inode->i_private); |
| } |
| |
| static const struct file_operations dbgfs_cabc_fops = { |
| .owner = THIS_MODULE, |
| .open = dbgfs_cabc_open, |
| .write = dbgfs_cabc_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| void __init kona_fb_create_debugfs(struct platform_device *pdev) |
| { |
| struct kona_fb *fb; |
| struct device *dev; |
| |
| if (!pdev) { |
| pr_err("%s: no device\n", __func__); |
| return; |
| } |
| |
| dev = &pdev->dev; |
| |
| fb = platform_get_drvdata(pdev); |
| if (!fb) { |
| dev_err(dev, "%s: failed to get drvdata\n", __func__); |
| return; |
| } |
| if (!&dev->kobj) { |
| dev_err(dev, "%s: no &dev->kobj\n", __func__); |
| return; |
| } |
| |
| fb->dbgfs_dir = debugfs_create_dir(kobject_name(&dev->kobj), 0); |
| if (!fb->dbgfs_dir) { |
| dev_err(dev, "%s: dbgfs create dir failed\n", __func__); |
| } else { |
| if (!debugfs_create_file("panel_read", S_IWUSR, fb->dbgfs_dir, |
| fb, &dbgfs_read_fops)) { |
| dev_err(dev, "%s: failed to create dbgfs read file\n", |
| __func__); |
| return; |
| } |
| if (!debugfs_create_file("panel_write", S_IWUSR, fb->dbgfs_dir, |
| fb, &dbgfs_write_fops)) { |
| dev_err(dev, "%s: failed to create dbgfs write file\n", |
| __func__); |
| return; |
| } |
| if (!debugfs_create_file("cabc", S_IWUSR, fb->dbgfs_dir, |
| fb, &dbgfs_cabc_fops)) { |
| dev_err(dev, "%s: failed to create dbgfs cabc file\n", |
| __func__); |
| return; |
| } |
| } |
| } |
| |
| void __exit kona_fb_remove_debugfs(struct platform_device *pdev) |
| { |
| struct kona_fb *fb; |
| |
| if (!pdev || !&pdev->dev) { |
| pr_err("%s: no device\n", __func__); |
| return; |
| } |
| |
| fb = platform_get_drvdata(pdev); |
| if (!fb) { |
| dev_err(&pdev->dev, "%s: failed to get drvdata\n", __func__); |
| return; |
| } |
| debugfs_remove_recursive(fb->dbgfs_dir); |
| } |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| static int enable_display(struct kona_fb *fb) |
| { |
| int ret = 0; |
| |
| ret = fb->display_ops->init(fb->display_info, &fb->display_hdl, fb->pdev); |
| if (ret != 0) { |
| konafb_error("Failed to init this display device!\n"); |
| goto fail_to_init; |
| } |
| |
| kona_clock_start(fb); |
| ret = fb->display_ops->open(fb->display_hdl); |
| if (ret != 0) { |
| konafb_error("Failed to open this display device!\n"); |
| goto fail_to_open; |
| } |
| fb->link_suspended = false; |
| if (fb->fb_data->pm_sleep) |
| ret = fb->display_ops->power_control(fb->display_hdl, |
| CTRL_SLEEP_OUT); |
| else |
| ret = fb->display_ops->power_control(fb->display_hdl, |
| CTRL_PWR_ON); |
| if (ret != 0) { |
| konafb_error("Failed to power on this display device!\n"); |
| goto fail_to_power_control; |
| } |
| INIT_WORK(&fb->vsync_smart, vsync_work_smart); |
| if (!fb->display_info->vmode) { |
| schedule_work(&fb->vsync_smart); |
| kona_clock_stop(fb); |
| } |
| |
| konafb_debug("kona display is enabled successfully\n"); |
| return 0; |
| |
| fail_to_power_control: |
| fb->display_ops->close(fb->display_hdl); |
| fail_to_open: |
| kona_clock_stop(fb); |
| fb->display_ops->exit(fb->display_hdl); |
| fail_to_init: |
| return ret; |
| |
| } |
| |
| static int disable_display(struct kona_fb *fb) |
| { |
| int ret = 0; |
| |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| cancel_work_sync(&fb->vsync_smart); |
| |
| if (fb->fb_data->pm_sleep) |
| fb->display_ops->power_control(fb->display_hdl, |
| CTRL_SLEEP_IN); |
| else |
| fb->display_ops->power_control(fb->display_hdl, |
| CTRL_PWR_OFF); |
| fb->display_ops->close(fb->display_hdl); |
| fb->link_suspended = true; |
| fb->display_ops->exit(fb->display_hdl); |
| kona_clock_stop(fb); |
| konafb_debug("kona display is disabled successfully\n"); |
| return ret; |
| } |
| |
| static int kona_fb_ioctl(struct fb_info *info, unsigned int cmd, |
| unsigned long arg) |
| { |
| void *ptr = NULL; |
| int ret = 0; |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| |
| konafb_debug("kona ioctl called! Cmd %x, Arg %lx\n", cmd, arg); |
| switch (cmd) { |
| |
| case FBIO_WAITFORVSYNC: |
| if (wait_for_completion_killable(&vsync_event) < 0) { |
| konafb_info("Failed to get a vsync event\n"); |
| ret = -ETIMEDOUT; |
| } |
| break; |
| |
| case KONA_IOCTL_SET_BUFFER_AND_UPDATE: |
| if (mutex_lock_killable(&fb->update_sem)) |
| return -EINTR; |
| ptr = (void *)arg; |
| |
| if (ptr == NULL) { |
| mutex_unlock(&fb->update_sem); |
| return -EFAULT; |
| } |
| |
| if (!fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| kona_clock_start(fb); |
| ret = fb->display_ops->update( |
| fb->display_hdl, ptr, NULL, NULL); |
| kona_clock_stop(fb); |
| complete(&g_kona_fb->prev_buf_done_sem); |
| } else { |
| ret = fb->display_ops->update( |
| fb->display_hdl, ptr, NULL, NULL); |
| } |
| |
| mutex_unlock(&fb->update_sem); |
| break; |
| |
| case KONA_IOCTL_GET_FB_IOVA: |
| ptr = (void *)arg; |
| if (ptr == NULL) { |
| pr_err("arg=NULL\n"); |
| return -EFAULT; |
| } |
| ret = copy_to_user(ptr, &fb->buff0, sizeof(u32)); |
| if (ret < 0) |
| pr_err("copy2user failed ret=%d\n", ret); |
| break; |
| |
| default: |
| konafb_error("Wrong ioctl cmd\n"); |
| ret = -ENOTTY; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int clear_panel_ram(struct kona_fb *fb) |
| { |
| int ret = 0; |
| DISPDRV_WIN_t p_win; |
| void *black_buf; |
| size_t fsize, black_ram_size; |
| dma_addr_t phys_base; |
| struct platform_device *pdev = fb->pdev; |
| uint16_t black_ram_lines; |
| |
| pr_info("%s: fill RAM with black\n", __func__); |
| /* Workaround: One page extra allocated and mapped via m4u to avoid |
| * v3d write faulting in m4u doing extra access */ |
| black_ram_lines = fb->display_info->clear_ram_row_end - |
| fb->display_info->clear_ram_row_start + 1; |
| black_ram_size = fb->display_info->width * black_ram_lines * |
| fb->display_info->Bpp; |
| fsize = PAGE_ALIGN(4096 + black_ram_size); |
| |
| black_buf = dma_alloc_writecombine(&pdev->dev, fsize, &phys_base, |
| GFP_KERNEL); |
| if (black_buf == NULL) { |
| ret = -ENOMEM; |
| konafb_error("Unable to allocate black framebuffer\n"); |
| } else { |
| memset(black_buf, 0, fsize); |
| p_win.l = 0; |
| p_win.r = fb->display_info->width - 1; |
| p_win.t = fb->display_info->clear_ram_row_start - 1; |
| p_win.b = fb->display_info->clear_ram_row_end - 1; |
| p_win.h = fb->display_info->clear_ram_row_end; |
| p_win.w = fb->display_info->width; |
| ret = fb->display_ops->update(fb->display_hdl, black_buf, |
| &p_win, NULL); |
| dma_free_writecombine(&pdev->dev, fsize, black_buf, phys_base); |
| } |
| return ret; |
| } |
| |
| static int kona_fb_blank(int blank_mode, struct fb_info *info) |
| { |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| |
| switch (blank_mode) { |
| case FB_BLANK_POWERDOWN: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| mutex_lock(&fb->update_sem); |
| if (fb->blank_state == KONA_FB_BLANK) { |
| konafb_debug("Display already in blank state. " |
| "Do nothing\n"); |
| mutex_unlock(&fb->update_sem); |
| break; |
| } |
| |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| |
| if (fb->fb_data->esdcheck) { |
| /* cancel the esd check routine */ |
| cancel_delayed_work(&fb->esd_check_work); |
| } |
| |
| fb->g_stop_drawing = 1; |
| /* In case of video mode, DSI commands can be sent out-of-sync |
| * of buffers */ |
| if (!fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| kona_clock_start(fb); |
| if (fb->display_ops->power_control(fb->display_hdl, |
| CTRL_SCREEN_OFF)) |
| konafb_error( |
| "Failed to blank this display device!\n"); |
| kona_clock_stop(fb); |
| complete(&g_kona_fb->prev_buf_done_sem); |
| } else { |
| if (fb->display_ops->power_control(fb->display_hdl, |
| CTRL_SCREEN_OFF)) |
| konafb_error( |
| "Failed to blank this display device!\n"); |
| } |
| |
| /* screen goes to sleep mode */ |
| disable_display(fb); |
| |
| /* Ok for MM going to shutdown state */ |
| pi_mgr_qos_request_update(&g_mm_qos_node, |
| PI_MGR_QOS_DEFAULT_VALUE); |
| |
| fb->blank_state = KONA_FB_BLANK; |
| mutex_unlock(&fb->update_sem); |
| break; |
| |
| case FB_BLANK_UNBLANK: |
| mutex_lock(&fb->update_sem); |
| if (fb->blank_state == KONA_FB_UNBLANK) { |
| konafb_debug("Display already in unblank state. " |
| "Do nothing\n"); |
| mutex_unlock(&fb->update_sem); |
| break; |
| } |
| |
| /* Ok for MM going to retention but not shutdown state */ |
| pi_mgr_qos_request_update(&g_mm_qos_node, 10); |
| /* screen comes out of sleep */ |
| if (enable_display(fb)) |
| konafb_error("Failed to enable this display device\n"); |
| |
| if (!fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| |
| kona_clock_start(fb); |
| } |
| if (fb->display_info->clear_panel_ram) |
| (void)clear_panel_ram(fb); |
| fb->display_ops->update(fb->display_hdl, |
| fb->fb.var.yoffset ? fb->buff1 : fb->buff0, |
| NULL, |
| (DISPDRV_CB_T)kona_display_done_cb); |
| |
| if (fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| } |
| |
| |
| fb->g_stop_drawing = 0; |
| |
| if (fb->display_info->special_mode_on == true) { |
| konafb_debug("Go to special mode\n"); |
| kona_clock_start(fb); |
| panel_write(fb->display_info->special_mode_on_seq); |
| kona_clock_stop(fb); |
| } |
| |
| if (!fb->display_info->vmode) { |
| kona_clock_start(fb); |
| |
| if (fb->display_ops-> |
| power_control(fb->display_hdl, CTRL_SCREEN_ON)) |
| konafb_error |
| ("Failed to unblank this display device!\n"); |
| |
| kona_clock_stop(fb); |
| } else { |
| if (fb->display_ops-> |
| power_control(fb->display_hdl, CTRL_SCREEN_ON)) |
| konafb_error |
| ("Failed to unblank this display device!\n"); |
| } |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| if (atomic_read(&g_kona_fb->force_update)) |
| kona_display_crash_image(CP_CRASH_DUMP_START); |
| #endif |
| if (fb->fb_data->esdcheck) { |
| /* schedule the esd check routine */ |
| queue_delayed_work(fb->esd_check_wq, |
| &fb->esd_check_work, |
| msecs_to_jiffies(fb->fb_data->esdcheck_period_ms)); |
| } |
| |
| if (fb->suspend_link) |
| link_control(fb, SUSPEND_LINK); |
| |
| fb->blank_state = KONA_FB_UNBLANK; |
| mutex_unlock(&fb->update_sem); |
| break; |
| |
| default: |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static irqreturn_t kona_fb_esd_irq(int irq, void *dev_id) |
| { |
| struct kona_fb *fb = (struct kona_fb *)dev_id; |
| complete(&fb->tectl_gpio_done_sem); |
| return IRQ_HANDLED; |
| } |
| |
| |
| |
| static void kona_fb_esd_check(struct work_struct *data) |
| { |
| struct kona_fb *fb = container_of((struct delayed_work *)data, |
| struct kona_fb, esd_check_work); |
| int esd_result = -EIO; |
| uint32_t tectl_gpio = fb->fb_data->tectl_gpio; |
| |
| if (g_display_enabled) { |
| /* Wait fb initial completed */ |
| queue_delayed_work(fb->esd_check_wq, &fb->esd_check_work, |
| msecs_to_jiffies(fb->fb_data->esdcheck_period_ms)); |
| return; |
| } |
| |
| if (fb->g_stop_drawing) |
| return; |
| |
| if (tectl_gpio > 0) { |
| unsigned long jiff_in = 0; |
| |
| /* mark as incomplete before waiting the esd signal */ |
| INIT_COMPLETION(fb->tectl_gpio_done_sem); |
| enable_irq(gpio_to_irq(tectl_gpio)); |
| jiff_in = wait_for_completion_timeout(&fb->tectl_gpio_done_sem, |
| msecs_to_jiffies(200)); |
| if (!jiff_in) { |
| konafb_error("Wait esd gpio irq timeout\n"); |
| esd_result = -EIO; |
| } else |
| esd_result = 0; |
| disable_irq(gpio_to_irq(tectl_gpio)); |
| } else { |
| mutex_lock(&fb->update_sem); |
| if (fb->display_info->esd_check_fn) |
| esd_result = fb->display_info->esd_check_fn(); |
| if (esd_result < 0) /* stop drawing till error is recovered */ |
| fb->g_stop_drawing = 1; |
| mutex_unlock(&fb->update_sem); |
| } |
| |
| konafb_debug("esd_result %d\n", esd_result); |
| |
| if (esd_result < 0) { |
| konafb_error("result %d failure_cnt %d\n", esd_result, |
| fb->esd_failure_cnt); |
| |
| fb->esd_failure_cnt++; |
| if (fb->esd_failure_cnt > fb->fb_data->esdcheck_retry) { |
| konafb_error("too many fail %d, disable esd check\n", |
| fb->esd_failure_cnt); |
| fb->fb_data->esdcheck = FALSE; |
| cancel_delayed_work(&fb->esd_check_work); |
| destroy_workqueue(fb->esd_check_wq); |
| } |
| } else { |
| fb->esd_failure_cnt = 0; |
| queue_delayed_work(fb->esd_check_wq, &fb->esd_check_work, |
| msecs_to_jiffies(fb->fb_data->esdcheck_period_ms)); |
| } |
| |
| return; |
| } |
| |
| void free_platform_data(struct device *dev) |
| { |
| if (dev->of_node) |
| kfree(dev->platform_data); |
| } |
| |
| static int __init lcd_panel_setup(char *panel) |
| { |
| if (panel && strlen(panel)) { |
| pr_err("bootloader has initialised %s\n", panel); |
| strcpy(g_disp_str, panel); |
| g_display_enabled = 0; |
| } |
| return 1; |
| } |
| __setup("lcd_panel=", lcd_panel_setup); |
| |
| |
| static int __init lcd_boot_mode(char *mode) |
| { |
| #ifndef CONFIG_FB_NEED_PAGE_ALIGNMENT |
| if (mode && strlen(mode)) { |
| if (!strcmp(mode, "recovery") || !strcmp(mode, "charger")) |
| need_page_alignment = 0; |
| } |
| #endif |
| return 1; |
| } |
| __setup("androidboot.mode=", lcd_boot_mode); |
| |
| #ifdef CONFIG_OF |
| static struct kona_fb_platform_data * __init get_of_data(struct device_node *np) |
| { |
| u32 val; |
| const char *str; |
| struct kona_fb_platform_data *fb_data; |
| #ifdef CONFIG_IOMMU_API |
| struct device_node *tmp_node; |
| #endif |
| |
| fb_data = kzalloc(sizeof(struct kona_fb_platform_data), |
| GFP_KERNEL); |
| if (!fb_data) { |
| pr_err("couldn't allocate memory for pdata"); |
| goto alloc_failed; |
| } |
| |
| if (of_property_read_string(np, "module-name", &str)) |
| goto of_fail; |
| if (unlikely(strlen(str) > DISPDRV_NAME_SZ)) |
| goto of_fail; |
| if (g_display_enabled && strcmp(str, g_disp_str)) { |
| pr_err("%s != %s enabled by bootloader\n", str, g_disp_str); |
| goto of_fail; |
| } |
| strcpy(fb_data->name, str); |
| |
| if (of_property_read_string(np, "reg-name", &str)) |
| goto of_fail; |
| if (unlikely(strlen(str) > DISPDRV_NAME_SZ)) |
| goto of_fail; |
| strcpy(fb_data->reg_name, str); |
| fb_data->rst.gpio = of_get_named_gpio(np, "rst-gpio", 0); |
| if (!gpio_is_valid(fb_data->rst.gpio)) |
| goto of_fail; |
| if (of_property_read_u32(np, "rst-setup", &val)) |
| goto of_fail; |
| fb_data->rst.setup = val; |
| if (of_property_read_u32(np, "rst-pulse", &val)) |
| goto of_fail; |
| fb_data->rst.pulse = val; |
| if (of_property_read_u32(np, "rst-hold", &val)) |
| goto of_fail; |
| fb_data->rst.hold = val; |
| |
| if (of_property_read_bool(np, "rst-active-high")) |
| fb_data->rst.active = true; |
| else |
| fb_data->rst.active = false; |
| |
| if (of_property_read_u32(np, "rotation", &val)) |
| goto of_fail; |
| fb_data->rotation = val; |
| |
| fb_data->detect.gpio = of_get_named_gpio(np, "detect-gpio", 0); |
| if (gpio_is_valid(fb_data->detect.gpio)) { |
| if (of_property_read_u32(np, |
| "detect-gpio-val", &val)) |
| goto of_fail; |
| fb_data->detect.gpio_val = val; |
| |
| fb_data->detect.active = |
| gpio_get_value(fb_data->detect.gpio); |
| if (fb_data->detect.active != |
| fb_data->detect.gpio_val) { |
| konafb_error( |
| "gpio %d value failed for panel %s\n", |
| fb_data->detect.gpio, fb_data->name); |
| goto of_fail; |
| } |
| } |
| |
| if (of_property_read_bool(np, "vmode")) |
| fb_data->vmode = true; |
| else |
| fb_data->vmode = false; |
| if (of_property_read_bool(np, "vburst")) |
| fb_data->vburst = true; |
| else |
| fb_data->vburst = false; |
| if (of_property_read_bool(np, "cmnd-LP")) |
| fb_data->cmnd_LP = true; |
| else |
| fb_data->cmnd_LP = false; |
| if (of_property_read_bool(np, "te-ctrl")) |
| fb_data->te_ctrl = true; |
| else |
| fb_data->te_ctrl = false; |
| if (of_property_read_bool(np, "pm-sleep")) |
| fb_data->pm_sleep = true; |
| else |
| fb_data->pm_sleep = false; |
| |
| if (of_property_read_u32(np, "col-mod-i", &val)) |
| goto of_fail; |
| fb_data->col_mod_i = (uint8_t)val; |
| if (of_property_read_u32(np, "col-mod-o", &val)) |
| goto of_fail; |
| fb_data->col_mod_o = (uint8_t)val; |
| if (of_property_read_u32(np, "width", &val)) |
| goto of_fail; |
| fb_data->width = (uint16_t)val; |
| if (of_property_read_u32(np, "height", &val)) |
| goto of_fail; |
| fb_data->height = (uint16_t)val; |
| if (of_property_read_u32(np, "fps", &val)) |
| goto of_fail; |
| fb_data->fps = (uint8_t)val; |
| if (of_property_read_u32(np, "lanes", &val)) |
| goto of_fail; |
| fb_data->lanes = (uint8_t)val; |
| if (of_property_read_u32(np, "hs-bitrate", &val)) |
| goto of_fail; |
| fb_data->hs_bps = val; |
| if (of_property_read_u32(np, "lp-bitrate", &val)) |
| goto of_fail; |
| fb_data->lp_bps = val; |
| |
| /* Desense offset value to be absolute value w.r.t hs-bitrate */ |
| if (of_property_read_u32(np, "desense-offset", &val)) { |
| konafb_info("desense offset not populated\n"); |
| fb_data->desense_offset = 0; |
| } else { |
| konafb_info("desense offset requested %d\n", val); |
| fb_data->desense_offset = (int) val; |
| } |
| |
| /*Get the ESD config */ |
| if (of_property_read_bool(np, "esdcheck")) { |
| fb_data->esdcheck = TRUE; |
| if (of_property_read_u32(np, "esdcheck-period-ms", &val)) |
| goto of_fail; |
| fb_data->esdcheck_period_ms = val; |
| if (of_property_read_u32(np, "esdcheck-retry", &val)) |
| goto of_fail; |
| fb_data->esdcheck_retry = (uint8_t)val; |
| if (of_property_read_u32(np, "tectl-gpio", &val)) |
| fb_data->tectl_gpio = 0; |
| else { |
| /* Only allow tectl as gpio if TE is not used */ |
| if (fb_data->vmode || !fb_data->te_ctrl) |
| fb_data->tectl_gpio = val; |
| else |
| konafb_info("Can't enable tectl_gpio"); |
| } |
| } else |
| fb_data->esdcheck = FALSE; |
| |
| #ifdef CONFIG_IOMMU_API |
| /* Get the iommu device and link fb dev to iommu dev */ |
| tmp_node = of_parse_phandle(np, "iommu", 0); |
| if (tmp_node == NULL) { |
| pr_err("%s get node(iommu) failed\n", __func__); |
| goto of_fail; |
| } |
| fb_data->pdev_iommu = of_find_device_by_node(tmp_node); |
| if (fb_data->pdev_iommu == NULL) { |
| pr_err("%s get iommu device failed\n", __func__); |
| goto of_fail; |
| } |
| #endif /* CONFIG_IOMMU_API */ |
| |
| #ifdef CONFIG_BCM_IOVMM |
| /* Get the iommu mapping and attach fb dev to mapping */ |
| tmp_node = of_parse_phandle(np, "iovmm", 0); |
| if (tmp_node == NULL) { |
| pr_err("%s get node(iovmm) failed\n", __func__); |
| goto of_fail; |
| } |
| fb_data->pdev_iovmm = of_find_device_by_node(tmp_node); |
| if (fb_data->pdev_iovmm == NULL) { |
| pr_err("%s get iovmm device failed\n", __func__); |
| goto of_fail; |
| } |
| #endif /* CONFIG_BCM_IOVMM */ |
| |
| |
| return fb_data; |
| |
| of_fail: |
| konafb_error("get_of_data failed\n"); |
| kfree(fb_data); |
| alloc_failed: |
| return NULL; |
| } |
| #endif /* CONFIG_OF */ |
| |
| static char *get_seq(DISPCTRL_REC_T *rec) |
| { |
| char *buff, *dst; |
| int list_len, cmd_len; |
| DISPCTRL_REC_T *cur; |
| int i; |
| |
| buff = NULL; |
| /* Get the length of sequence in bytes = cmd+data+headersize+null */ |
| cur = rec; |
| list_len = 0; |
| for (i = 0; DISPCTRL_LIST_END != cur[i].type; i++) { |
| if (DISPCTRL_WR_DATA == cur[i].type) |
| list_len++; |
| else if (DISPCTRL_GEN_WR_CMND == cur[i].type) |
| list_len += 3; /* One more tag for Gen cmd */ |
| else |
| list_len += 2; /* Indicates new packet */ |
| } |
| |
| list_len++; /* NULL termination */ |
| |
| /* Allocate buff = length */ |
| buff = kmalloc(list_len, GFP_KERNEL); |
| if (!buff) |
| goto seq_done; |
| |
| |
| /* Parse the DISPCTRL_REC_T[], extract data and fill buff */ |
| cur = rec; |
| dst = buff; |
| i = 0; |
| for (i = 0; DISPCTRL_LIST_END != cur[i].type; i++) { |
| switch (cur[i].type) { |
| case DISPCTRL_GEN_WR_CMND: |
| *dst++ = DISPCTRL_TAG_GEN_WR; |
| /* fall through */ |
| case DISPCTRL_WR_CMND: |
| cmd_len = 1; |
| dst++; |
| *dst++ = cur[i].val; |
| while (DISPCTRL_WR_DATA == cur[i + 1].type) { |
| i++; |
| *dst++ = cur[i].val; |
| cmd_len++; |
| } |
| if (cmd_len > DISPCTRL_MAX_DATA_LEN) { |
| pr_err("cmd_len %d reach max\n", cmd_len); |
| kfree(buff); |
| buff = NULL; |
| goto seq_done; |
| } |
| *(dst - cmd_len - 1) = cmd_len; |
| break; |
| case DISPCTRL_SLEEP_MS: |
| /* Maximum packet size is limited to 254 */ |
| *dst++ = DISPCTRL_TAG_SLEEP; |
| *dst++ = cur[i].val; |
| break; |
| default: |
| pr_err("Invalid control list type %d\n", cur[i].type); |
| } |
| } |
| |
| /* Put a NULL at the end */ |
| *dst = 0; |
| if (dst != (buff + list_len - 1)) |
| pr_err("dst ptr mismatch\n"); |
| #if 0 |
| pr_err("dst %p buff %p\n", dst, buff); |
| dst = buff; |
| while (*dst) |
| pr_err("%d ", *dst++); |
| pr_err("list end size %d\n", list_len); |
| #endif |
| seq_done: |
| return buff; |
| } |
| |
| static int __init populate_dispdrv_cfg(struct kona_fb *fb, |
| struct kona_fb_platform_data *pd) |
| { |
| struct lcd_config *cfg; |
| DISPDRV_INFO_T *info; |
| int ret = -1; |
| |
| cfg = get_dispdrv_cfg(pd->name); |
| if (!cfg) { |
| pr_err("Couldn't find a suitable dispdrv\n"); |
| ret = -ENXIO; |
| goto err_cfg; |
| } |
| |
| if (cfg->mode_supp != LCD_CMD_VID_BOTH) { |
| if (pd->vmode && (cfg->mode_supp != LCD_VID_ONLY)) { |
| pr_err("No vid mode support\n"); |
| ret = -EINVAL; |
| goto err_cfg; |
| } |
| if (!pd->vmode && (cfg->mode_supp != LCD_CMD_ONLY)) { |
| pr_err("No cmd mode support\n"); |
| ret = -EINVAL; |
| goto err_cfg; |
| } |
| } |
| |
| /* Allocate memory for disp_info */ |
| /* Setting memory to zero is mandatory for this struct */ |
| info = kzalloc(sizeof(DISPDRV_INFO_T), GFP_KERNEL); |
| if (!info) { |
| pr_err("Failed to allocate memory fr disp_info\n"); |
| ret = -ENOMEM; |
| goto err_mem_disp_info; |
| } |
| |
| /* Fill up "info" using "cfg" and "pd" */ |
| info->name = pd->name; |
| info->reg_name = pd->reg_name; |
| info->rst = &pd->rst; |
| info->vmode = pd->vmode; |
| info->vburst = (pd->vburst == cfg->vburst) ? pd->vburst : false; |
| info->vid_cmnds = cfg->vid_cmnds; |
| info->cmnd_LP = pd->cmnd_LP; |
| info->te_ctrl = pd->te_ctrl; |
| info->width = pd->width; |
| info->height = pd->height; |
| info->lanes = (pd->lanes > cfg->max_lanes) ? cfg->max_lanes : pd->lanes; |
| |
| /* Hardcode for now */ |
| info->in_fmt = pd->col_mod_i; |
| info->out_fmt = pd->col_mod_o; |
| if (info->in_fmt == DISPDRV_FB_FORMAT_RGB666U || |
| info->in_fmt == DISPDRV_FB_FORMAT_RGB666P || |
| info->in_fmt == DISPDRV_FB_FORMAT_RGB565) |
| info->Bpp = 2; |
| else |
| info->Bpp = 4; |
| |
| info->phys_width = cfg->phys_width; |
| info->phys_height = cfg->phys_height; |
| info->fps = pd->fps; |
| |
| info->slp_in_seq = get_seq(cfg->slp_in_seq); |
| if (!info->slp_in_seq) |
| goto err_slp_in_seq; |
| info->slp_out_seq = get_seq(cfg->slp_out_seq); |
| if (!info->slp_out_seq) |
| goto err_slp_out_seq; |
| info->scrn_on_seq = get_seq(cfg->scrn_on_seq); |
| if (!info->scrn_on_seq) |
| goto err_scrn_on_seq; |
| info->scrn_off_seq = get_seq(cfg->scrn_off_seq); |
| if (!info->scrn_off_seq) |
| goto err_scrn_off_seq; |
| if (cfg->verify_id) { |
| info->id_seq = get_seq(cfg->id_seq); |
| if (!info->id_seq) |
| goto err_id_seq; |
| } |
| memcpy(info->phy_timing, cfg->phy_timing, sizeof(info->phy_timing)); |
| if (info->vmode) { |
| info->init_seq = get_seq(cfg->init_vid_seq); |
| info->hs = cfg->hs; |
| info->hbp = cfg->hbp; |
| info->hfp = cfg->hfp; |
| info->hbllp = cfg->hbllp; |
| info->vs = cfg->vs; |
| info->vbp = cfg->vbp; |
| info->vfp = cfg->vfp; |
| } else { |
| info->init_seq = get_seq(cfg->init_cmd_seq); |
| info->updt_win_fn = cfg->updt_win_fn; |
| info->updt_win_seq_len = cfg->updt_win_seq_len; |
| } |
| if (!info->init_seq) |
| goto err_init_seq; |
| |
| if (!info->vmode) { |
| if (!info->updt_win_seq_len) { |
| pr_err("Abort: win_seq_len = 0 for command mode\n"); |
| goto err_win_seq; |
| } |
| info->win_seq = kmalloc(info->updt_win_seq_len, GFP_KERNEL); |
| if (!info->win_seq) |
| goto err_win_seq; |
| } |
| |
| /* Panel special mode */ |
| if (cfg->special_mode_panel) { |
| info->special_mode_on_seq = |
| get_seq(cfg->special_mode_on_cmd_seq); |
| if (!info->special_mode_on_seq) |
| goto err_special_mode_seq; |
| info->special_mode_off_seq = |
| get_seq(cfg->special_mode_off_cmd_seq); |
| if (!info->special_mode_off_seq) |
| goto err_special_mode_seq; |
| info->special_mode_on = cfg->special_mode_on; |
| info->special_mode_panel = cfg->special_mode_panel; |
| pr_info("%s(%d): Panel special mode: %d\n", |
| __func__, __LINE__, info->special_mode_on); |
| } |
| |
| info->cabc_enabled = cfg->cabc_enabled; |
| if (info->cabc_enabled) { |
| info->cabc_init_seq = get_seq(cfg->cabc_init_seq); |
| if (!info->cabc_init_seq) |
| goto err_cabc_seq; |
| info->cabc_on_seq = get_seq(cfg->cabc_on_seq); |
| if (!info->cabc_on_seq) |
| goto err_cabc_seq; |
| info->cabc_off_seq = get_seq(cfg->cabc_off_seq); |
| if (!info->cabc_off_seq) |
| goto err_cabc_seq; |
| } |
| |
| info->clear_panel_ram = cfg->clear_panel_ram; |
| info->clear_ram_row_start = cfg->clear_ram_row_start; |
| info->clear_ram_row_end = cfg->clear_ram_row_end; |
| info->no_te_in_sleep = cfg->no_te_in_sleep; |
| |
| if (info->clear_panel_ram) /* Override kernel command line ... */ |
| g_display_enabled = 0; /* ... and force re-init of display */ |
| |
| |
| /* burst mode changes to be taken care here or PV? */ |
| info->hs_bps = (pd->hs_bps > cfg->max_hs_bps) ? |
| cfg->max_hs_bps : pd->hs_bps; |
| info->lp_bps = (pd->lp_bps > cfg->max_lp_bps) ? |
| cfg->max_lp_bps : pd->lp_bps; |
| info->desense_offset = pd->desense_offset; |
| |
| info->vsync_cb = (info->vmode) ? konafb_vsync_cb : NULL; |
| info->cont_clk = cfg->cont_clk; |
| info->init_fn = cfg->init_fn; |
| info->esd_check_fn = cfg->esd_check_fn; |
| fb->display_info = info; |
| return 0; |
| |
| err_cabc_seq: |
| kfree(info->cabc_init_seq); |
| kfree(info->cabc_on_seq); |
| kfree(info->cabc_off_seq); |
| err_special_mode_seq: |
| kfree(info->special_mode_on_seq); |
| kfree(info->special_mode_off_seq); |
| err_win_seq: |
| kfree(info->init_seq); |
| err_init_seq: |
| if (cfg->verify_id) |
| kfree(info->id_seq); |
| err_id_seq: |
| kfree(info->scrn_off_seq); |
| err_scrn_off_seq: |
| kfree(info->scrn_on_seq); |
| err_scrn_on_seq: |
| kfree(info->slp_out_seq); |
| err_slp_out_seq: |
| kfree(info->slp_in_seq); |
| err_slp_in_seq: |
| kfree(info); |
| err_mem_disp_info: |
| err_cfg: |
| return ret; |
| } |
| |
| void release_dispdrv_info(DISPDRV_INFO_T *info) |
| { |
| BUG_ON(ZERO_OR_NULL_PTR(info)); |
| kfree(info->cabc_init_seq); |
| kfree(info->cabc_on_seq); |
| kfree(info->cabc_off_seq); |
| kfree(info->special_mode_on_seq); |
| kfree(info->special_mode_off_seq); |
| kfree(info->init_seq); |
| kfree(info->slp_in_seq); |
| kfree(info->slp_out_seq); |
| kfree(info->scrn_on_seq); |
| kfree(info->scrn_off_seq); |
| kfree(info->win_seq); |
| kfree(info); |
| } |
| |
| static int kona_fb_open(struct fb_info *info, int user) |
| { |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| fb->open_count++; |
| return 0; |
| } |
| |
| static int kona_fb_release(struct fb_info *info, int user) |
| { |
| struct kona_fb *fb = container_of(info, struct kona_fb, fb); |
| fb->open_count--; |
| |
| if (fb->open_count < 0) |
| return -EIO; |
| /* If the open count goes 0, then restore the rotation parameter so that |
| * user space can again get to know the rotation parameter. */ |
| if (fb->open_count == 0) |
| info->var.rotate = fb->rotate; |
| |
| return 0; |
| } |
| |
| static struct fb_ops kona_fb_ops = { |
| .fb_open = kona_fb_open, |
| .fb_release = kona_fb_release, |
| .owner = THIS_MODULE, |
| .fb_check_var = kona_fb_check_var, |
| .fb_set_par = kona_fb_set_par, |
| .fb_setcolreg = kona_fb_setcolreg, |
| .fb_pan_display = kona_fb_pan_display, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| .fb_ioctl = kona_fb_ioctl, |
| .fb_sync = kona_fb_sync, |
| .fb_blank = kona_fb_blank, |
| }; |
| |
| |
| static const struct file_operations proc_fops = { |
| .write = proc_write_fb_test, |
| }; |
| |
| static int __ref kona_fb_probe(struct platform_device *pdev) |
| { |
| int ret = -ENXIO; |
| struct kona_fb *fb; |
| size_t framesize, framesize_alloc; |
| uint32_t width, height; |
| int ret_val = -1; |
| struct kona_fb_platform_data *fb_data; |
| dma_addr_t phys_fbbase, dma_addr, direct_dma_addr; |
| uint64_t pixclock_64; |
| #ifdef CONFIG_LOGO |
| int logo_rotate; |
| #endif |
| uint32_t uboot_phys; |
| void *uboot_kvirt = NULL; |
| int need_map_switch = 0; |
| |
| konafb_info("start\n"); |
| if (g_kona_fb && (g_kona_fb->is_display_found == 1)) { |
| konafb_info("A right display device is already found!\n"); |
| return -EINVAL; |
| } |
| |
| fb = kzalloc(sizeof(struct kona_fb), GFP_KERNEL); |
| if (fb == NULL) { |
| konafb_error("Unable to allocate framebuffer structure\n"); |
| ret = -ENOMEM; |
| goto err_fb_alloc_failed; |
| } |
| fb->g_stop_drawing = 0; |
| |
| g_kona_fb = fb; |
| ret_val = |
| pi_mgr_dfs_add_request(&g_kona_fb->dfs_node, "lcd", PI_MGR_PI_ID_MM, |
| PI_OPP_ECONOMY); |
| |
| if (ret_val) { |
| printk(KERN_ERR "Failed to add dfs request for LCD\n"); |
| ret = -EIO; |
| goto fb_dfs_fail; |
| } |
| |
| #ifdef CONFIG_OF |
| if (pdev->dev.of_node) { |
| fb_data = get_of_data(pdev->dev.of_node); |
| if (!fb_data) |
| goto fb_data_failed; |
| else /* Save the pointer needed in remove method */ |
| pdev->dev.platform_data = fb_data; |
| } else { |
| #endif |
| fb_data = pdev->dev.platform_data; |
| if (!fb_data) { |
| ret = -EINVAL; |
| goto fb_data_failed; |
| } |
| #ifdef CONFIG_OF |
| } |
| #endif |
| |
| if (populate_dispdrv_cfg(fb, fb_data)) |
| goto dispdrv_data_failed; |
| |
| pr_err("Initialising in %s mode\n", fb->display_info->vmode ? |
| "VIDEO" : "COMMAND"); |
| fb->display_ops = (DISPDRV_T *)DISP_DRV_GetFuncTable(); |
| |
| spin_lock_init(&fb->lock); |
| platform_set_drvdata(pdev, fb); |
| fb->fb_data = fb_data; |
| fb->pdev = pdev; |
| |
| mutex_init(&fb->update_sem); |
| atomic_set(&fb->buff_idx, 0); |
| atomic_set(&fb->is_fb_registered, 0); |
| atomic_set(&fb->force_update, 0); |
| init_completion(&fb->prev_buf_done_sem); |
| init_completion(&vsync_event); |
| complete(&fb->prev_buf_done_sem); |
| atomic_set(&fb->is_graphics_started, 0); |
| INIT_DELAYED_WORK(&fb->link_work, fb_suspend_link_work); |
| wake_lock_init(&fb->wlock, WAKE_LOCK_SUSPEND, "dsi_link_wakelock"); |
| |
| ret = enable_display(fb); |
| if (ret) { |
| konafb_error("Failed to enable this display device\n"); |
| goto err_enable_display_failed; |
| } else { |
| fb->is_display_found = 1; |
| } |
| |
| framesize = fb->display_info->width * fb->display_info->height * |
| fb->display_info->Bpp; |
| |
| if (need_page_alignment) |
| framesize = PAGE_ALIGN(framesize); |
| framesize *= 2; |
| |
| /* Workaround: One page extra allocated and mapped via m4u to avoid |
| * v3d write faulting in m4u doing extra access */ |
| framesize_alloc = PAGE_ALIGN(framesize + 4096); |
| fb->framesize_alloc = framesize_alloc; |
| |
| fb->fb.screen_base = dma_alloc_writecombine(&pdev->dev, |
| framesize_alloc, |
| &phys_fbbase, |
| GFP_KERNEL); |
| pr_info("kona_fb: screen_base=%p, phys_fbbase=%p size=0x%x\n", |
| (void *)fb->fb.screen_base, (void *)phys_fbbase, |
| framesize_alloc); |
| if (fb->fb.screen_base == NULL) { |
| ret = -ENOMEM; |
| konafb_error("Unable to allocate fb memory\n"); |
| goto err_fbmem_alloc_failed; |
| } |
| dma_addr = phys_fbbase; |
| direct_dma_addr = dma_addr; |
| |
| #ifdef CONFIG_IOMMU_API |
| pdev->dev.archdata.iommu = &fb_data->pdev_iommu->dev; |
| pr_info("%s iommu-device(%p)\n", "framebuffer", |
| pdev->dev.archdata.iommu); |
| #ifdef CONFIG_BCM_IOVMM |
| need_map_switch = g_display_enabled && fb->display_info->vmode; |
| if (need_map_switch) { |
| /* 1:1 map for uboot logo */ |
| ret = kona_fb_direct_map(fb, |
| framesize_alloc, phys_fbbase, direct_dma_addr); |
| if (ret < 0) { |
| pr_err("kona_fb_direct_map failed\n"); |
| goto err_set_var_failed; |
| } |
| } |
| ret = kona_fb_iovmm_map(fb, framesize_alloc, phys_fbbase, &dma_addr); |
| #else |
| ret = kona_fb_direct_map(fb, |
| framesize_alloc, phys_fbbase, direct_dma_addr); |
| #endif /* CONFIG_BCM_IOVMM */ |
| pr_info("%16s: iommu map da(%#x) pa(%#x) size(%#x)\n", |
| "framebuffer", dma_addr, phys_fbbase, framesize_alloc); |
| if (ret < 0) { |
| pr_err("kona_fb_iommu_mapping failed\n"); |
| goto err_set_var_failed; |
| } |
| #endif /* CONFIG_IOMMU_API */ |
| |
| /* Now we should get correct width and height for this display .. */ |
| width = fb->display_info->width; |
| height = fb->display_info->height; |
| fb->buff0 = (void *)dma_addr; |
| fb->buff1 = (void *)dma_addr + framesize / 2; |
| |
| fb->fb.fbops = &kona_fb_ops; |
| fb->fb.flags = FBINFO_FLAG_DEFAULT; |
| fb->fb.pseudo_palette = fb->cmap; |
| fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; |
| fb->fb.fix.visual = FB_VISUAL_TRUECOLOR; |
| fb->fb.fix.line_length = width * fb->display_info->Bpp; |
| fb->fb.fix.accel = FB_ACCEL_NONE; |
| fb->fb.fix.ypanstep = 1; |
| fb->fb.fix.xpanstep = 4; |
| #ifdef PARTIAL_UPDATE_SUPPORT |
| fb->fb.fix.reserved[0] = 0x5444; |
| fb->fb.fix.reserved[1] = 0x5055; |
| #endif |
| fb->fb.var.xres = width; |
| fb->fb.var.yres = height; |
| fb->fb.var.xres_virtual = width; |
| fb->fb.var.yres_virtual = height * 2; |
| fb->fb.var.bits_per_pixel = fb->display_info->Bpp << 3; |
| fb->fb.var.activate = FB_ACTIVATE_NOW; |
| fb->fb.var.height = fb->display_info->phys_height; |
| fb->fb.var.width = fb->display_info->phys_width; |
| pixclock_64 = div_u64(1000000000000LLU, |
| width * height * fb->display_info->fps); |
| fb->fb.var.pixclock = pixclock_64; |
| fb->fb.var.rotate = fb_data->rotation == 180 ? |
| FB_ROTATE_UD : FB_ROTATE_UR; |
| fb->rotate = fb->fb.var.rotate; |
| |
| switch (fb->display_info->in_fmt) { |
| case DISPDRV_FB_FORMAT_RGB666P: |
| case DISPDRV_FB_FORMAT_RGB666U: |
| case DISPDRV_FB_FORMAT_RGB565: |
| fb->fb.var.red.offset = 11; |
| fb->fb.var.red.length = 5; |
| fb->fb.var.green.offset = 5; |
| fb->fb.var.green.length = 6; |
| fb->fb.var.blue.offset = 0; |
| fb->fb.var.blue.length = 5; |
| break; |
| |
| case DISPDRV_FB_FORMAT_xRGB8888: |
| fb->fb.var.transp.offset = 24; |
| fb->fb.var.transp.length = 8; |
| fb->fb.var.red.offset = 16; |
| fb->fb.var.red.length = 8; |
| fb->fb.var.green.offset = 8; |
| fb->fb.var.green.length = 8; |
| fb->fb.var.blue.offset = 0; |
| fb->fb.var.blue.length = 8; |
| break; |
| |
| case DISPDRV_FB_FORMAT_xBGR8888: |
| fb->fb.var.transp.offset = 24; |
| fb->fb.var.transp.length = 8; |
| fb->fb.var.blue.offset = 16; |
| fb->fb.var.blue.length = 8; |
| fb->fb.var.green.offset = 8; |
| fb->fb.var.green.length = 8; |
| fb->fb.var.red.offset = 0; |
| fb->fb.var.red.length = 8; |
| break; |
| |
| default: |
| konafb_error("Wrong format!\n"); |
| break; |
| } |
| |
| fb->fb.fix.smem_start = phys_fbbase; |
| fb->fb.fix.smem_len = framesize; |
| |
| konafb_debug( |
| "Framebuffer starts at phys[0x%08x], da[0x%08x] and virt[0x%08x] with frame size[0x%08x]\n", |
| phys_fbbase, dma_addr, (uint32_t) fb->fb.screen_base, |
| framesize); |
| |
| ret = fb_set_var(&fb->fb, &fb->fb.var); |
| if (ret) { |
| konafb_error("fb_set_var failed\n"); |
| goto err_set_var_failed; |
| } |
| |
| if (need_map_switch) { |
| uboot_phys = readl(KONA_AXIPV_VA + REG_CUR_FRAME); |
| uboot_kvirt = ioremap_nocache(uboot_phys, framesize / 2); |
| pr_info("Copy uboot logo to fb, phys 0x%x kvirt 0x%x\n", |
| uboot_phys, (uint32_t)uboot_kvirt); |
| if (uboot_kvirt) { |
| /* memcopy uboot buffer to kernel's buff1 */ |
| memcpy(fb->fb.screen_base + (framesize / 2), |
| uboot_kvirt, framesize / 2); |
| iounmap(uboot_kvirt); |
| /* update display with 1:1 map */ |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| if (fb->display_info->clear_panel_ram) |
| (void)clear_panel_ram(fb); |
| ret = fb->display_ops->update(fb->display_hdl, |
| (void *)phys_fbbase + |
| (framesize / 2), NULL, NULL); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| if (ret) { |
| konafb_error("Can not enable the LCD!\n"); |
| goto err_fb_register_failed; |
| } |
| /* Wait new buffer. TODO: checking frame end */ |
| usleep_range(16666, 16668); |
| } |
| } |
| |
| #ifdef CONFIG_IOMMU_API |
| if (bcm_iommu_enable(&pdev->dev) < 0) |
| konafb_error("bcm_iommu_enable failed\n"); |
| #endif |
| if (!fb->display_info->vmode) |
| kona_clock_start(fb); |
| /* Paint it black (assuming default fb contents are all zero) */ |
| /* Or Paint it with the logo uboot has initliased */ |
| if (g_display_enabled && !fb->display_info->vmode) { |
| /* Keep the boot display for command display */ |
| } else { |
| if (fb->display_info->clear_panel_ram) |
| (void)clear_panel_ram(fb); |
| else |
| ret = fb->display_ops->update(fb->display_hdl, |
| fb->buff1, NULL, NULL); |
| } |
| if (ret) { |
| konafb_error("Can not enable the LCD!\n"); |
| /* Stop esc_clock in cmd mode since disable_display |
| * again starts clock for cmd mode */ |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| goto err_fb_register_failed; |
| } |
| |
| #ifdef CONFIG_LOGO |
| logo_rotate = fb->fb.var.rotate ? FB_ROTATE_UD : FB_ROTATE_UR; |
| fb_prepare_logo(&fb->fb, logo_rotate); |
| fb_show_logo(&fb->fb, logo_rotate); |
| mutex_lock(&fb->update_sem); |
| if (!fb->display_info->vmode) { |
| kona_clock_start(fb); |
| fb->display_ops->update(fb->display_hdl, fb->buff0, NULL, NULL); |
| kona_clock_stop(fb); |
| } else { |
| fb->display_ops->update(fb->display_hdl, fb->buff0, NULL, NULL); |
| } |
| mutex_unlock(&fb->update_sem); |
| #endif |
| |
| /* Display on after painted blank */ |
| fb->display_ops->power_control(fb->display_hdl, CTRL_SCREEN_ON); |
| if (!fb->display_info->vmode) |
| kona_clock_stop(fb); |
| |
| if (need_map_switch) { |
| /* To switch to new buffer. TODO: checking frame update end */ |
| usleep_range(16666, 16668); |
| #ifdef CONFIG_IOMMU_API |
| /* unmap the uboot direct map */ |
| kona_fb_direct_unmap(fb, framesize_alloc, direct_dma_addr); |
| #endif |
| } |
| |
| ret = register_framebuffer(&fb->fb); |
| if (ret) { |
| konafb_error("Framebuffer registration failed\n"); |
| goto err_fb_register_failed; |
| } |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| fb->fps_info = fb_fps_register(&fb->fb); |
| if (NULL == fb->fps_info) |
| printk(KERN_ERR "No fps display"); |
| #endif |
| |
| atomic_set(&fb->is_fb_registered, 1); |
| konafb_info("kona Framebuffer probe successfull\n"); |
| |
| if (fb->fb_data->esdcheck) { |
| uint32_t tectl_gpio = fb->fb_data->tectl_gpio; |
| konafb_info("Enable esd check function for lcd\n"); |
| if (tectl_gpio > 0) { |
| konafb_info("Enable TECTL gpio check\n"); |
| gpio_request(tectl_gpio, "tectl_gpio"); |
| gpio_direction_input(tectl_gpio); |
| ret = request_irq(gpio_to_irq(tectl_gpio), |
| kona_fb_esd_irq, IRQF_TRIGGER_FALLING, |
| "tectl_gpio", fb); |
| if (ret < 0) { |
| konafb_error("Request esd gpio irq fail\n"); |
| goto err_fb_register_failed; |
| } |
| } |
| disable_irq(gpio_to_irq(tectl_gpio)); |
| init_completion(&fb->tectl_gpio_done_sem); |
| |
| fb->esd_check_wq = |
| create_singlethread_workqueue("lcd_esd_check"); |
| INIT_DELAYED_WORK(&fb->esd_check_work, kona_fb_esd_check); |
| queue_delayed_work(fb->esd_check_wq, &fb->esd_check_work, |
| msecs_to_jiffies(fb->fb_data->esdcheck_period_ms)); |
| } |
| |
| |
| fb->proc_entry = proc_create_data("fb_debug", 0666, NULL, |
| &proc_fops, NULL); |
| if (NULL == fb->proc_entry) |
| printk(KERN_ERR "%s: could not create proc entry.\n", __func__); |
| |
| fb->reboot_nb.notifier_call = kona_fb_reboot_cb; |
| register_reboot_notifier(&fb->reboot_nb); |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| fb->die_nb.notifier_call = kona_fb_die_cb; |
| register_die_notifier(&fb->die_nb); |
| #endif |
| ret = register_attributes(&pdev->dev); |
| if (ret) |
| dev_err(&pdev->dev, "%s: failed to register attributes\n", |
| __func__); |
| #ifdef CONFIG_DEBUG_FS |
| kona_fb_create_debugfs(pdev); |
| #endif |
| |
| pr_info("%s(%d): Probe success. Panel: %s\n", |
| __func__, __LINE__, fb->fb_data->name); |
| return 0; |
| |
| err_fb_register_failed: |
| err_set_var_failed: |
| dma_free_writecombine(&pdev->dev, framesize_alloc, fb->fb.screen_base, |
| phys_fbbase); |
| err_fbmem_alloc_failed: |
| disable_display(fb); |
| err_enable_display_failed: |
| release_dispdrv_info(fb->display_info); |
| dispdrv_data_failed: |
| free_platform_data(&pdev->dev); |
| fb_data_failed: |
| if (pi_mgr_dfs_request_remove(&fb->dfs_node)) |
| printk(KERN_ERR "Failed to remove dfs request for LCD\n"); |
| fb_dfs_fail: |
| kfree(fb); |
| g_kona_fb = NULL; |
| err_fb_alloc_failed: |
| pr_err("%s failed ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| static int kona_fb_reboot_cb(struct notifier_block *nb, |
| unsigned long val, void *v) |
| { |
| struct kona_fb *fb = container_of(nb, struct kona_fb, reboot_nb); |
| struct fb_event event; |
| int blank = FB_BLANK_POWERDOWN; |
| |
| pr_err("Turning off display\n"); |
| if (fb->g_stop_drawing) { |
| pr_err("Display is already suspended, nothing to do\n"); |
| goto exit; |
| } |
| |
| /*shut down the backlight before disable the display*/ |
| pr_info("Turning off backlight\r\n"); |
| event.info = &fb->fb; |
| event.data = ␣ |
| fb_notifier_call_chain(FB_EVENT_BLANK, &event); |
| |
| mutex_lock(&fb->update_sem); |
| if (fb->link_suspended) |
| link_control(fb, RESUME_LINK); |
| |
| fb->g_stop_drawing = 1; |
| if (!fb->display_info->vmode) { |
| if (wait_for_completion_timeout( |
| &fb->prev_buf_done_sem, msecs_to_jiffies(10000)) <= 0) |
| pr_err("%s:%d timed out waiting for completion", |
| __func__, __LINE__); |
| kona_clock_start(fb); |
| fb->display_ops->power_control( |
| fb->display_hdl, CTRL_SCREEN_OFF); |
| kona_clock_stop(fb); |
| } else { |
| fb->display_ops->power_control( |
| fb->display_hdl, CTRL_SCREEN_OFF); |
| } |
| disable_display(fb); |
| mutex_unlock(&fb->update_sem); |
| pr_err("Display disabled\n"); |
| exit: |
| return 0; |
| } |
| |
| #ifdef CONFIG_FB_BRCM_CP_CRASH_DUMP_IMAGE_SUPPORT |
| static int kona_fb_die_cb(struct notifier_block *nb, unsigned long val, void *v) |
| { |
| pr_err("kona_fb: die notifier invoked\n"); |
| if (!crash_dump_ui_on) { |
| if (ramdump_enable) |
| kona_display_crash_image(AP_RAM_DUMP_START); |
| else |
| kona_display_crash_image(AP_CRASH_DUMP_START); |
| crash_dump_ui_on = 1; |
| } |
| return NOTIFY_DONE; |
| } |
| #endif |
| |
| static int kona_fb_remove(struct platform_device *pdev) |
| { |
| struct kona_fb *fb = platform_get_drvdata(pdev); |
| struct kona_fb_platform_data *pdata = (struct kona_fb_platform_data *) |
| pdev->dev.platform_data; |
| |
| remove_attributes(&pdev->dev); |
| #ifdef CONFIG_DEBUG_FS |
| kona_fb_remove_debugfs(pdev); |
| #endif |
| #ifdef CONFIG_FRAMEBUFFER_FPS |
| fb_fps_unregister(fb->fps_info); |
| #endif |
| unregister_framebuffer(&fb->fb); |
| disable_display(fb); |
| #ifdef CONFIG_IOMMU_API |
| #ifdef CONFIG_BCM_IOVMM |
| kona_fb_iovmm_unmap(fb, fb->framesize_alloc, (dma_addr_t)fb->buff0); |
| #else |
| kona_fb_direct_unmap(fb, fb->framesize_alloc, (dma_addr_t)fb->buff0); |
| #endif |
| #endif |
| if (pdev->dev.of_node) { |
| kfree(pdata); |
| pdev->dev.platform_data = NULL; |
| } |
| |
| wake_lock_destroy(&fb->wlock); |
| |
| kfree(fb); |
| konafb_info("kona FB removed !!\n"); |
| return 0; |
| } |
| |
| static const struct of_device_id kona_fb_of_match[] = { |
| { .compatible = "bcm,kona-fb", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, kona_fb_of_match); |
| |
| |
| static struct platform_driver kona_fb_driver = { |
| .probe = kona_fb_probe, |
| .remove = kona_fb_remove, |
| .driver = { |
| .name = "kona_fb", |
| .owner = THIS_MODULE, |
| .of_match_table = kona_fb_of_match, |
| }, |
| }; |
| |
| static int __init kona_fb_init(void) |
| { |
| int ret; |
| |
| ret = |
| pi_mgr_qos_add_request(&g_mm_qos_node, "lcd", PI_MGR_PI_ID_MM, 10); |
| if (ret) |
| printk(KERN_ERR "failed to register qos client for lcd\n"); |
| |
| ret = platform_driver_register(&kona_fb_driver); |
| if (ret) { |
| printk(KERN_ERR |
| "%s : Unable to register kona framebuffer driver\n", |
| __func__); |
| goto fail_to_register; |
| } |
| fail_to_register: |
| printk(KERN_INFO "BRCM Framebuffer Init %s !\n", ret ? "FAILED" : "OK"); |
| |
| return ret; |
| } |
| |
| static void __exit kona_fb_exit(void) |
| { |
| platform_driver_unregister(&kona_fb_driver); |
| printk(KERN_INFO "BRCM Framebuffer exit OK\n"); |
| } |
| |
| module_init(kona_fb_init); |
| module_exit(kona_fb_exit); |
| |
| MODULE_AUTHOR("Broadcom"); |
| MODULE_DESCRIPTION("KONA FB Driver"); |