// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2021 ArtInChip Technology Co.,Ltd * Huahui Mai */ #include #include #include #include #include #include #include #include #include #include #include "aic_com.h" DECLARE_GLOBAL_DATA_PTR; #define AICFB_ON 1 #define AICFB_OFF 0 struct aicfb_info { phys_addr_t fb_start; struct udevice *de_dev; struct udevice *di_dev; struct udevice *panel_dev; struct de_funcs *de; struct di_funcs *di; struct aic_panel *panel; }; struct aicfb_format aicfb_format_lists[] = { {"a8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {24, 8}, AIC_FMT_ARGB_8888}, {"a8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {24, 8}, AIC_FMT_ABGR_8888}, {"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, AIC_FMT_XRGB_8888}, {"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, AIC_FMT_RGB_888}, {"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, AIC_FMT_RGB_565}, {"a1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {15, 1}, AIC_FMT_ARGB_1555}, }; static int get_dev_by_port(ofnode node, enum uclass_id id, struct udevice **devp) { ofnode endpoint, remote; u32 remote_phandle; struct udevice *dev; int ret; endpoint = ofnode_first_subnode(node); ret = ofnode_read_u32(endpoint, "remote-endpoint", &remote_phandle); if (ret) return ret; remote = ofnode_get_by_phandle(remote_phandle); if (!ofnode_valid(remote)) return -EINVAL; while (ofnode_valid(remote)) { remote = ofnode_get_parent(remote); if (!ofnode_valid(remote)) { debug("%s: no UCLASS for remote-endpoint\n", __func__); return -EINVAL; } uclass_get_device_by_ofnode(id, remote, &dev); if (dev) { *devp = dev; debug("Found device: %s\n", dev_read_name(dev)); break; } }; return 0; } static int aicfb_find_de(struct udevice *dev) { struct aicfb_dt *plat = dev_get_plat(dev); struct aicfb_info *priv = dev_get_priv(dev); ofnode port; int ret; port = ofnode_find_subnode(plat->fb_np, "port"); if (!ofnode_valid(port)) { debug("%s(%s): Failed to find port subnode\n", __func__, dev_read_name(dev)); } ret = get_dev_by_port(port, UCLASS_DISPLAY, &priv->de_dev); if (ret) { debug("Failed to find de udevice\n"); return ret; } priv->de = dev_get_priv(priv->de_dev); return 0; } static int aicfb_find_di(struct udevice *dev) { struct aicfb_info *priv = dev_get_priv(dev); ofnode port; int ret; port = dev_read_subnode(priv->de_dev, "port@1"); ret = get_dev_by_port(port, UCLASS_DISPLAY, &priv->di_dev); if (ret) { debug("Failed to find di udevice\n"); return ret; } priv->di = dev_get_priv(priv->di_dev); return 0; } static int aicfb_find_panel(struct udevice *dev) { struct aicfb_info *priv = dev_get_priv(dev); ofnode port; int ret; port = dev_read_subnode(priv->di_dev, "port@1"); ret = get_dev_by_port(port, UCLASS_PANEL, &priv->panel_dev); if (ret) { debug("Failed to find panel udevice\n"); return ret; } priv->panel = dev_get_priv(priv->panel_dev); return 0; } static void aicfb_video_mode_to_dt(struct udevice *dev, struct fb_videomode *vm) { struct aicfb_dt *dt = dev_get_plat(dev); struct aicfb_format *f = dt->format; if (!dt->width) dt->width = vm->xres; if (!dt->height) dt->height = vm->yres; if (!dt->stride) dt->stride = ALIGN_8B(dt->width * f->bits_per_pixel / 8); } static void aicfb_get_panel_info(struct aicfb_info *priv, struct fb_videomode *vm) { struct de_funcs *de = priv->de; struct di_funcs *di = priv->di; struct aic_panel *panel = priv->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 *priv) { struct aic_panel *p = priv->panel; struct aic_panel_callbacks cb; cb.di_enable = priv->di->enable; cb.di_send_cmd = priv->di->send_cmd; cb.di_set_videomode = priv->di->set_videomode; cb.timing_enable = priv->de->timing_enable; p->funcs->register_callback(priv->panel, &cb); } static int aicfb_parse_dt(struct udevice *dev) { struct aicfb_dt *dt = dev_get_plat(dev); const char *format; int i; dt->fb_np = dev_read_subnode(dev, "fb"); if (!ofnode_valid(dt->fb_np)) { debug("%s(%s): 'fb0' subnode not found\n", __func__, dev_read_name(dev)); return -EINVAL; } if (ofnode_read_u32(dt->fb_np, "width", &dt->width)) dt->width = 0; if (ofnode_read_u32(dt->fb_np, "height", &dt->height)) dt->height = 0; if (ofnode_read_u32(dt->fb_np, "stride", &dt->stride)) dt->stride = 0; dt->format = NULL; format = ofnode_read_string(dt->fb_np, "format"); if (!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; } } return 0; } static void aicfb_enable_panel(struct udevice *dev, u32 on) { struct aicfb_info *priv = dev_get_priv(dev); struct aic_panel *p = priv->panel; p->funcs->prepare(p); p->funcs->enable(p); } static void aicfb_enable_clk(struct aicfb_info *priv, u32 on) { ulong pixclk; struct de_funcs *de = priv->de; struct di_funcs *di = priv->di; de->clk_enable(); pixclk = de->pixclk_rate(); di->pixclk2mclk(pixclk); di->clk_enable(); de->pixclk_enable(); } static void aicfb_update_layer(struct udevice *dev) { struct video_priv *uc_priv = dev_get_uclass_priv(dev); struct aicfb_info *priv = dev_get_priv(dev); struct aicfb_dt *dt = dev_get_plat(dev); struct aicfb_layer_data layer = {0}; struct de_funcs *de = priv->de; layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; layer.enable = 1; layer.buf.phy_addr[0] = (uintptr_t)uc_priv->fb; layer.buf.size.width = dt->width; layer.buf.size.height = dt->height; layer.buf.stride[0] = dt->stride; layer.buf.format = dt->format->format; de->update_layer_config(&layer); } static void aicfb_video_init(struct udevice *dev) { struct aicfb_dt *dt = dev_get_plat(dev); struct video_priv *uc_priv = dev_get_uclass_priv(dev); enum video_log2_bpp l2bpp; switch (dt->format->format) { case AIC_FMT_ARGB_8888: l2bpp = VIDEO_BPP32; break; case AIC_FMT_RGB_565: l2bpp = VIDEO_BPP16; break; default: l2bpp = VIDEO_BPP8; } uc_priv->xsize = dt->width; uc_priv->ysize = dt->height; uc_priv->line_length = dt->stride; uc_priv->bpix = l2bpp; video_set_flush_dcache(dev, true); #ifdef CONFIG_SPL_BUILD struct aicfb_info *priv = dev_get_priv(dev); uc_priv->fb = (void *)priv->fb_start; uc_priv->fb_size = dt->stride * dt->height; memset(uc_priv->fb, 0xFF, uc_priv->fb_size); flush_dcache_range(priv->fb_start, priv->fb_start + uc_priv->fb_size); #endif } static int aicfb_probe(struct udevice *dev) { struct aicfb_info *priv = dev_get_priv(dev); struct video_uc_plat *plat = dev_get_uclass_plat(dev); struct fb_videomode vm; #ifndef CONFIG_SPL_BUILD /* Before relocation we don't need to do anything */ if (!(gd->flags & GD_FLG_RELOC)) return 0; priv->fb_start = plat->base; debug("%s() fb_start: fb_base 0x%lx, size 0x%x\n", __func__, plat->base, plat->size); #else int parent, node, ret; struct fdt_resource reg_res; void *blob = (void *)CONFIG_SYS_SPL_ARGS_ADDR; parent = fdt_path_offset(blob, "/reserved-memory"); if (parent < 0) { pr_err("not find reserved-memory\n"); return parent; } fdt_for_each_subnode(node, blob, parent) { const char *name = fdt_get_name(blob, node, NULL); if (strncmp(name, "aic-logo", 8) == 0) { ret = fdt_get_resource(blob, node, "reg", 0, ®_res); priv->fb_start = reg_res.start; plat->base = reg_res.start; break; } } #endif /* Find the component device, and turn on */ if (aicfb_find_de(dev)) { dev_err(dev, "Failed to find display engine\n"); return -EINVAL; } if (aicfb_find_di(dev)) { dev_err(dev, "Failed to find display interface\n"); return -EINVAL; } if (aicfb_find_panel(dev)) { dev_err(dev, "Failed to find panel\n"); return -EINVAL; } aicfb_get_panel_info(priv, &vm); aicfb_video_mode_to_dt(dev, &vm); aicfb_register_panel_callback(priv); aicfb_enable_clk(priv, AICFB_ON); aicfb_video_init(dev); return 0; } static int aicfb_ofdata_to_platdata(struct udevice *dev) { int ret; ret = aicfb_parse_dt(dev); if (ret) { debug("parse_dt failed %s(%s)\n", __func__, dev_read_name(dev)); return ret; } return 0; } static int aicfb_bind(struct udevice *dev) { struct video_uc_plat *plat = dev_get_uclass_plat(dev); plat->size = CONFIG_VIDEO_ARTINCHIP_MAX_XRES * CONFIG_VIDEO_ARTINCHIP_MAX_YRES * (CONFIG_VIDEO_ARTINCHIP_MAX_BPP >> 3); return 0; } struct aicfb_ops fb_ops = { .update_layer = aicfb_update_layer, .enable_panel = aicfb_enable_panel, }; static const struct udevice_id aicfb_match_ids[] = { { .compatible = "artinchip,aic-framebuffer", .data = (ulong)&fb_ops }, { /* sentinel */ } }; U_BOOT_DRIVER(aicfb) = { .name = "aicfb", .id = UCLASS_VIDEO, .of_match = aicfb_match_ids, .bind = aicfb_bind, .probe = aicfb_probe, .flags = DM_FLAG_PRE_RELOC, .of_to_plat = aicfb_ofdata_to_platdata, .priv_auto = sizeof(struct aicfb_info), .plat_auto = sizeof(struct aicfb_dt), };