| /* |
| * drivers/amlogic/media/osd/osd_virtual.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| */ |
| |
| /* Linux Headers */ |
| #include <linux/version.h> |
| #include <linux/compat.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/ctype.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/interrupt.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/fb.h> |
| #include <linux/spinlock.h> |
| #include <linux/fs.h> |
| #include <linux/sysfs.h> |
| #include <linux/file.h> |
| #include <linux/fdtable.h> |
| #include <linux/console.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/of_fdt.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/uaccess.h> |
| #include <linux/dma-mapping.h> |
| #include <ion/ion.h> |
| #include <meson_ion.h> |
| #include <linux/fs.h> |
| #include <linux/cma.h> |
| #include <linux/dma-contiguous.h> |
| #include <linux/delay.h> |
| |
| /* Amlogic Headers */ |
| #include <linux/amlogic/media/vout/vinfo.h> |
| #include <linux/amlogic/media/vout/vout_notify.h> |
| |
| /* Local Headers */ |
| #ifdef CONFIG_AMLOGIC_LCD_SPI |
| #include "../vout/spi/lcd_spi_api.h" |
| #endif |
| #ifdef CONFIG_AMLOGIC_PERIPHERAL_LCD |
| #include "../vout/peripheral_lcd/peripheral_lcd_drv.h" |
| #endif |
| #include "osd_log.h" |
| #include "osd_fb.h" |
| #include "osd_virtual.h" |
| |
| #define SOFTWARE_VSYNC |
| |
| #ifdef CONFIG_AMLOGIC_PERIPHERAL_LCD |
| #undef HW_VSYNC |
| #define SOFTWARE_VSYNC |
| #endif |
| |
| #ifdef CONFIG_AMLOGIC_LCD_SPI |
| /* #define SPI_DEBUG */ |
| #undef SOFTWARE_VSYNC |
| #define HW_VSYNC |
| #endif |
| |
| #define DEFAULT_FPS (HZ/25) |
| static u32 fb_memsize; |
| static __u32 var_screeninfo[5]; |
| static bool b_reserved_mem; |
| static int ready_post; |
| static int start_post; |
| static struct fb_virtual_dev_s *fb_vir_dev; |
| static struct virt_fb_para_s virt_fb; |
| static struct fb_var_screeninfo fb_def_var = { |
| |
| .xres = 240, |
| .yres = 320, |
| .xres_virtual = 240, |
| .yres_virtual = 640, |
| .xoffset = 0, |
| .yoffset = 0, |
| .bits_per_pixel = 16, |
| .grayscale = 0, |
| .red = {0, 0, 0}, |
| .green = {0, 0, 0}, |
| .blue = {0, 0, 0}, |
| .transp = {0, 0, 0}, |
| .nonstd = 0, |
| .activate = FB_ACTIVATE_NOW, |
| .height = -1, |
| .width = -1, |
| .accel_flags = 0, |
| .pixclock = 10, |
| .left_margin = 0, |
| .right_margin = 0, |
| .upper_margin = 0, |
| .lower_margin = 0, |
| .hsync_len = 0, |
| .vsync_len = 0, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .rotate = 0, |
| }; |
| |
| static struct fb_fix_screeninfo fb_def_fix = { |
| .id = "VIRTUAL FB", |
| .xpanstep = 1, |
| .ypanstep = 1, |
| .type = FB_TYPE_PACKED_PIXELS, |
| .visual = FB_VISUAL_TRUECOLOR, |
| .accel = FB_ACCEL_NONE, |
| }; |
| |
| #ifdef SPI_DEBUG |
| static int spi_write_min; |
| module_param(spi_write_min, int, 0664); |
| MODULE_PARM_DESC(spi_write_min, "spi_write_min"); |
| |
| static int spi_write_max; |
| module_param(spi_write_max, int, 0664); |
| MODULE_PARM_DESC(spi_write_max, "spi_write_max"); |
| #endif |
| |
| static void lcd_init(void) |
| { |
| /* set gamma */ |
| } |
| |
| static int lcd_set_format(int color_format) |
| { |
| /* COLOR_INDEX_16_565 */ |
| return 0; |
| |
| } |
| |
| static void lcd_enable(int blank) |
| { |
| |
| } |
| |
| static void fb_get_fps(u32 index, u32 *osd_fps) |
| { |
| *osd_fps = virt_fb.osd_fps; |
| } |
| |
| static void fb_set_fps(u32 index, u32 osd_fps_start) |
| { |
| static int stime, etime; |
| int osd_fps; |
| |
| virt_fb.osd_fps_start = osd_fps_start; |
| if (osd_fps_start) { |
| /* start to calc fps */ |
| stime = ktime_to_us(ktime_get()); |
| virt_fb.osd_fps = 0; |
| } else { |
| /* stop to calc fps */ |
| etime = ktime_to_us(ktime_get()); |
| osd_fps = |
| (virt_fb.osd_fps * 1000000) |
| / (etime - stime); |
| osd_log_info("osd fps:=%d\n", osd_fps); |
| } |
| } |
| |
| static ssize_t show_fb_fps(struct device *device, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct fb_info *fb_info = dev_get_drvdata(device); |
| u32 fb_fps; |
| |
| fb_get_fps(fb_info->node, &fb_fps); |
| return snprintf(buf, 40, "%d\n", |
| fb_fps); |
| } |
| |
| static ssize_t store_fb_fps(struct device *device, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct fb_info *fb_info = dev_get_drvdata(device); |
| int res = 0; |
| int ret = 0; |
| |
| ret = kstrtoint(buf, 0, &res); |
| fb_set_fps(fb_info->node, res); |
| |
| return count; |
| } |
| |
| static ssize_t show_log_level(struct device *device, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 40, "%d\n", osd_log_level); |
| } |
| |
| static ssize_t store_log_level(struct device *device, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int res = 0; |
| int ret = 0; |
| |
| ret = kstrtoint(buf, 0, &res); |
| osd_log_info("log_level: %d->%d\n", osd_log_level, res); |
| osd_log_level = res; |
| |
| return count; |
| } |
| |
| static ssize_t show_log_module(struct device *device, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 40, "0x%x\n", osd_log_module); |
| } |
| |
| static ssize_t store_log_module(struct device *device, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int res = 0; |
| int ret = 0; |
| |
| ret = kstrtoint(buf, 0, &res); |
| osd_log_info("log_module: 0x%x->0x%x\n", osd_log_module, res); |
| osd_log_module = res; |
| |
| return count; |
| } |
| |
| static int virt_osd_check_var(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct fb_fix_screeninfo *fix; |
| struct fb_virtual_dev_s *fbdev = (struct fb_virtual_dev_s *)info->par; |
| const struct color_bit_define_s *color_format_pt; |
| |
| fix = &info->fix; |
| color_format_pt = _find_color_format(var); |
| if (color_format_pt == NULL || color_format_pt->color_index == 0) |
| return -EFAULT; |
| |
| osd_log_dbg(MODULE_BASE, "select color format :index %d, bpp %d\n", |
| color_format_pt->color_index, |
| color_format_pt->bpp); |
| fbdev->color = color_format_pt; |
| var->red.offset = color_format_pt->red_offset; |
| var->red.length = color_format_pt->red_length; |
| var->red.msb_right = color_format_pt->red_msb_right; |
| var->green.offset = color_format_pt->green_offset; |
| var->green.length = color_format_pt->green_length; |
| var->green.msb_right = color_format_pt->green_msb_right; |
| var->blue.offset = color_format_pt->blue_offset; |
| var->blue.length = color_format_pt->blue_length; |
| var->blue.msb_right = color_format_pt->blue_msb_right; |
| var->transp.offset = color_format_pt->transp_offset; |
| var->transp.length = color_format_pt->transp_length; |
| var->transp.msb_right = color_format_pt->transp_msb_right; |
| var->bits_per_pixel = color_format_pt->bpp; |
| osd_log_dbg(MODULE_BASE, "rgba(L/O):%d/%d-%d/%d-%d/%d-%d/%d\n", |
| var->red.length, var->red.offset, |
| var->green.length, var->green.offset, |
| var->blue.length, var->blue.offset, |
| var->transp.length, var->transp.offset); |
| fix->visual = color_format_pt->color_type; |
| /* adjust memory length. */ |
| fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; |
| osd_log_dbg(MODULE_BASE, "xvirtual=%d, bpp:%d, line_length=%d\n", |
| var->xres_virtual, var->bits_per_pixel, fix->line_length); |
| |
| if (var->xres_virtual < var->xres) |
| var->xres_virtual = var->xres; |
| if (var->yres_virtual < var->yres) |
| var->yres_virtual = var->yres; |
| var->left_margin = var->right_margin = 0; |
| var->upper_margin = var->lower_margin = 0; |
| if (var->xres + var->xoffset > var->xres_virtual) |
| var->xoffset = var->xres_virtual - var->xres; |
| if (var->yres + var->yoffset > var->yres_virtual) |
| var->yoffset = var->yres_virtual - var->yres; |
| return 0; |
| } |
| |
| static int virt_osd_set_par(struct fb_info *info) |
| { |
| struct fb_virtual_dev_s *fbdev = (struct fb_virtual_dev_s *)info->par; |
| const struct color_bit_define_s *color_format_pt; |
| struct fb_var_screeninfo *var = NULL; |
| u32 stride, fb_len; |
| |
| var = &info->var; |
| if (!var) |
| return -EFAULT; |
| stride = var->xres * (var->bits_per_pixel >> 3); |
| fb_len = stride * var->yres; |
| osd_log_info("%s:stride=%d, fb_len=%d\n", __func__, stride, fb_len); |
| virt_fb.screen_size = fb_len; |
| virt_fb.offset = 0; |
| virt_fb.stride = stride; |
| color_format_pt = _find_color_format(&info->var); |
| if (color_format_pt == NULL || color_format_pt->color_index == 0) |
| return -EFAULT; |
| fbdev->color = color_format_pt; |
| lcd_set_format(fbdev->color->color_index); |
| return 0; |
| } |
| |
| static int virt_osd_check_fbsize(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct fb_virtual_dev_s *fbdev = (struct fb_virtual_dev_s *)info->par; |
| |
| if (var->xres_virtual * var->yres_virtual * |
| var->bits_per_pixel / 8 > fbdev->fb_len) { |
| osd_log_err("no enough memory for %d*%d*%d\n", |
| var->xres, |
| var->yres, |
| var->bits_per_pixel); |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| #ifdef SOFTWARE_VSYNC |
| s64 virt_osd_wait_vsync_event(void) |
| { |
| int ret; |
| unsigned long timeout; |
| ktime_t stime; |
| |
| timeout = msecs_to_jiffies(2000); |
| /* waiting for 1s. */ |
| ret = wait_for_completion_timeout(&fb_vir_dev->fb_com, |
| timeout); |
| if (ret == 0) |
| pr_err("software vsync timeout\n"); |
| stime = ktime_get(); |
| osd_log_dbg(MODULE_BASE, "%s\n", __func__); |
| |
| return stime.tv64; |
| } |
| |
| static void sw_vsync_timer_func(unsigned long arg) |
| { |
| struct fb_virtual_dev_s *fbdev = |
| (struct fb_virtual_dev_s *)arg; |
| |
| /* gen complete signal*/ |
| complete(&fbdev->timer_com); |
| } |
| |
| static void lcd_post_frame(u32 addr) |
| { |
| unsigned char *fb_data; |
| #ifdef SPI_DEBUG |
| int stime, etime, time_write; |
| static int cnt; |
| |
| stime = ktime_to_us(ktime_get()); |
| #endif |
| |
| start_post = 1; |
| /* frame post*/ |
| fb_data = virt_fb.screen_base_vaddr + addr; |
| |
| #if defined(CONFIG_AMLOGIC_LCD_SPI) || defined(CONFIG_AMLOGIC_PERIPHERAL_LCD) |
| frame_post(fb_data, 0, virt_fb.xres, 0, virt_fb.yres); |
| #endif |
| |
| #ifdef SPI_DEBUG |
| etime = ktime_to_us(ktime_get()); |
| time_write = etime - stime; |
| cnt++; |
| if (cnt == 1) { |
| spi_write_min = time_write; |
| spi_write_max = time_write; |
| } |
| if (time_write < spi_write_min) |
| spi_write_min = time_write; |
| if (time_write > spi_write_max) |
| spi_write_max = time_write; |
| #endif |
| /* gen complete signal*/ |
| complete(&fb_vir_dev->post_com); |
| /*start_post = 0; */ |
| osd_log_dbg(MODULE_BASE, "lcd_post_frame:=>addr 0x%x\n", |
| addr); |
| } |
| |
| static int fb_monitor_thread(void *data) |
| { |
| int ret; |
| struct fb_virtual_dev_s *fbdev = fb_vir_dev; |
| |
| osd_log_info("fb monitor start\n"); |
| while (fbdev->fb_monitor_run) { |
| ret = wait_for_completion_timeout(&fb_vir_dev->timer_com, |
| msecs_to_jiffies(2000)); |
| if (ret == 0) |
| pr_err("timer post frame timeout\n"); |
| |
| if (start_post) { |
| /* waiting for 1s. */ |
| ret = wait_for_completion_timeout(&fb_vir_dev->post_com, |
| msecs_to_jiffies(200)); |
| if (ret == 0) |
| pr_err("post frame timeout\n"); |
| } |
| /* gen complete signal*/ |
| complete(&fbdev->fb_com); |
| |
| /* Todo: if frame_post started, wait frame_pos finished*/ |
| fbdev->timer.expires = jiffies + DEFAULT_FPS; |
| add_timer(&fbdev->timer); |
| start_post = 0; |
| } |
| osd_log_info("exit fb_monitor_thread\n"); |
| return 0; |
| } |
| |
| int te_cb(void) |
| { |
| return 0; |
| } |
| #endif |
| |
| #ifdef HW_VSYNC |
| s64 virt_osd_wait_vsync_event(void) |
| { |
| int ret; |
| unsigned long timeout; |
| ktime_t stime; |
| |
| timeout = msecs_to_jiffies(2000); |
| /* waiting for 1s. */ |
| ret = wait_for_completion_timeout(&fb_vir_dev->fb_com, |
| timeout); |
| if (ret == 0) |
| pr_err("software vsync timeout\n"); |
| stime = ktime_get(); |
| osd_log_dbg(MODULE_BASE, "%s\n", __func__); |
| |
| return stime.tv64; |
| } |
| |
| static void lcd_post_frame(u32 addr) |
| { |
| unsigned char *fb_data; |
| #ifdef SPI_DEBUG |
| int stime, etime, time_write; |
| static int cnt; |
| |
| stime = ktime_to_us(ktime_get()); |
| #endif |
| /* frame post*/ |
| fb_data = virt_fb.screen_base_vaddr + addr; |
| |
| #if defined(CONFIG_AMLOGIC_LCD_SPI) || defined(CONFIG_AMLOGIC_PERIPHERAL_LCD) |
| start_post = 1; |
| frame_post(fb_data, 0, virt_fb.xres, 0, virt_fb.yres); |
| start_post = 0; |
| #endif |
| |
| #ifdef SPI_DEBUG |
| etime = ktime_to_us(ktime_get()); |
| time_write = etime - stime; |
| cnt++; |
| if (cnt == 1) { |
| spi_write_min = time_write; |
| spi_write_max = time_write; |
| } |
| if (time_write < spi_write_min) |
| spi_write_min = time_write; |
| if (time_write > spi_write_max) |
| spi_write_max = time_write; |
| #endif |
| osd_log_dbg(MODULE_BASE, "lcd_post_frame:=>addr 0x%x\n", |
| addr); |
| } |
| |
| static int fb_monitor_thread(void *data) |
| { |
| struct fb_virtual_dev_s *fbdev = fb_vir_dev; |
| |
| osd_log_info("fb monitor start\n"); |
| ready_post = 0; |
| while (fbdev->fb_monitor_run) { |
| /* waiting for 1s. */ |
| wait_for_completion(&fb_vir_dev->post_com); |
| /* call frame_post*/ |
| lcd_post_frame(virt_fb.offset); |
| /* gen complete signal*/ |
| complete(&fbdev->fb_com); |
| } |
| osd_log_info("exit fb_monitor_thread\n"); |
| return 0; |
| } |
| |
| int te_cb(void) |
| { |
| if (ready_post && (start_post == 0)) { |
| /* gen complete signal*/ |
| complete(&fb_vir_dev->post_com); |
| ready_post = 0; |
| } |
| return 0; |
| } |
| #endif |
| EXPORT_SYMBOL(te_cb); |
| |
| static int virt_fb_start_monitor(void) |
| { |
| int ret = 0; |
| struct fb_virtual_dev_s *fbdev = fb_vir_dev; |
| |
| osd_log_info("virt fb start monitor\n"); |
| fbdev->fb_monitor_run = 1; |
| fbdev->fb_thread = kthread_run(fb_monitor_thread, |
| &fbdev, "fb_monitor"); |
| if (IS_ERR(fbdev->fb_thread)) { |
| ret = PTR_ERR(fbdev->fb_thread); |
| osd_log_err("osd failed to start kthread (%d)\n", ret); |
| } |
| return ret; |
| } |
| |
| static int virt_fb_stop_monitor(void) |
| { |
| struct fb_virtual_dev_s *fbdev = fb_vir_dev; |
| |
| osd_log_info("stop virt fb monitor thread\n"); |
| if (fbdev->fb_thread) { |
| fbdev->fb_monitor_run = 0; |
| kthread_stop(fbdev->fb_thread); |
| fbdev->fb_thread = NULL; |
| } |
| return 0; |
| } |
| |
| static void *aml_mm_vmap(phys_addr_t phys, unsigned long size) |
| { |
| u32 offset, npages; |
| struct page **pages = NULL; |
| pgprot_t pgprot = PAGE_KERNEL; |
| void *vaddr; |
| int i; |
| |
| offset = offset_in_page(phys); |
| npages = DIV_ROUND_UP(size + offset, PAGE_SIZE); |
| |
| pages = vmalloc(sizeof(struct page *) * npages); |
| if (!pages) |
| return NULL; |
| for (i = 0; i < npages; i++) { |
| pages[i] = phys_to_page(phys); |
| phys += PAGE_SIZE; |
| } |
| vaddr = vmap(pages, npages, VM_MAP, pgprot); |
| if (!vaddr) { |
| pr_err("vmaped fail, size: %d\n", |
| npages << PAGE_SHIFT); |
| vfree(pages); |
| return NULL; |
| } |
| vfree(pages); |
| osd_log_info("[HIGH-MEM-MAP] pa(%lx) to va(%p), size: %d\n", |
| (unsigned long)phys, vaddr, npages << PAGE_SHIFT); |
| return vaddr; |
| } |
| |
| static void *aml_map_phyaddr_to_virt(dma_addr_t phys, unsigned long size) |
| { |
| void *vaddr = NULL; |
| |
| if (!PageHighMem(phys_to_page(phys))) |
| return phys_to_virt(phys); |
| vaddr = aml_mm_vmap(phys, size); |
| return vaddr; |
| } |
| |
| static int malloc_fb_memory(struct fb_info *info) |
| { |
| int ret = 0; |
| u32 fb_index, stride, fb_len; |
| struct fb_virtual_dev_s *fbdev; |
| struct fb_fix_screeninfo *fix = NULL; |
| struct fb_var_screeninfo *var = NULL; |
| struct platform_device *pdev = NULL; |
| phys_addr_t base = 0; |
| unsigned long size = 0; |
| unsigned long fb_memsize_total = 0; |
| struct cma *cma = NULL; |
| |
| static void __iomem *fb_rmem_vaddr; |
| static phys_addr_t fb_rmem_paddr; |
| static struct page *fb_page; |
| static struct ion_client *fb_ion_client; |
| static struct ion_handle *fb_ion_handle; |
| |
| cma = dev_get_cma_area(info->device); |
| if (cma) { |
| base = cma_get_base(cma); |
| size = cma_get_size(cma); |
| pr_info("%s, cma:%p\n", __func__, cma); |
| } |
| |
| osd_log_info("%s, %d, base:%pa, size:%ld\n", |
| __func__, __LINE__, &base, size); |
| fbdev = (struct fb_virtual_dev_s *)info->par; |
| pdev = fbdev->dev; |
| fb_index = fbdev->fb_index; |
| fix = &info->fix; |
| var = &info->var; |
| fb_ion_client = meson_ion_client_create(-1, "meson-fb"); |
| fb_memsize_total = fb_memsize; |
| /* read cma/fb-reserved memory first */ |
| if ((b_reserved_mem == true) && |
| (fb_memsize_total <= size) && |
| (fb_memsize > 0)) { |
| pr_info("%s, %d, fb_index=%d,fb_rmem_size=%d\n", |
| __func__, __LINE__, fb_index, |
| fb_memsize); |
| fb_page = dma_alloc_from_contiguous( |
| info->device, |
| fb_memsize >> PAGE_SHIFT, |
| 0); |
| if (!fb_page) { |
| pr_err("allocate buffer failed:%d\n", fb_memsize); |
| return -ENOMEM; |
| } |
| fb_rmem_paddr = page_to_phys(fb_page); |
| fb_rmem_vaddr = |
| aml_map_phyaddr_to_virt(fb_rmem_paddr, fb_memsize); |
| osd_log_dbg(MODULE_BASE, "fb_index=%d dma_alloc=0x%x\n", |
| fb_index, fb_memsize); |
| } else { |
| #ifdef CONFIG_AMLOGIC_ION |
| pr_info("use ion buffer for fb memory, fb_index=%d\n", |
| fb_index); |
| fb_ion_handle = |
| ion_alloc(fb_ion_client, |
| fb_memsize, |
| 0, |
| (1 << ION_HEAP_TYPE_DMA), |
| 0); |
| if (IS_ERR(fb_ion_handle)) { |
| osd_log_err("%s: size=%x, FAILED.\n", |
| __func__, fb_memsize); |
| return -ENOMEM; |
| } |
| ret = ion_phys(fb_ion_client, |
| fb_ion_handle, |
| (ion_phys_addr_t *) |
| &fb_rmem_paddr, |
| (size_t *)&fb_memsize_total); |
| fb_rmem_vaddr = |
| ion_map_kernel(fb_ion_client, |
| fb_ion_handle); |
| dev_notice(&pdev->dev, |
| "create ion_client %p, handle=%p\n", |
| fb_ion_client, |
| fb_ion_handle); |
| dev_notice(&pdev->dev, |
| "ion memory(%d): created fb at 0x%p, size %ld MiB\n", |
| fb_index, |
| (void *)fb_rmem_paddr, |
| (unsigned long) |
| fb_memsize / SZ_1M); |
| #endif |
| } |
| fbdev->fb_len = fb_memsize; |
| fbdev->fb_mem_paddr = fb_rmem_paddr; |
| fbdev->fb_mem_vaddr = fb_rmem_vaddr; |
| if (!fbdev->fb_mem_vaddr) { |
| osd_log_err("failed to ioremap frame buffer\n"); |
| return -ENOMEM; |
| } |
| osd_log_info("Frame buffer memory assigned at"); |
| osd_log_info(" %d, phy: 0x%p, vir:0x%p, size=%dK\n\n", |
| fb_index, (void *)fbdev->fb_mem_paddr, |
| fbdev->fb_mem_vaddr, fbdev->fb_len >> 10); |
| stride = var->xres * (var->bits_per_pixel >> 3); |
| fb_len = stride * var->yres_virtual; |
| fix->smem_start = fbdev->fb_mem_paddr; |
| if (fb_len > fbdev->fb_len) |
| fb_len = fbdev->fb_len; |
| osd_log_info(" %d, stride=%d,var->yres_virtual=%d,fb_len=%d\n", |
| fb_index, stride, var->yres_virtual, fb_len); |
| fix->smem_len = fb_len; |
| info->screen_base = (char __iomem *)fbdev->fb_mem_vaddr; |
| info->screen_size = fb_len; |
| virt_fb.screen_base_paddr = fbdev->fb_mem_paddr; |
| virt_fb.screen_base_vaddr = fbdev->fb_mem_vaddr; |
| virt_fb.screen_size = stride * var->yres; |
| virt_fb.offset = 0; |
| virt_fb.stride = stride; |
| virt_fb.xres = var->xres; |
| virt_fb.yres = var->yres; |
| if (virt_osd_check_fbsize(var, info)) |
| return -ENOMEM; |
| /* clear osd buffer */ |
| osd_log_info("---------------clear fb%d memory %p\n", |
| fb_index, fbdev->fb_mem_vaddr); |
| if (fbdev->fb_mem_vaddr) |
| memset(fbdev->fb_mem_vaddr, 0x0, fb_len); |
| return 0; |
| } |
| |
| static int virt_osd_open(struct fb_info *info, int arg) |
| { |
| struct fb_virtual_dev_s *fbdev; |
| int ret = 0; |
| |
| fbdev = (struct fb_virtual_dev_s *)info->par; |
| fbdev->open_count++; |
| osd_log_dbg(MODULE_BASE, "osd_open index=%d,open_count=%d\n", |
| fbdev->fb_index, fbdev->open_count); |
| if (info->screen_base != NULL) |
| return 0; |
| |
| if (info->screen_base == NULL) { |
| ret = malloc_fb_memory(info); |
| if (ret < 0) |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int virt_osd_mmap(struct fb_info *info, struct vm_area_struct *vma) |
| { |
| unsigned long mmio_pgoff; |
| unsigned long start; |
| u32 len; |
| |
| start = info->fix.smem_start; |
| len = info->fix.smem_len; |
| mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT; |
| if (vma->vm_pgoff >= mmio_pgoff) { |
| if (info->var.accel_flags) { |
| return -EINVAL; |
| } |
| |
| vma->vm_pgoff -= mmio_pgoff; |
| start = info->fix.mmio_start; |
| len = info->fix.mmio_len; |
| } |
| |
| vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| return vm_iomap_memory(vma, start, len); |
| } |
| |
| int virt_osd_blank(int blank_mode, struct fb_info *info) |
| { |
| osd_log_dbg(MODULE_BASE, "blank_mode=%d\n", |
| blank_mode); |
| lcd_enable((blank_mode != 0) ? 0 : 1); |
| return 0; |
| } |
| |
| static int virt_osd_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *fbi) |
| { |
| long diff_x, diff_y; |
| u32 offset = 0, size = 0, stride = 0; |
| |
| if (var->xoffset != virt_fb.pandata.x_start |
| || var->yoffset != virt_fb.pandata.y_start) { |
| diff_x = var->xoffset - virt_fb.pandata.x_start; |
| diff_y = var->yoffset - virt_fb.pandata.y_start; |
| virt_fb.pandata.x_start += diff_x; |
| virt_fb.pandata.x_end += diff_x; |
| virt_fb.pandata.y_start += diff_y; |
| virt_fb.pandata.y_end += diff_y; |
| if (virt_fb.osd_fps_start) |
| virt_fb.osd_fps++; |
| stride = var->xres * (var->bits_per_pixel >> 3); |
| offset = stride * virt_fb.pandata.y_start; |
| virt_fb.offset = offset; |
| size = stride * var->yres; |
| virt_fb.size = size; |
| ready_post = 1; |
| osd_log_dbg(MODULE_BASE, "offset[%d-%d]x[%d-%d]y[%d-%d]\n", |
| var->xoffset, var->yoffset, |
| virt_fb.pandata.x_start, |
| virt_fb.pandata.x_end, |
| virt_fb.pandata.y_start, |
| virt_fb.pandata.y_end); |
| #ifdef SOFTWARE_VSYNC |
| lcd_post_frame(offset); |
| #endif |
| } |
| return 0; |
| } |
| |
| |
| static int virt_osd_release(struct fb_info *info, int arg) |
| { |
| struct osd_fb_dev_s *fbdev; |
| int err = 0; |
| |
| fbdev = (struct osd_fb_dev_s *)info->par; |
| |
| if (!fbdev->open_count) { |
| err = -EINVAL; |
| osd_log_info("osd already released. index=%d\n", |
| fbdev->fb_index); |
| goto done; |
| } else if (fbdev->open_count == 1) { |
| osd_log_info("osd_release now.index=%d,open_count=%d\n", |
| fbdev->fb_index, fbdev->open_count); |
| } |
| fbdev->open_count--; |
| done: |
| return err; |
| } |
| |
| static int virt_osd_ioctl(struct fb_info *info, |
| unsigned int cmd, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| u32 blank = 0; |
| int ret = 0; |
| s32 vsync_timestamp; |
| |
| switch (cmd) { |
| case FBIO_WAITFORVSYNC: |
| vsync_timestamp = (s32)virt_osd_wait_vsync_event(); |
| ret = copy_to_user(argp, &vsync_timestamp, sizeof(s32)); |
| break; |
| case FBIOPUT_OSD_BLANK: |
| ret = copy_from_user(&blank, argp, sizeof(u32)); |
| lcd_enable((blank != 0) ? 0 : 1); |
| break; |
| default: |
| osd_log_err("command 0x%x not supported (%s)\n", |
| cmd, current->comm); |
| return -1; |
| } |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static int virt_osd_compat_ioctl(struct fb_info *info, |
| unsigned int cmd, unsigned long arg) |
| { |
| unsigned long ret; |
| |
| arg = (unsigned long)compat_ptr(arg); |
| ret = virt_osd_ioctl(info, cmd, arg); |
| return ret; |
| } |
| #endif |
| |
| static void fbdev_virt_set_default(struct fb_virtual_dev_s *fbdev, int index) |
| { |
| /* setup default value */ |
| fbdev->fb_info->var = fb_def_var; |
| fbdev->fb_info->fix = fb_def_fix; |
| fbdev->color = _find_color_format(&fbdev->fb_info->var); |
| } |
| |
| static struct device_attribute virt_fb_attrs[] = { |
| #if 0 |
| __ATTR(debug, 0644, |
| show_debug, store_debug), |
| #endif |
| __ATTR(log_level, 0644, |
| show_log_level, store_log_level), |
| __ATTR(log_module, 0644, |
| show_log_module, store_log_module), |
| __ATTR(fps, 0644, |
| show_fb_fps, store_fb_fps), |
| }; |
| |
| /* fb_ops structures */ |
| static struct fb_ops virtual_fb_ops = { |
| .owner = THIS_MODULE, |
| .fb_check_var = virt_osd_check_var, |
| .fb_set_par = virt_osd_set_par, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| .fb_ioctl = virt_osd_ioctl, |
| #ifdef CONFIG_COMPAT |
| .fb_compat_ioctl = virt_osd_compat_ioctl, |
| #endif |
| .fb_open = virt_osd_open, |
| .fb_mmap = virt_osd_mmap, |
| .fb_blank = virt_osd_blank, |
| .fb_pan_display = virt_osd_pan_display, |
| .fb_release = virt_osd_release, |
| }; |
| |
| int amlfb_virtual_probe(struct platform_device *pdev) |
| { |
| struct fb_info *fbi = NULL; |
| struct fb_var_screeninfo *var; |
| struct fb_fix_screeninfo *fix; |
| int index = 0, bpp; |
| struct fb_virtual_dev_s *fbdev = NULL; |
| int i; |
| int ret = 0; |
| |
| /* get buffer size from dt */ |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "mem_size", &fb_memsize); |
| if (ret) { |
| osd_log_err("not found mem_size from dtd\n"); |
| goto failed1; |
| } |
| if (!fb_memsize) |
| goto failed1; |
| osd_log_info("fb_memsize=0x%x\n", fb_memsize); |
| /* init reserved memory */ |
| ret = of_reserved_mem_device_init(&pdev->dev); |
| if (ret != 0) { |
| osd_log_err("failed to init reserved memory\n"); |
| b_reserved_mem = false; |
| } else { |
| b_reserved_mem = true; |
| } |
| /* register frame buffer memory */ |
| fbi = framebuffer_alloc(sizeof(struct fb_virtual_dev_s), |
| &pdev->dev); |
| if (!fbi) { |
| ret = -ENOMEM; |
| goto failed1; |
| } |
| fbdev = (struct fb_virtual_dev_s *)fbi->par; |
| fbdev->fb_index = 0; |
| fbdev->fb_info = fbi; |
| fbdev->dev = pdev; |
| mutex_init(&fbdev->lock); |
| var = &fbi->var; |
| fix = &fbi->fix; |
| fb_vir_dev = fbdev; |
| fbdev->fb_len = 0; |
| fbdev->fb_mem_paddr = 0; |
| fbdev->fb_mem_vaddr = 0; |
| /* setup fb0 display size */ |
| ret = of_property_read_u32_array(pdev->dev.of_node, |
| "display_size_default", |
| &var_screeninfo[0], 5); |
| if (ret) |
| osd_log_info("not found display_size_default\n"); |
| else { |
| fb_def_var.xres = var_screeninfo[0]; |
| fb_def_var.yres = var_screeninfo[1]; |
| fb_def_var.xres_virtual = |
| var_screeninfo[2]; |
| fb_def_var.yres_virtual = |
| var_screeninfo[3]; |
| fb_def_var.bits_per_pixel = |
| var_screeninfo[4]; |
| osd_log_info("init fbdev bpp is:%d\n", |
| fb_def_var.bits_per_pixel); |
| if (fb_def_var.bits_per_pixel > 32) |
| fb_def_var.bits_per_pixel = 32; |
| } |
| |
| fbdev_virt_set_default(fbdev, index); |
| if (fbdev->color == NULL) { |
| osd_log_err("fbdev->color NULL\n"); |
| ret = -ENOENT; |
| goto failed1; |
| } |
| bpp = (fbdev->color->color_index > 8 ? |
| (fbdev->color->color_index > 16 ? |
| (fbdev->color->color_index > 24 ? |
| 4 : 3) : 2) : 1); |
| fix->line_length = var->xres_virtual * bpp; |
| fix->smem_start = fbdev->fb_mem_paddr; |
| fix->smem_len = fbdev->fb_len; |
| if (fb_alloc_cmap(&fbi->cmap, 16, 0) != 0) { |
| osd_log_err("unable to allocate color map memory\n"); |
| ret = -ENOMEM; |
| goto failed2; |
| } |
| fbi->pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); |
| if (!(fbi->pseudo_palette)) { |
| osd_log_err("unable to allocate pseudo palette mem\n"); |
| ret = -ENOMEM; |
| goto failed2; |
| } |
| memset(fbi->pseudo_palette, 0, sizeof(u32) * 16); |
| fbi->fbops = &virtual_fb_ops; |
| fbi->screen_base = (char __iomem *)fbdev->fb_mem_vaddr; |
| fbi->screen_size = fix->smem_len; |
| virt_osd_check_var(var, fbi); |
| /* register frame buffer */ |
| register_framebuffer(fbi); |
| /* create device attribute files */ |
| for (i = 0; i < ARRAY_SIZE(virt_fb_attrs); i++) |
| ret = device_create_file( |
| fbi->dev, &virt_fb_attrs[i]); |
| |
| init_completion(&fbdev->fb_com); |
| init_completion(&fbdev->post_com); |
| #ifdef SOFTWARE_VSYNC |
| init_completion(&fbdev->timer_com); |
| #endif |
| lcd_init(); |
| virt_fb_start_monitor(); |
| #ifdef SOFTWARE_VSYNC |
| /* add timer to simulate software vsync */ |
| init_timer(&fbdev->timer); |
| fbdev->timer.data = (ulong) fbdev; |
| fbdev->timer.function = sw_vsync_timer_func; |
| fbdev->timer.expires = jiffies + DEFAULT_FPS; |
| add_timer(&fbdev->timer); |
| #endif |
| osd_log_info("virtual osd probe OK\n"); |
| return 0; |
| failed2: |
| fb_dealloc_cmap(&fbi->cmap); |
| failed1: |
| osd_log_err("osd probe failed.\n"); |
| return ret; |
| } |
| |
| void amlfb_virtual_remove(struct platform_device *pdev) |
| { |
| struct fb_info *fbi; |
| |
| if (fb_vir_dev) { |
| struct fb_virtual_dev_s *fbdev = fb_vir_dev; |
| |
| virt_fb_stop_monitor(); |
| fbi = fbdev->fb_info; |
| iounmap(fbdev->fb_mem_vaddr); |
| kfree(fbi->pseudo_palette); |
| fb_dealloc_cmap(&fbi->cmap); |
| unregister_framebuffer(fbi); |
| framebuffer_release(fbi); |
| } |
| } |