// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ /*#include */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtkfb.h" #include "mtkfb_info.h" #include /* #include */ /* #include */ /* #include "disp_assert_layer.h" */ /* #include */ /* #include */ /* #include */ /* #include */ /* #include "mach/mt_boot.h" */ #define ALIGN_TO(x, n) \ (((x) + ((n) - 1)) & ~((n) - 1)) #define SCREEN_WIDHT (1080) #define SCREEN_HEIGHT (1920) #define MTK_FB_ALIGNMENT 32 static u32 MTK_FB_XRES; static u32 MTK_FB_YRES; static u32 MTK_FB_BPP; static u32 MTK_FB_PAGES; static u32 fb_xres_update; static u32 fb_yres_update; #define MTK_FB_XRESV (ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT)) #define MTK_FB_YRESV (ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT) * \ MTK_FB_PAGES) /* For page flipping */ #define MTK_FB_BYPP ((MTK_FB_BPP + 7) >> 3) #define MTK_FB_LINE (ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT) * MTK_FB_BYPP) #define MTK_FB_SIZE (MTK_FB_LINE * ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT)) #define MTK_FB_SIZEV (MTK_FB_LINE * \ ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT) * MTK_FB_PAGES) /* ------------------------------------------------------------------------- */ /* local variables */ /* ------------------------------------------------------------------------- */ unsigned long fb_pa; struct fb_info *mtkfb_fbi; unsigned int lcd_fps = 6000; char mtkfb_lcm_name[256] = { 0 }; bool is_ipoh_bootup; bool is_early_suspended; /* ------------------------------------------------------------------------- */ /* local function declarations */ /* ------------------------------------------------------------------------- */ int mtkfb_get_debug_state(char *stringbuf, int buf_len) { return 0; } unsigned int mtkfb_fm_auto_test(void) { return 0; } unsigned int DISP_GetVRamSizeBoot(void) { return 0x01800000; } /* * --------------------------------------------------------------------------- * fbdev framework callbacks and the ioctl interface * --------------------------------------------------------------------------- */ /* Called each time the mtkfb device is opened */ static int mtkfb_open(struct fb_info *info, int user) { return 0; } /* Called when the mtkfb device is closed. We make sure that any pending * gfx DMA operations are ended, before we return. */ static int mtkfb_release(struct fb_info *info, int user) { return 0; } /* Store a single color palette entry into a pseudo palette or the hardware * palette if one is available. For now we support only 16bpp and thus store * the entry only to the pseudo palette. */ static int mtkfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, u_int transp, struct fb_info *info) { int r = 0; unsigned int bpp, m; /* NOT_REFERENCED(transp); */ bpp = info->var.bits_per_pixel; m = 1 << bpp; if (regno >= m) { r = -EINVAL; goto exit; } switch (bpp) { case 16: /* RGB 565 */ ((u32 *) (info->pseudo_palette))[regno] = ((red & 0xF800) | ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11)); break; case 32: /* ARGB8888 */ ((u32 *) (info->pseudo_palette))[regno] = (0xff000000) | ((red & 0xFF00) << 8) | ((green & 0xFF00)) | ((blue & 0xFF00) >> 8); break; /* TODO: RGB888, BGR888, ABGR8888 */ default: return -EINVAL; } exit: return r; } /* Set fb_info.fix fields and also updates fbdev. * When calling this fb_info.var must be set up already. */ static void set_fb_fix(struct mtkfb_device *fbdev) { struct fb_info *fbi = fbdev->fb_info; struct fb_fix_screeninfo *fix = &fbi->fix; struct fb_var_screeninfo *var = &fbi->var; struct fb_ops *fbops = fbi->fbops; strncpy(fix->id, MTKFB_DRIVER, sizeof(fix->id)); fix->type = FB_TYPE_PACKED_PIXELS; switch (var->bits_per_pixel) { case 16: case 24: case 32: fix->visual = FB_VISUAL_TRUECOLOR; break; case 1: case 2: case 4: case 8: fix->visual = FB_VISUAL_PSEUDOCOLOR; break; default: return; } fix->accel = FB_ACCEL_NONE; fix->line_length = ALIGN_TO(var->xres_virtual, MTK_FB_ALIGNMENT) * var->bits_per_pixel / 8; fix->smem_len = fbdev->fb_size_in_byte; fix->smem_start = fbdev->fb_pa_base; fix->xpanstep = 0; fix->ypanstep = 1; fbops->fb_fillrect = cfb_fillrect; fbops->fb_copyarea = cfb_copyarea; fbops->fb_imageblit = cfb_imageblit; } /* Switch to a new mode. The parameters for it has been check already by * mtkfb_check_var. */ static int mtkfb_set_par(struct fb_info *fbi) { struct mtkfb_device *fbdev = (struct mtkfb_device *)fbi->par; set_fb_fix(fbdev); return 0; } static int mtkfb_pan_display_impl(struct fb_var_screeninfo *var, struct fb_info *info) { pr_info("xoffset=%u, yoffset=%u, xres=%u, yres=%u, xresv=%u, yresv=%u\n", var->xoffset, var->yoffset, info->var.xres, info->var.yres, info->var.xres_virtual, info->var.yres_virtual); info->var.yoffset = var->yoffset; return 0; } /* Check values in var, try to adjust them in case of out of bound values if * possible, or return error. */ static int mtkfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) { unsigned int bpp; unsigned long max_frame_size; unsigned long line_size; struct mtkfb_device *fbdev = (struct mtkfb_device *)fbi->par; pr_info("%s, xres=%u, yres=%u, xres_virtual=%u, yres_virtual=%u, xoffset=%u, yoffset=%u, bits_per_pixel=%u)\n", __func__, var->xres, var->yres, var->xres_virtual, var->yres_virtual, var->xoffset, var->yoffset, var->bits_per_pixel); bpp = var->bits_per_pixel; if (bpp != 16 && bpp != 24 && bpp != 32) { pr_info("[%s]unsupported bpp: %d", __func__, bpp); return -1; } switch (var->rotate) { case 0: case 180: var->xres = MTK_FB_XRES; var->yres = MTK_FB_YRES; break; case 90: case 270: var->xres = MTK_FB_YRES; var->yres = MTK_FB_XRES; break; default: return -1; } if (var->xres_virtual < var->xres) var->xres_virtual = var->xres; if (var->yres_virtual < var->yres) var->yres_virtual = var->yres; max_frame_size = fbdev->fb_size_in_byte; pr_info("fbdev->fb_size_in_byte=0x%08lx\n", fbdev->fb_size_in_byte); line_size = var->xres_virtual * bpp / 8; if (line_size * var->yres_virtual > max_frame_size) { /* Try to keep yres_virtual first */ line_size = max_frame_size / var->yres_virtual; var->xres_virtual = line_size * 8 / bpp; if (var->xres_virtual < var->xres) { /* Still doesn't fit. Shrink yres_virtual too */ var->xres_virtual = var->xres; line_size = var->xres * bpp / 8; var->yres_virtual = max_frame_size / line_size; } } pr_info("%s, xres=%u, yres=%u, xres_virtual=%u, yres_virtual=%u, xoffset=%u, yoffset=%u, bits_per_pixel=%u)\n", __func__, var->xres, var->yres, var->xres_virtual, var->yres_virtual, var->xoffset, var->yoffset, var->bits_per_pixel); 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; pr_info("%s, xres=%u, yres=%u, xres_virtual=%u, yres_virtual=%u, xoffset=%u, yoffset=%u, bits_per_pixel=%u)\n", __func__, var->xres, var->yres, var->xres_virtual, var->yres_virtual, var->xoffset, var->yoffset, var->bits_per_pixel); if (bpp == 16) { var->red.offset = 11; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 0; var->blue.length = 5; var->transp.offset = 0; var->transp.length = 0; } else if (bpp == 24) { var->red.length = var->green.length = var->blue.length = 8; var->transp.length = 0; /* Check if format is RGB565 or BGR565 */ if (!(var->green.offset == 8)) { pr_err("%s: green.offset=%d\n", __func__, var->green.offset); return -1; } if (!(var->red.offset + var->blue.offset == 16)) { pr_err("%s: red.offset=%d, blue.offset=%d\n", __func__, var->green.offset, var->blue.offset); return -1; } if (!(var->red.offset == 16 || var->red.offset == 0)) { pr_err("%s: red.offset=%d\n", __func__, var->green.offset); return -1; } } else if (bpp == 32) { var->red.length = var->green.length = var->blue.length = var->transp.length = 8; /* Check if format is ARGB565 or ABGR565 */ if (!(var->green.offset == 8 && var->transp.offset == 24)) { pr_err("%s: green.offset=%d, transp.offset=%d\n", __func__, var->green.offset, var->transp.offset); return -1; } if (!(var->red.offset + var->blue.offset == 16)) { pr_err("%s: red.offset=%d, blue.offset=%d\n", __func__, var->red.offset, var->blue.offset); return -1; } if (!(var->red.offset == 16 || var->red.offset == 0)) { pr_err("%s: red.offset=%d\n", __func__, var->red.offset); return -1; } } var->red.msb_right = var->green.msb_right = var->blue.msb_right = var->transp.msb_right = 0; var->activate = FB_ACTIVATE_NOW; var->height = UINT_MAX; var->width = UINT_MAX; var->grayscale = 0; var->nonstd = 0; var->pixclock = UINT_MAX; var->left_margin = UINT_MAX; var->right_margin = UINT_MAX; var->upper_margin = UINT_MAX; var->lower_margin = UINT_MAX; var->hsync_len = UINT_MAX; var->vsync_len = UINT_MAX; var->vmode = FB_VMODE_NONINTERLACED; var->sync = 0; return 0; } static int mtkfb_pan_display_proxy(struct fb_var_screeninfo *var, struct fb_info *info) { return mtkfb_pan_display_impl(var, info); } /* Callback table for the frame buffer framework. Some of these pointers * will be changed according to the current setting of fb_info->accel_flags. */ static struct fb_ops mtkfb_ops = { .owner = THIS_MODULE, .fb_open = mtkfb_open, .fb_release = mtkfb_release, .fb_setcolreg = mtkfb_setcolreg, .fb_pan_display = mtkfb_pan_display_proxy, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_cursor = NULL, .fb_check_var = mtkfb_check_var, .fb_set_par = mtkfb_set_par, .fb_ioctl = NULL, }; /* * --------------------------------------------------------------------------- * LDM callbacks * --------------------------------------------------------------------------- */ /* Initialize system fb_info object and set the default video mode. * The frame buffer memory already allocated by lcddma_init */ static int mtkfb_fbinfo_init(struct fb_info *info) { struct mtkfb_device *fbdev = (struct mtkfb_device *)info->par; struct fb_var_screeninfo var; int r = 0; WARN_ON(!fbdev->fb_va_base); info->fbops = &mtkfb_ops; info->flags = FBINFO_FLAG_DEFAULT; info->screen_base = (char *)fbdev->fb_va_base; info->screen_size = fbdev->fb_size_in_byte; info->pseudo_palette = fbdev->pseudo_palette; r = fb_alloc_cmap(&info->cmap, 32, 0); if (r != 0) pr_info("unable to allocate color map memory\n"); /* setup the initial video mode (RGB565) */ memset(&var, 0, sizeof(var)); var.xres = MTK_FB_XRES; var.yres = MTK_FB_YRES; var.xres_virtual = MTK_FB_XRESV; var.yres_virtual = MTK_FB_YRESV; /* use 32 bit framebuffer as default */ var.bits_per_pixel = 32; var.transp.offset = 24; var.red.length = 8; var.red.offset = 0; var.red.length = 8; var.green.offset = 8; var.green.length = 8; var.blue.offset = 16; var.blue.length = 8; var.width = 0; var.height = 0; var.activate = FB_ACTIVATE_NOW; r = mtkfb_check_var(&var, info); if (r != 0) pr_info("failed to mtkfb_check_var\n"); info->var = var; r = mtkfb_set_par(info); if (r != 0) pr_info("failed to mtkfb_set_par\n"); return r; } /* Release the fb_info object */ static void mtkfb_fbinfo_cleanup(struct mtkfb_device *fbdev) { fb_dealloc_cmap(&fbdev->fb_info->cmap); } /* Free driver resources. Can be called to rollback an aborted initialization * sequence. */ static void mtkfb_free_resources(struct mtkfb_device *fbdev, int state) { int r = 0; switch (state) { case MTKFB_ACTIVE: r = unregister_framebuffer(fbdev->fb_info); WARN_ON(r == 0); /* lint -fallthrough */ case 4: mtkfb_fbinfo_cleanup(fbdev); /* lint -fallthrough */ case 2: #ifndef FPGA_EARLY_PORTING dma_free_coherent(0, fbdev->fb_size_in_byte, fbdev->fb_va_base, fbdev->fb_pa_base); #endif /* lint -fallthrough */ case 1: dev_set_drvdata(fbdev->dev, NULL); framebuffer_release(fbdev->fb_info); /* lint -fallthrough */ case 0: /* nothing to free */ break; default: return; } } #ifdef CONFIG_OF struct tag_videolfb { u64 fb_base; u32 islcmfound; u32 fps; u32 vram; char lcmname[1]; /* this is the minimum size */ }; unsigned int islcmconnected; unsigned int is_lcm_inited; unsigned int vramsize; phys_addr_t fb_base; static int is_videofb_parse_done; static int __parse_tag_videolfb_extra(struct device_node *node) { void *prop; unsigned long size = 0; u32 fb_base_h, fb_base_l; prop = (void *)of_get_property(node, "atag,videolfb-fb_base_h", NULL); if (!prop) return -1; fb_base_h = of_read_number(prop, 1); prop = (void *)of_get_property(node, "atag,videolfb-fb_base_l", NULL); if (!prop) return -1; fb_base_l = of_read_number(prop, 1); fb_base = ((u64) fb_base_h << 32) | (u64) fb_base_l; prop = (void *)of_get_property(node, "atag,videolfb-islcmfound", NULL); if (!prop) return -1; islcmconnected = of_read_number(prop, 1); prop = (void *)of_get_property(node, "atag,videolfb-islcm_inited", NULL); if (!prop) is_lcm_inited = 1; else is_lcm_inited = of_read_number(prop, 1); prop = (void *)of_get_property(node, "atag,videolfb-fps", NULL); if (!prop) return -1; lcd_fps = of_read_number(prop, 1); if (lcd_fps == 0) lcd_fps = 6000; prop = (void *)of_get_property(node, "atag,videolfb-vramSize", NULL); if (!prop) return -1; vramsize = of_read_number(prop, 1); prop = (void *)of_get_property(node, "atag,videolfb-fb_base_l", NULL); if (!prop) return -1; fb_base_l = of_read_number(prop, 1); prop = (void *)of_get_property(node, "atag,videolfb-lcmname", (int *)&size); if (!prop) return -1; if (size >= sizeof(mtkfb_lcm_name)) { pr_info("%s: error to get lcmname size=%ld\n", __func__, size); return -1; } memset((void *)mtkfb_lcm_name, 0, sizeof(mtkfb_lcm_name)); strncpy((char *)mtkfb_lcm_name, prop, sizeof(mtkfb_lcm_name)); mtkfb_lcm_name[size] = '\0'; pr_info("%s done\n", __func__); return 0; } int __parse_tag_videolfb(struct device_node *node) { struct tag_videolfb *videolfb_tag = NULL; unsigned long size = 0; videolfb_tag = (struct tag_videolfb *)of_get_property(node, "atag,videolfb", (int *)&size); if (videolfb_tag) { memset((void *)mtkfb_lcm_name, 0, sizeof(mtkfb_lcm_name)); strcpy((char *)mtkfb_lcm_name, videolfb_tag->lcmname); mtkfb_lcm_name[strlen(videolfb_tag->lcmname)] = '\0'; lcd_fps = videolfb_tag->fps; if (lcd_fps == 0) lcd_fps = 6000; islcmconnected = videolfb_tag->islcmfound; vramsize = videolfb_tag->vram; fb_base = videolfb_tag->fb_base; is_lcm_inited = 1; return 0; } pr_info("[DT][videolfb] videolfb_tag not found\n"); return -1; } static int _parse_tag_videolfb(void) { int ret; struct device_node *chosen_node; pr_info("[DT][videolfb]isvideofb_parse_done = %d\n", is_videofb_parse_done); if (is_videofb_parse_done) return 0; chosen_node = of_find_node_by_path("/chosen"); if (!chosen_node) chosen_node = of_find_node_by_path("/chosen@0"); if (chosen_node) { ret = __parse_tag_videolfb(chosen_node); #if defined(CONFIG_MTK_DUAL_DISPLAY_SUPPORT) && \ (CONFIG_MTK_DUAL_DISPLAY_SUPPORT == 2) ret = __parse_tag_ext_videolfb(chosen_node); #endif if (!ret) goto found; ret = __parse_tag_videolfb_extra(chosen_node); if (!ret) goto found; } else { pr_info("[DT][videolfb] of_chosen not found\n"); } return -1; found: is_videofb_parse_done = 1; pr_info("[DT][videolfb] islcmfound = %d\n", islcmconnected); pr_info("[DT][videolfb] is_lcm_inited = %d\n", is_lcm_inited); pr_info("[DT][videolfb] fps = %d\n", lcd_fps); pr_info("[DT][videolfb] fb_base = 0x%lx\n", (unsigned long)fb_base); pr_info("[DT][videolfb] vram = 0x%x (%d)\n", vramsize, vramsize); pr_info("[DT][videolfb] lcmname = %s\n", mtkfb_lcm_name); return 0; } phys_addr_t mtkfb_get_fb_base(void) { _parse_tag_videolfb(); return fb_base; } #endif int mtkfb_allocate_framebuffer(phys_addr_t pa_start, phys_addr_t pa_end, unsigned long *va, unsigned long *mva) { *va = (unsigned long)ioremap_nocache(pa_start, pa_end - pa_start + 1); pr_info("disphal_allocate_fb, pa=%pa, va=0x%08lx\n", &pa_start, *va); { *mva = pa_start & 0xffffffffULL; } return 0; } static int mtkfb_probe(struct platform_device *pdev) { struct mtkfb_device *fbdev = NULL; struct fb_info *fbi; int init_state; int r = 0; pr_info("%s begin\n", __func__); #ifdef CONFIG_OF _parse_tag_videolfb(); #endif init_state = 0; fbi = framebuffer_alloc(sizeof(struct mtkfb_device), &(pdev->dev)); if (!fbi) { pr_info("unable to allocate memory for device info\n"); r = -ENOMEM; goto cleanup; } fbdev = (struct mtkfb_device *)fbi->par; fbdev->fb_info = fbi; fbdev->dev = &(pdev->dev); dev_set_drvdata(&(pdev->dev), fbdev); { #ifdef CONFIG_OF pr_info("%s:get FB MEM REG\n", __func__); _parse_tag_videolfb(); pr_info("%s: fb_pa = 0x%p\n", __func__, (void *)fb_base); mtkfb_allocate_framebuffer(fb_base, (fb_base + vramsize - 1), (unsigned long *)&fbdev->fb_va_base, &fb_pa); fbdev->fb_pa_base = (dma_addr_t) fb_base; #else struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); disp_hal_allocate_framebuffer(res->start, res->end, (unsigned long *)&fbdev->fb_va_base, &fb_pa); fbdev->fb_pa_base = res->start; #endif } init_state++; /* 1 */ MTK_FB_XRES = SCREEN_WIDHT; MTK_FB_YRES = SCREEN_HEIGHT; fb_xres_update = MTK_FB_XRES; fb_yres_update = MTK_FB_YRES; MTK_FB_BPP = 32; MTK_FB_PAGES = 3; pr_info("MTK_FB_XRES=%d, MTKFB_YRES=%d, MTKFB_BPP=%d, MTK_FB_PAGES=%d, MTKFB_LINE=%d, MTKFB_SIZEV=%d\n", MTK_FB_XRES, MTK_FB_YRES, MTK_FB_BPP, MTK_FB_PAGES, MTK_FB_LINE, MTK_FB_SIZEV); fbdev->fb_size_in_byte = MTK_FB_SIZEV; /* Allocate and initialize video frame buffer */ pr_info("[FB Driver] fbdev->fb_pa_base = %p, fbdev->fb_va_base = %lx\n", (void *)fbdev->fb_pa_base, (unsigned long)(fbdev->fb_va_base)); if (!fbdev->fb_va_base) { pr_info("unable to allocate memory for frame buffer\n"); r = -ENOMEM; goto cleanup; } init_state++; /* 2 */ /* Register to system */ r = mtkfb_fbinfo_init(fbi); if (r) { pr_info("mtkfb_fbinfo_init fail, r = %d\n", r); goto cleanup; } init_state++; /* 4 */ mtkfb_fbi = fbi; init_state++; /* 5 */ r = register_framebuffer(fbi); if (r != 0) { pr_info("register_framebuffer failed\n"); goto cleanup; } fbdev->state = MTKFB_ACTIVE; pr_info("%s end\n", __func__); return 0; cleanup: mtkfb_free_resources(fbdev, init_state); pr_info("%s end\n", __func__); return r; } /* Called when the device is being detached from the driver */ static int mtkfb_remove(struct device *dev) { struct mtkfb_device *fbdev = dev_get_drvdata(dev); enum mtkfb_state saved_state = fbdev->state; fbdev->state = MTKFB_DISABLED; mtkfb_free_resources(fbdev, saved_state); return 0; } int mtkfb_ipo_init(void) { return 0; } void mtkfb_clear_lcm(void) { ; } static const struct of_device_id mtkfb_of_ids[] = { { .compatible = "mediatek,mtkfb", }, {} }; static struct platform_driver mtkfb_driver = { .probe = mtkfb_probe, .driver = { .name = MTKFB_DRIVER, #ifdef CONFIG_PM .pm = NULL, #endif .bus = &platform_bus_type, .remove = mtkfb_remove, .suspend = NULL, .resume = NULL, .shutdown = NULL, .of_match_table = mtkfb_of_ids, }, }; /* Register both the driver and the device */ int __init mtkfb_init(void) { int r = 0; pr_info("%s init", __func__); if (platform_driver_register(&mtkfb_driver)) { pr_info("failed to register mtkfb driver\n"); r = -ENODEV; } return r; } static void __exit mtkfb_cleanup(void) { platform_driver_unregister(&mtkfb_driver); } module_init(mtkfb_init); module_exit(mtkfb_cleanup); MODULE_DESCRIPTION("MEDIATEK framebuffer driver"); MODULE_AUTHOR("Xuecheng Zhang "); MODULE_LICENSE("GPL");