// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020-2022 ArtInChip Technology Co., Ltd. * Authors: Ning Fang */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "video/artinchip_fb.h" #include "aic_fb.h" #include "hw/de_hw.h" #define AICFB_NAME "aicfb" #define AICFB_ON 1 #define AICFB_OFF 0 #define MAX_FB_NUM 1 enum aicfb_port_dir { AICFB_PORT_IN, AICFB_PORT_OUT }; struct aicfb_format { const char *name; u32 bits_per_pixel; struct fb_bitfield red; struct fb_bitfield green; struct fb_bitfield blue; struct fb_bitfield transp; enum mpp_pixel_format format; }; struct aicfb_dt { u32 rotation; u32 disp_buf_num; u32 width; u32 height; u32 width_virtual; u32 height_virtual; u32 stride; u32 size; /* The final framebuffer size to be requested */ struct aicfb_format *format; struct aicfb_disp_prop disp_prop; struct device_node *fb_np; /* node pointer of framebuffer */ struct device_node *de_np; /* node pointer of display engine */ struct device_node *di_np; /* node pointer of display interface */ struct device_node *panel_np; /* node pointer of panel */ }; struct aicfb_data { struct fb_info *info[MAX_FB_NUM]; struct device *dev; struct aicfb_dt dt_lists[MAX_FB_NUM]; bool fb_valid[MAX_FB_NUM]; }; struct aicfb_format aicfb_format_lists[] = { {"a8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {24, 8}, MPP_FMT_ARGB_8888}, {"a8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {24, 8}, MPP_FMT_ABGR_8888}, {"r8g8b8a8", 32, {24, 8}, {16, 8}, {8, 8}, {0, 8}, MPP_FMT_RGBA_8888}, {"b8g8r8a8", 32, {8, 8}, {16, 8}, {24, 8}, {0, 8}, MPP_FMT_BGRA_8888}, {"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, MPP_FMT_XRGB_8888}, {"x8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {0, 0}, MPP_FMT_XBGR_8888}, {"r8g8b8x8", 32, {24, 8}, {16, 8}, {8, 8}, {0, 0}, MPP_FMT_RGBX_8888}, {"b8g8r8x8", 32, {8, 8}, {16, 8}, {24, 8}, {0, 0}, MPP_FMT_BGRX_8888}, {"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, MPP_FMT_RGB_888}, {"b8g8r8", 24, {0, 8}, {8, 8}, {16, 8}, {0, 0}, MPP_FMT_BGR_888}, {"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, MPP_FMT_RGB_565}, {"b5g6r5", 16, {0, 5}, {5, 6}, {11, 5}, {0, 0}, MPP_FMT_BGR_565}, {"a1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {15, 1}, MPP_FMT_ARGB_1555}, {"a1b5g5r5", 16, {0, 5}, {5, 5}, {10, 5}, {15, 1}, MPP_FMT_ABGR_1555}, {"r5g5b5a1", 16, {11, 5}, {6, 5}, {1, 5}, {0, 1}, MPP_FMT_RGBA_5551}, {"b5g5r5a1", 16, {1, 5}, {6, 5}, {11, 5}, {0, 1}, MPP_FMT_BGRA_5551}, {"a4r4g4b4", 16, {8, 4}, {4, 4}, {0, 4}, {12, 4}, MPP_FMT_ARGB_4444}, {"a4b4g4r4", 16, {0, 4}, {4, 4}, {8, 4}, {12, 4}, MPP_FMT_ABGR_4444}, {"r4g4b4a4", 16, {12, 4}, {8, 4}, {4, 4}, {0, 4}, MPP_FMT_RGBA_4444}, {"b4g4r4a4", 16, {8, 4}, {8, 4}, {12, 4}, {0, 4}, MPP_FMT_BGRA_4444}, }; phys_addr_t uboot_logo_base; phys_addr_t uboot_logo_size; static int uboot_logo_on; static int __init aicfb_uboot_mem_free(void) { int err; if (uboot_logo_size) { void *start = phys_to_virt(uboot_logo_base); void *end = phys_to_virt(uboot_logo_base + uboot_logo_size); err = memblock_free(uboot_logo_base, uboot_logo_size); if (err < 0) { pr_err("%s: Freeing memblock failed: %d\n", __func__, err); return err; } free_reserved_area(start, end, -1, "logo"); } return 0; } late_initcall(aicfb_uboot_mem_free); static void aicfb_videomode_to_fbdata(struct aicfb_dt *dt, struct videomode *vm) { struct aicfb_format *f = dt->format; if (!dt->width) dt->width = vm->hactive; if (!dt->height) dt->height = vm->vactive; if (dt->rotation == 90 || dt->rotation == 270) { u32 tmp = dt->width; dt->width = dt->height; dt->height = tmp; } if (dt->width_virtual < dt->width) dt->width_virtual = dt->width; if (dt->height_virtual < dt->height) dt->height_virtual = dt->height; if (dt->rotation && !dt->disp_buf_num) { if (dt->height_virtual == dt->height) dt->disp_buf_num = 1; else dt->disp_buf_num = 2; } if (!dt->stride) dt->stride = ALIGN_8B(dt->width_virtual * f->bits_per_pixel / 8); dt->size = dt->height_virtual * dt->stride; } static void aicfb_format_to_var(struct fb_var_screeninfo *var, struct aicfb_dt *dt) { struct aicfb_format *f = dt->format; var->bits_per_pixel = f->bits_per_pixel; var->red = f->red; var->green = f->green; var->blue = f->blue; var->transp = f->transp; var->yres = dt->height; var->yres_virtual = dt->height_virtual; var->xres = dt->width; var->xres_virtual = dt->width_virtual; var->rotate = dt->rotation; } static inline bool need_set_hsbc(struct aicfb_disp_prop *disp_prop) { if (disp_prop->bright != 50 || disp_prop->contrast != 50 || disp_prop->saturation != 50 || disp_prop->hue != 50) return true; return false; } static inline bool check_rotation_degree(u32 degree) { switch (degree) { case 0: case 90: case 180: case 270: return true; default: break; }; pr_err("Invalid rotation degree\n"); return false; } static int aic_fb_open(struct fb_info *info, int user) { return 0; } static int aic_fb_release(struct fb_info *info, int user) { struct aicfb_info *fbi = (struct aicfb_info *)info->par; return fbi->de->release_dmabuf(); } static unsigned int fb_index; static int aic_fb_rotate(struct fb_info *info, struct fb_var_screeninfo *var, struct aicfb_layer_data *layer, unsigned int offset) { struct aicfb_info *fbi = (struct aicfb_info *)info->par; struct ge_bitblt blt = {0}; s32 ret = 0; if (!check_rotation_degree(var->rotate)) return -EINVAL; /* source buffer */ blt.src_buf.buf_type = MPP_PHY_ADDR; blt.src_buf.phy_addr[0] = fbi->fb_start_dma + offset; blt.src_buf.stride[0] = info->fix.line_length; blt.src_buf.size.width = info->var.xres; blt.src_buf.size.height = info->var.yres; blt.src_buf.format = layer->buf.format; /* destination buffer */ blt.dst_buf.buf_type = MPP_PHY_ADDR; blt.dst_buf.phy_addr[0] = layer->buf.phy_addr[0]; blt.dst_buf.stride[0] = layer->buf.stride[0]; blt.dst_buf.size.width = layer->buf.size.width; blt.dst_buf.size.height = layer->buf.size.height; blt.dst_buf.format = layer->buf.format; mutex_lock(&fbi->mutex); fbi->fb_rotate = var->rotate; mutex_unlock(&fbi->mutex); switch (fbi->fb_rotate) { case 0: blt.ctrl.flags = 0; break; case 90: blt.ctrl.flags = MPP_ROTATION_90; break; case 180: blt.ctrl.flags = MPP_ROTATION_180; break; case 270: blt.ctrl.flags = MPP_ROTATION_270; break; default: pr_err("Invalid rotation degree\n"); return -EINVAL; }; if (fbi->set_hsbc) { u32 csc_coef[12]; int bright; int contrast; int sat; int hue; mutex_lock(&fbi->mutex); bright = fbi->disp_prop->bright; contrast = fbi->disp_prop->contrast; sat = fbi->disp_prop->saturation; hue = fbi->disp_prop->hue; mutex_unlock(&fbi->mutex); get_rgb_hsbc_csc_coefs(bright, contrast, sat, hue, csc_coef); ret = aic_ge_bitblt_with_hsbc(&blt, csc_coef); } else { ret = aic_ge_bitblt(&blt); } return ret; } static int aic_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { struct aicfb_info *fbi = (struct aicfb_info *)info->par; struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; u32 offset; offset = var->xoffset * info->var.bits_per_pixel / 8 + var->yoffset * ALIGN_8B(info->var.bits_per_pixel / 8 * var->xres); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 1; if (var->reserved[0]) { layer.buf.fd[0] = var->reserved[0]; layer.buf.flags = MPP_BUF_PAN_DISPLAY_DMABUF; return de->update_layer_config(&layer); } layer.buf.flags = 0; layer.buf.phy_addr[0] = fbi->fb_start_dma + offset; if (fbi->fb_rotate || fbi->set_hsbc) { layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size + offset; if (fbi->disp_buf_num == 2 && info->var.yres == info->var.yres_virtual) { layer.buf.phy_addr[0] += fbi->fb_size * fb_index; fb_index = !fb_index; } if (aic_fb_rotate(info, var, &layer, offset)) pr_err("GE Bitblt FB error\n"); } de->update_layer_config(&layer); return 0; } int aic_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { if (var->xoffset + var->xres > var->xres_virtual) return -EINVAL; if (var->yoffset + var->yres > var->yres_virtual) return -EINVAL; return 0; } int aic_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct aicfb_info *fbi = (struct aicfb_info *)info->par; unsigned long mmio_pgoff; unsigned long start; u32 len; if (fbi->dma_coherent) return dma_mmap_attrs(info->dev->parent, vma, fbi->fb_start, fbi->fb_start_dma, fbi->fb_size, DMA_ATTR_WRITE_COMBINE); /* * Ugh. This can be either the frame buffer mapping, or * if pgoff points past it, the mmio mapping. */ 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) { mutex_unlock(&info->mm_lock); return -EINVAL; } vma->vm_pgoff -= mmio_pgoff; start = info->fix.mmio_start; len = info->fix.mmio_len; } mutex_unlock(&info->mm_lock); vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); /* * The framebuffer needs to be accessed decrypted, be sure * SME protection is removed */ vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); return vm_iomap_memory(vma, start, len); } struct fb_ops aicfb_ops = { .owner = THIS_MODULE, .fb_open = aic_fb_open, .fb_release = aic_fb_release, .fb_pan_display = aic_fb_pan_display, .fb_ioctl = aic_fb_ioctl, .fb_check_var = aic_fb_check_var, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_mmap = aic_fb_mmap, }; static void aicfb_fb_info_setup(struct fb_info *info, struct aicfb_data *fdt) { struct aicfb_info *fbi = (struct aicfb_info *)info->par; struct aicfb_dt *dt = &fdt->dt_lists[0]; info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; info->node = 0; strcpy(info->fix.id, fbi->name); info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.type_aux = 0; info->fix.xpanstep = 0; if (info->var.yres_virtual == info->var.yres) info->fix.ypanstep = 0; else info->fix.ypanstep = 1; info->fix.ywrapstep = 0; info->fix.accel = FB_ACCEL_NONE; info->fix.smem_start = fbi->fb_start_dma; info->fix.smem_len = fbi->fb_size; info->fix.visual = FB_VISUAL_TRUECOLOR; info->fix.line_length = dt->stride; info->fbops = &aicfb_ops; info->pseudo_palette = fbi->pseudo_palette; info->screen_base = fbi->fb_start; info->screen_size = fbi->fb_size; fbi->fb_rotate = dt->rotation; fbi->disp_buf_num = dt->disp_buf_num; if (fbi->fb_rotate == 90 || fbi->fb_rotate == 270) { fbi->screen_size.width = dt->height; fbi->screen_size.height = dt->width; } else { fbi->screen_size.width = dt->width; fbi->screen_size.height = dt->height; } } static int compare_of(struct device *dev, void *data) { return dev->of_node == data; } static int aicfb_add_match_component(struct platform_device *pdev, struct component_match **match, u32 id) { struct aicfb_data *fbd = dev_get_drvdata(&pdev->dev); /* match de */ component_match_add(&pdev->dev, match, compare_of, fbd->dt_lists[id].de_np); /* match encoder */ component_match_add(&pdev->dev, match, compare_of, fbd->dt_lists[id].di_np); /* match panel */ component_match_add(&pdev->dev, match, compare_of, fbd->dt_lists[id].panel_np); return 0; } /** * of_get_fb_by_id() - get the fb device node matching a given id * @parent: pointer to the parent device node * @id: id of the fb * */ struct device_node *of_get_fb_by_id(struct device_node *parent, u32 id) { struct device_node *fb_node = NULL; for_each_child_of_node(parent, fb_node) { u32 fb_id = 0; if (!of_node_name_eq(fb_node, "fb")) continue; of_property_read_u32(fb_node, "reg", &fb_id); if (id == fb_id) break; } return fb_node; } static struct device_node *of_get_remote_by_port(struct device_node *parent) { struct device_node *remote = NULL; struct device_node *port; for_each_child_of_node(parent, port) { if (of_node_name_eq(port, "port")) { remote = of_graph_get_remote_port_parent(port->child); break; } of_node_put(port); } of_node_put(port); return remote; } static struct device_node *of_get_remote_by_port_id(struct device_node *parent, enum aicfb_port_dir id) { struct device_node *remote = NULL; struct device_node *port; port = of_graph_get_port_by_id(parent, id); if (!port) return NULL; remote = of_graph_get_remote_port_parent(port->child); of_node_put(port); return remote; } static int aicfb_parse_fb_rotation(struct device_node *np, struct aicfb_dt *dt) { int ret; ret = of_property_read_u32(np, "rotation-degress", &dt->rotation); if (ret) { dt->rotation = 0; dt->disp_buf_num = 0; return 0; } if (!check_rotation_degree(dt->rotation)) return -EINVAL; if (of_property_read_u32(np, "disp-buf-num", &dt->disp_buf_num)) dt->disp_buf_num = 0; return 0; } static int aicfb_parse_fb_size(struct device_node *np, struct aicfb_dt *dt) { const char *format; int i; if (of_property_read_u32(np, "width", &dt->width)) dt->width = 0; if (of_property_read_u32(np, "height", &dt->height)) dt->height = 0; if (of_property_read_u32(np, "width-virtual", &dt->width_virtual)) dt->width_virtual = 0; if (of_property_read_u32(np, "height-virtual", &dt->height_virtual)) dt->height_virtual = 0; if (of_property_read_u32(np, "stride", &dt->stride)) dt->stride = 0; dt->format = NULL; if (of_property_read_string(np, "format", &format)) { dt->format = &aicfb_format_lists[0]; } else { for (i = 0; i < ARRAY_SIZE(aicfb_format_lists); i++) { if (strcmp(format, aicfb_format_lists[i].name)) continue; dt->format = &aicfb_format_lists[i]; break; } } if (!dt->format) { pr_err("fb0 invalid format:%s\n", format); return -EINVAL; } return 0; } static void aicfb_parse_disp_prop(struct device_node *np, struct aicfb_dt *dt) { if (of_property_read_u32(np, "disp-bright", &dt->disp_prop.bright)) dt->disp_prop.bright = 50; if (of_property_read_u32(np, "disp-contrast", &dt->disp_prop.contrast)) dt->disp_prop.contrast = 50; if (of_property_read_u32(np, "disp-saturation", &dt->disp_prop.saturation)) dt->disp_prop.saturation = 50; if (of_property_read_u32(np, "disp-hue", &dt->disp_prop.hue)) dt->disp_prop.hue = 50; } static int aicfb_parse_dt_by_fb_id(struct platform_device *pdev, u32 id) { struct device_node *root_np = pdev->dev.of_node; struct aicfb_data *aicfb = dev_get_drvdata(&pdev->dev); struct aicfb_dt *dt = &aicfb->dt_lists[id]; struct device_node *np, *node; int ret; if (!root_np) { dev_err(&pdev->dev, "Can't find device node\n"); goto err_fb; } /* parse fb device node */ np = of_get_fb_by_id(root_np, id); if (!np) { dev_err(&pdev->dev, "Can't find fb@%u\n", id); goto err_fb; } dt->fb_np = np; /* parse fb param */ if (aicfb_parse_fb_rotation(np, dt)) goto err_param; if (aicfb_parse_fb_size(np, dt)) goto err_param; aicfb_parse_disp_prop(np, dt); /* get de device node */ dt->de_np = of_get_remote_by_port(np); if (!dt->de_np) { dev_err(&pdev->dev, "Can't get de%u device node\n", id); goto err_de; } /* get encoder device node */ dt->di_np = of_get_remote_by_port_id(dt->de_np, AICFB_PORT_OUT); if (!dt->di_np) { dev_err(&pdev->dev, "Can't get encoder%u device node\n", id); goto err_en; } /* get panel device node */ dt->panel_np = of_get_remote_by_port_id(dt->di_np, AICFB_PORT_OUT); if (!dt->panel_np) { dev_err(&pdev->dev, "Can't get panel%u device node\n", id); goto err_panel; } ret = of_property_read_u32(np, "artinchip,uboot-logo-on", &uboot_logo_on); if (ret) { dev_err(&pdev->dev, "Can't parse uboot-logo-on property\n"); goto err_logo; } node = of_find_node_by_name(NULL, "aic-logo"); if (node) { struct resource r; ret = of_address_to_resource(node, 0, &r); if (ret) { dev_err(&pdev->dev, "Can't get resource\n"); goto err_res; } if (uboot_logo_on) { uboot_logo_base = r.start; uboot_logo_size = resource_size(&r); dev_info(&pdev->dev, "logo: base=%#llx, size=%#llx\n", uboot_logo_base, uboot_logo_size); } } return 0; err_res: of_node_put(node); err_logo: of_node_put(dt->panel_np); dt->panel_np = NULL; err_panel: of_node_put(dt->di_np); dt->di_np = NULL; err_en: of_node_put(dt->de_np); dt->de_np = NULL; err_de: err_param: of_node_put(dt->fb_np); dt->fb_np = NULL; err_fb: return -EINVAL; } static int aicfb_alloc_fb(struct device *dev, u32 id) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_info *fbi; fbd->info[id] = framebuffer_alloc(sizeof(struct aicfb_info), dev); if (!fbd->info[id]) return -ENOMEM; fbi = (struct aicfb_info *)fbd->info[id]->par; sprintf(fbi->name, "%s%d", AICFB_NAME, id); return 0; } static int aicfb_find_panel(struct aicfb_data *fbd, u32 id) { struct platform_device *panel_pdev; struct aicfb_info *fbi; panel_pdev = of_find_device_by_node(fbd->dt_lists[id].panel_np); if (!panel_pdev) { dev_err(fbd->dev, "Failed to find panel node for fb%d\n", id); return -EINVAL; } fbi = (struct aicfb_info *)fbd->info[id]->par; fbi->panel = dev_get_drvdata(&panel_pdev->dev); fbi->panel_dev = get_device(&panel_pdev->dev); return 0; } static int aicfb_find_de(struct aicfb_data *fbd, u32 id) { struct platform_device *de_pdev; struct aicfb_info *fbi; de_pdev = of_find_device_by_node(fbd->dt_lists[id].de_np); if (!de_pdev) { dev_err(fbd->dev, "Failed to find de node for fb%d\n", id); return -EINVAL; } fbi = (struct aicfb_info *)fbd->info[id]->par; fbi->de = dev_get_drvdata(&de_pdev->dev); fbi->de_dev = get_device(&de_pdev->dev); return 0; } static int aicfb_find_di(struct aicfb_data *fbd, u32 id) { struct platform_device *di_pdev; struct aicfb_info *fbi; di_pdev = of_find_device_by_node(fbd->dt_lists[id].di_np); if (!di_pdev) { dev_err(fbd->dev, "Failed to find di node for fb%d\n", id); return -EINVAL; } fbi = (struct aicfb_info *)fbd->info[id]->par; fbi->di = dev_get_drvdata(&di_pdev->dev); fbi->di_dev = get_device(&di_pdev->dev); return 0; } static void aicfb_get_panel_info(struct aicfb_info *fbi, struct videomode **vm) { struct de_funcs *de = fbi->de; struct di_funcs *di = fbi->di; struct aic_panel *panel = fbi->panel; panel->funcs->get_video_mode(panel, vm); de->set_mode(panel, *vm); di->attach_panel(panel); } static void aicfb_register_panel_callback(struct aicfb_info *fbi) { struct aic_panel *p = fbi->panel; struct aic_panel_callbacks cb; cb.di_enable = fbi->di->enable; cb.di_disable = fbi->di->disable; cb.di_send_cmd = fbi->di->send_cmd; cb.di_set_videomode = fbi->di->set_videomode; cb.timing_enable = fbi->de->timing_enable; cb.timing_disable = fbi->de->timing_disable; p->funcs->register_callback(fbi->panel, &cb); } static void aicfb_enable_panel(struct aicfb_info *fbi, u32 on) { struct aic_panel *p = fbi->panel; if (on == AICFB_ON) { p->funcs->prepare(p); p->funcs->enable(p); } else { p->funcs->disable(p); p->funcs->unprepare(p); } } static void aicfb_enable_clk(struct aicfb_info *fbi, u32 on) { ulong pixclk; struct de_funcs *de = fbi->de; struct di_funcs *di = fbi->di; if (on == AICFB_ON) { de->clk_enable(); pixclk = de->pixclk_rate(); di->pixclk2mclk(pixclk); di->clk_enable(); de->pixclk_enable(); } else { di->clk_disable(); de->clk_disable(); } } static void aicfb_update_alpha(struct aicfb_info *fbi) { struct aicfb_alpha_config alpha = {0}; struct de_funcs *de = fbi->de; alpha.layer_id = AICFB_LAYER_TYPE_UI; de->get_alpha_config(&alpha); /* TODO: set the alpha config */ de->update_alpha_config(&alpha); } static void aicfb_update_layer(struct aicfb_info *fbi, struct aicfb_dt *dt) { struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 1; switch (fbi->fb_rotate) { case 0: layer.buf.phy_addr[0] = fbi->fb_start_dma; layer.buf.size.width = dt->width; layer.buf.size.height = dt->height; layer.buf.stride[0] = dt->stride; break; case 90: case 270: { unsigned int stride = ALIGN_8B(dt->height * dt->format->bits_per_pixel / 8); layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size; layer.buf.size.width = dt->height; layer.buf.size.height = dt->width; layer.buf.stride[0] = stride; break; } case 180: layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size; layer.buf.size.width = dt->width; layer.buf.size.height = dt->height; layer.buf.stride[0] = dt->stride; break; default: pr_err("Invalid rotation degree\n"); return; } layer.buf.crop_en = 0; layer.buf.format = dt->format->format; layer.buf.flags = 0; de->update_layer_config(&layer); } static void aicfb_update_disp_prop(struct aicfb_info *fbi) { struct de_funcs *de = fbi->de; de->set_display_prop(fbi->disp_prop); } static void aicfb_de_timing_enable(struct aicfb_info *fbi) { struct de_funcs *de = fbi->de; de->timing_enable(false); } static inline int aicfb_calc_fb_size(struct aicfb_info *fbi, struct aicfb_dt *dt) { fbi->fb_size = dt->size; if (!dt->rotation && !fbi->set_hsbc) return fbi->fb_size; if (dt->disp_buf_num == 2 && dt->height_virtual == dt->height) return fbi->fb_size * 3; return fbi->fb_size * 2; } static int aicfb_pm_suspend(struct device *dev); static int aicfb_pm_resume(struct device *dev); static ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret) return ret; if (enable) { aicfb_pm_suspend(dev); aic_delay_ms(20); aicfb_pm_resume(dev); } return size; } static DEVICE_ATTR_WO(reset); static void aicfb_disable_de_di(struct aicfb_info *fbi) { struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; struct di_funcs *di = fbi->di; layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 0; de->update_layer_config(&layer); di->disable(); de->timing_disable(); aicfb_enable_clk(fbi, AICFB_OFF); } static void aicfb_enable_de_di(struct aicfb_info *fbi) { struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; struct di_funcs *di = fbi->di; aicfb_enable_clk(fbi, AICFB_ON); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 1; de->update_layer_config(&layer); di->enable(); if (di->send_cmd) di->send_cmd(0, NULL, 0); de->timing_enable(true); } static ssize_t reset_de_di_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par; bool enable; int ret; ret = kstrtobool(buf, &enable); if (ret) return ret; if (!enable) return size; mutex_lock(&fbi->mutex); aicfb_disable_de_di(fbi); aic_delay_ms(20); aicfb_enable_de_di(fbi); mutex_unlock(&fbi->mutex); return size; } static DEVICE_ATTR_WO(reset_de_di); #define timing_config_attr(field) \ static ssize_t \ field##_show(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ struct aicfb_data *fbd = dev_get_drvdata(dev); \ struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;\ struct videomode *vm = fbi->panel->vm; \ \ return sprintf(buf, "%d\n", (u32)vm->field); \ } \ static ssize_t \ field##_store(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t size) \ { \ struct aicfb_data *fbd = dev_get_drvdata(dev); \ struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;\ struct aic_panel *panel = fbi->panel; \ struct de_funcs *de = fbi->de; \ u32 field; \ int ret; \ \ ret = kstrtou32(buf, 0, &field); \ if (ret) \ return ret; \ \ panel->vm->field = field; \ de->set_mode(panel, panel->vm); \ \ return size; \ } \ static DEVICE_ATTR_RW(field); timing_config_attr(pixelclock); timing_config_attr(hactive); timing_config_attr(hfront_porch); timing_config_attr(hback_porch); timing_config_attr(hsync_len); timing_config_attr(vactive); timing_config_attr(vfront_porch); timing_config_attr(vback_porch); timing_config_attr(vsync_len); static ssize_t di_type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_dt *dt = &fbd->dt_lists[0]; s8 *type[] = {"rgb", "lvds", "dsi", "dbi"}; s32 i; for (i = 0; i < ARRAY_SIZE(type); i++) if (strncasecmp(dt->di_np->name, type[i], strlen(type[i])) == 0) break; return sprintf(buf, "display interface type: %d (%s)\n", i + 1, dt->di_np->name); } static DEVICE_ATTR_RO(di_type); static struct attribute *aic_fb_attrs[] = { &dev_attr_reset.attr, &dev_attr_reset_de_di.attr, &dev_attr_pixelclock.attr, &dev_attr_hactive.attr, &dev_attr_hfront_porch.attr, &dev_attr_hback_porch.attr, &dev_attr_hsync_len.attr, &dev_attr_vactive.attr, &dev_attr_vfront_porch.attr, &dev_attr_vback_porch.attr, &dev_attr_vsync_len.attr, &dev_attr_di_type.attr, NULL }; static const struct attribute_group aic_fb_attr_group = { .attrs = aic_fb_attrs, .name = "debug", }; static int aicfb_bind(struct device *dev) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_dt *dt = &fbd->dt_lists[0]; struct aicfb_info *fbi; struct videomode *vm; u32 fb_size; int ret; ret = component_bind_all(dev, dev); if (ret) { dev_err(dev, "component_bind_all failed\n"); goto err_bind; } /* alloc fb0_info */ ret = aicfb_alloc_fb(dev, 0); if (ret) { dev_err(dev, "alloc fb0_info failed\n"); goto err_fb0; } fbi = (struct aicfb_info *)fbd->info[0]->par; /* set display properties */ fbi->disp_prop = &dt->disp_prop; fbi->set_hsbc = need_set_hsbc(fbi->disp_prop); /* Find the component device, and get video mode */ aicfb_find_de(fbd, 0); aicfb_find_di(fbd, 0); aicfb_find_panel(fbd, 0); aicfb_get_panel_info(fbi, &vm); aicfb_register_panel_callback(fbi); /* Initialize fb_var_screeninfo */ aicfb_videomode_to_fbdata(dt, vm); aicfb_format_to_var(&fbd->info[0]->var, dt); /* request fb0 framebuffer */ fb_size = aicfb_calc_fb_size(fbi, dt); fbi->fb_start = dma_alloc_attrs(dev, PAGE_ALIGN(fb_size), &fbi->fb_start_dma, GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); if (fbi->fb_start == NULL) { ret = -ENOMEM; goto err_dma_alloc; } fbi->dma_coherent = true; dev_info(dev, "%d allocated for %s, %#llx/%#llx\n", fb_size, fbi->name, (u64)fbi->fb_start, fbi->fb_start_dma); aicfb_fb_info_setup(fbd->info[0], fbd); ret = fb_alloc_cmap(&fbd->info[0]->cmap, 256, 0); if (ret < 0) { dev_err(dev, "Failed to alloc cmap\n"); goto err_cmap; } /* Turn on the component device */ aicfb_enable_clk(fbi, AICFB_ON); aicfb_update_alpha(fbi); aicfb_update_disp_prop(fbi); /* Avoid enable panel again */ if (uboot_logo_on && uboot_logo_size) { void *src = phys_to_virt(uboot_logo_base); void *dst = NULL; if (fbi->fb_rotate) dst = phys_to_virt(fbi->fb_start_dma + fbi->fb_size); else dst = fbi->fb_start; memcpy(dst, src, uboot_logo_size); /* * When the logo data is ready, update UI layer. * If update UI layer first, the screen will tear. */ aicfb_update_layer(fbi, dt); /* Ensure that display engine interrupts are enabled */ aicfb_de_timing_enable(fbi); } else { aicfb_update_layer(fbi, dt); aicfb_enable_panel(fbi, AICFB_ON); } ret = register_framebuffer(fbd->info[0]); if (ret < 0) { dev_err(dev, "Failed to register fb0: %d\n", ret); ret = -ENXIO; goto err_register; } dev_info(dev, "loaded to /dev/fb%d <%s>.\n", fbd->info[0]->node, fbd->info[0]->fix.id); ret = sysfs_create_group(&dev->kobj, &aic_fb_attr_group); if (ret) { dev_err(dev, "Failed to create %s node.\n", aic_fb_attr_group.name); return ret; } mutex_init(&fbi->mutex); return 0; err_register: fb_dealloc_cmap(&fbd->info[0]->cmap); err_cmap: dma_free_coherent(dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start, fbi->fb_start_dma); err_dma_alloc: framebuffer_release(fbd->info[0]); err_fb0: component_unbind_all(dev, dev); err_bind: return ret; } static void aicfb_unbind(struct device *dev) { struct aicfb_data *aicfb = dev_get_drvdata(dev); component_unbind_all(dev, dev); unregister_framebuffer(aicfb->info[0]); framebuffer_release(aicfb->info[0]); sysfs_remove_group(&dev->kobj, &aic_fb_attr_group); } static const struct component_master_ops aic_component_ops = { .bind = aicfb_bind, .unbind = aicfb_unbind, }; static int aicfb_probe(struct platform_device *pdev) { struct component_match *match = NULL; int ret; struct aicfb_data *fbd; fbd = devm_kzalloc(&pdev->dev, sizeof(*fbd), GFP_KERNEL); if (!fbd) return -ENOMEM; dev_dbg(&pdev->dev, "%s()\n", __func__); /* parse fb0 dt */ dev_set_drvdata(&pdev->dev, fbd); ret = aicfb_parse_dt_by_fb_id(pdev, 0); if (ret) return ret; /* add fb0 component */ aicfb_add_match_component(pdev, &match, 0); ret = component_master_add_with_match(&pdev->dev, &aic_component_ops, match); if (ret) { dev_err(&pdev->dev, "component_master_add_with_match failed\n"); return ret; } fbd->dev = &pdev->dev; return 0; } void aicfb_release_device_node(struct device *dev, u32 id) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[id]->par; put_device(fbi->de_dev); put_device(fbi->di_dev); put_device(fbi->panel_dev); of_node_put(fbd->dt_lists[id].fb_np); of_node_put(fbd->dt_lists[id].de_np); of_node_put(fbd->dt_lists[id].di_np); of_node_put(fbd->dt_lists[id].panel_np); } static int aicfb_remove(struct platform_device *pdev) { component_master_del(&pdev->dev, &aic_component_ops); aicfb_release_device_node(&pdev->dev, 0); return 0; } static int aicfb_pm_suspend(struct device *dev) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par; struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; mutex_lock(&fbi->mutex); aicfb_enable_panel(fbi, AICFB_OFF); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 0; de->update_layer_config(&layer); aicfb_enable_clk(fbi, AICFB_OFF); mutex_unlock(&fbi->mutex); return 0; } static int aicfb_pm_resume(struct device *dev) { struct aicfb_data *fbd = dev_get_drvdata(dev); struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par; struct aicfb_layer_data layer = {0}; struct de_funcs *de = fbi->de; mutex_lock(&fbi->mutex); aicfb_enable_clk(fbi, AICFB_ON); aicfb_update_alpha(fbi); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->get_layer_config(&layer); layer.enable = 1; de->update_layer_config(&layer); aicfb_enable_panel(fbi, AICFB_ON); mutex_unlock(&fbi->mutex); return 0; } #ifdef CONFIG_PM static UNIVERSAL_DEV_PM_OPS(aicfb_pm_ops, aicfb_pm_suspend, aicfb_pm_resume, NULL); #endif static const struct of_device_id aicfb_match_table[] = { {.compatible = "artinchip,aic-framebuffer",}, {}, }; MODULE_DEVICE_TABLE(of, aicfb_match_table); static struct platform_driver aicfb_driver = { .probe = aicfb_probe, .remove = aicfb_remove, .driver = { .name = AICFB_NAME, .of_match_table = aicfb_match_table, #ifdef CONFIG_PM .pm = &aicfb_pm_ops, #endif }, }; module_platform_driver(aicfb_driver); MODULE_AUTHOR("Ning Fang "); MODULE_DESCRIPTION("AIC framebuffer driver"); MODULE_ALIAS("platform:" AICFB_NAME); MODULE_LICENSE("GPL v2");