/* * Copyright (C) 2024-2025, ArtInChip Technology Co., Ltd * * SPDX-License-Identifier: Apache-2.0 * * Authors: huahui.mai@artinchip.com */ #include #include #include "drv_fb.h" #include "drv_fb_helper.h" #ifdef AIC_FB_ROTATE_EN #include #endif #if defined(KERNEL_RTTHREAD) #include #endif #undef pr_debug #ifdef AIC_FB_DRV_DEBUG #define pr_debug pr_info #else #define pr_debug(fmt, ...) #endif static struct aicfb_info *g_aicfb_info; static bool aicfb_probed = false; static inline struct aicfb_info *aicfb_get_drvdata(void) { return g_aicfb_info; } static inline void aicfb_set_drvdata(struct aicfb_info *fbi) { g_aicfb_info = fbi; } #ifdef AIC_FB_ROTATE_EN static int aicfb_rotate(struct aicfb_info *fbi, struct aicfb_layer_data *layer, u32 buf_id) { struct ge_bitblt blt = {0}; struct aic_ge_client *client = NULL; /* source buffer */ blt.src_buf.buf_type = MPP_PHY_ADDR; blt.src_buf.phy_addr[0] = (uintptr_t)fbi->fb_start + fbi->fb_size * buf_id; blt.src_buf.stride[0] = fbi->stride; blt.src_buf.size.width = fbi->width; blt.src_buf.size.height = fbi->height; 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; 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; }; return hal_ge_control(client, IOC_GE_BITBLT, &blt); } #endif static int aicfb_pan_display(struct aicfb_info *fbi, u32 buf_id) { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; #if !defined(AIC_PAN_DISPLAY) && !defined(AIC_FB_ROTATE_EN) #ifdef AIC_BOOTLOADER_CMD_PROGRESS_BAR #else pr_err("pan display do not enabled\n"); return -EINVAL; #endif #endif layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 1; layer.buf.phy_addr[0] = (uintptr_t)fbi->fb_start + fbi->fb_size * buf_id; #ifdef AIC_FB_ROTATE_EN if (fbi->fb_rotate) { layer.buf.phy_addr[0] += fbi->fb_size * AIC_FB_DRAW_BUF_NUM; aicfb_rotate(fbi, &layer, buf_id); } #endif de->de_funcs->update_layer_config(&layer); return 0; } static int aicfb_ioctl_wait_for_vsync(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->wait_for_vsync(); } static int aicfb_ioctl_get_screen_register(struct aicfb_info *fbi, void *args) { if (!fbi->di->di_funcs->read_cmd) { pr_err("display interface do not supports AICFB_GET_SCREENREG\n"); return -EINVAL; } return fbi->di->di_funcs->read_cmd(*(u32 *)args); } static int aicfb_ioctl_get_screen_info(struct aicfb_info *fbi, void *args) { struct aicfb_screeninfo *info; info = (struct aicfb_screeninfo *) args; info->format = AICFB_FORMAT; info->bits_per_pixel = fbi->bits_per_pixel; info->stride = fbi->stride; info->framebuffer = (unsigned char *)fbi->fb_start; info->width = fbi->width; info->height = fbi->height; info->smem_len = fbi->fb_size; return 0; } static int aicfb_ioctl_pan_display(struct aicfb_info *fbi, void *args) { return aicfb_pan_display(fbi, *(int *)args); } static int aicfb_ioctl_power_on(struct aicfb_info *fbi, void *args) { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; if (fbi->power_on) return 0; aicfb_enable_clk(fbi, AICFB_ON); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 1; de->de_funcs->update_layer_config(&layer); aicfb_enable_panel(fbi, AICFB_ON); fbi->power_on = true; return 0; } static int aicfb_ioctl_power_off(struct aicfb_info *fbi, void *args) { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; if (!fbi->power_on) return 0; aicfb_enable_panel(fbi, AICFB_OFF); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 0; de->de_funcs->update_layer_config(&layer); aicfb_enable_clk(fbi, AICFB_OFF); fbi->power_on = false; return 0; } static int aicfb_ioctl_get_layer_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->get_layer_config(args); } static int aicfb_ioctl_update_layer_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->update_layer_config(args); } static int aicfb_ioctl_update_layer_cfg_list(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->update_layer_config_list(args); } static int aicfb_ioctl_get_alpha_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->get_alpha_config(args); } static int aicfb_ioctl_update_alpha_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->update_alpha_config(args); } static int aicfb_ioctl_get_ck_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->get_ck_config(args); } static int aicfb_ioctl_update_ck_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->update_ck_config(args); } static int aicfb_ioctl_set_disp_prop(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->set_display_prop(args); } static int aicfb_ioctl_get_disp_prop(struct aicfb_info *fbi, void *args) { u32 ret = 0; struct aicfb_disp_prop prop; ret = fbi->de->de_funcs->get_display_prop(&prop); if (ret) return ret; memcpy(args, &prop, sizeof(struct aicfb_disp_prop)); return ret; } static int aicfb_ioctl_get_ccm_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->get_ccm_config(args); } static int aicfb_ioctl_update_ccm_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->set_ccm_config(args); } static int aicfb_ioctl_update_gamma_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->set_gamma_config(args); } static int aicfb_ioctl_get_gamma_cfg(struct aicfb_info *fbi, void *args) { return fbi->de->de_funcs->get_gamma_config(args); } #ifdef AIC_DISPLAY_TEST static int aicfb_ioctl_pq_set_cfg(struct aicfb_info *fbi, void *args) { struct aicfb_pq_config *config = args; aicfb_pq_set_config(fbi, config); return 0; } static int aicfb_ioctl_pq_get_cfg(struct aicfb_info *fbi, void *args) { struct aicfb_pq_config *config = args; aicfb_pq_get_config(fbi, config); return 0; } #endif /* AIC_DISPLAY_TEST */ struct aicfb_ioctl_cmd { u32 cmd; int (*f)(struct aicfb_info *fbi, void *args); char desc[32]; }; struct aicfb_ioctl_cmd aicfb_ioctl_cmds[] = { {AICFB_WAIT_FOR_VSYNC, aicfb_ioctl_wait_for_vsync, "Wait for vsync"}, {AICFB_PAN_DISPLAY, aicfb_ioctl_pan_display, "Pan display"}, {AICFB_UPDATE_LAYER_CONFIG, aicfb_ioctl_update_layer_cfg, "Update layer cfg"}, {AICFB_GET_LAYER_CONFIG, aicfb_ioctl_get_layer_cfg,"Get layer cfg"}, {AICFB_GET_SCREENINFO, aicfb_ioctl_get_screen_info, "Get screen info"}, {AICFB_UPDATE_LAYER_CONFIG_LISTS, aicfb_ioctl_update_layer_cfg_list, "Update layer cfg list"}, {AICFB_GET_ALPHA_CONFIG, aicfb_ioctl_get_alpha_cfg, "Get alpha cfg"}, {AICFB_UPDATE_ALPHA_CONFIG, aicfb_ioctl_update_alpha_cfg, "Update alpha cfg"}, {AICFB_GET_CK_CONFIG, aicfb_ioctl_get_ck_cfg, "Get Color Key cfg"}, {AICFB_UPDATE_CK_CONFIG, aicfb_ioctl_update_ck_cfg, "Update Color Key cfg"}, {AICFB_SET_DISP_PROP, aicfb_ioctl_set_disp_prop, "Set display prop"}, {AICFB_GET_DISP_PROP, aicfb_ioctl_get_disp_prop, "Get display prop"}, {AICFB_GET_CCM_CONFIG, aicfb_ioctl_get_ccm_cfg, "Get CCM cfg"}, {AICFB_UPDATE_CCM_CONFIG, aicfb_ioctl_update_ccm_cfg, "Update CCM cfg"}, {AICFB_UPDATE_GAMMA_CONFIG, aicfb_ioctl_update_gamma_cfg, "Update gamma cfg"}, {AICFB_GET_GAMMA_CONFIG, aicfb_ioctl_get_gamma_cfg, "Get gamma cfg"}, {AICFB_POWERON, aicfb_ioctl_power_on, "Power on screen"}, {AICFB_POWEROFF, aicfb_ioctl_power_off, "Power off screen"}, {AICFB_GET_SCREENREG, aicfb_ioctl_get_screen_register, "Get screen register"}, #ifdef AIC_DISPLAY_TEST {AICFB_PQ_SET_CONFIG, aicfb_ioctl_pq_set_cfg, "PQ Tools set cfg"}, {AICFB_PQ_GET_CONFIG, aicfb_ioctl_pq_get_cfg, "PQ Tools get cfg"}, #endif {0xFFFF, NULL, ""} }; int aicfb_ioctl(int cmd, void *args) { struct aicfb_ioctl_cmd *ioctl = aicfb_ioctl_cmds; struct aicfb_info *fbi = aicfb_get_drvdata(); do { if ((ioctl->cmd == cmd) && ioctl->f) return ioctl->f(fbi, args); ioctl++; } while (ioctl->cmd != 0xFFFF); pr_err("Invalid ioctl cmd %#x\n", cmd); return -EINVAL; } #if defined(KERNEL_RTTHREAD) #ifdef RT_USING_PM static int aicfb_suspend(const struct rt_device *device, rt_uint8_t mode) { struct aicfb_info *fbi = device->user_data; struct platform_driver *de = fbi->de; switch (mode) { case PM_SLEEP_MODE_IDLE: break; case PM_SLEEP_MODE_LIGHT: case PM_SLEEP_MODE_DEEP: case PM_SLEEP_MODE_STANDBY: { struct aicfb_layer_data layer = {0}; #ifdef AIC_PM_INDEPENDENT_POWER_KEY fbi->panel->independent_pwkey = 1; #endif aicfb_enable_panel(fbi, AICFB_OFF); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 0; de->de_funcs->update_layer_config(&layer); aicfb_enable_clk(fbi, AICFB_OFF); } break; default: break; } return 0; } static void aicfb_resume(const struct rt_device *device, rt_uint8_t mode) { struct aicfb_info *fbi = device->user_data; struct platform_driver *de = fbi->de; switch (mode) { case PM_SLEEP_MODE_IDLE: break; case PM_SLEEP_MODE_LIGHT: case PM_SLEEP_MODE_DEEP: case PM_SLEEP_MODE_STANDBY: { struct aicfb_layer_data layer = {0}; aicfb_enable_clk(fbi, AICFB_ON); layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 1; de->de_funcs->update_layer_config(&layer); aicfb_enable_panel(fbi, AICFB_ON); #ifdef AIC_PM_INDEPENDENT_POWER_KEY fbi->panel->independent_pwkey = 0; #endif } break; default: break; } } static struct rt_device_pm_ops aicfb_pm_ops = { SET_DEVICE_PM_OPS(aicfb_suspend, aicfb_resume) NULL, }; #endif /* RT_USING_PM */ rt_err_t aicfb_control(rt_device_t dev, int cmd, void *args) { struct aicfb_info *fbi = aicfb_get_drvdata(); int command; if (!fbi) return RT_EINVAL; switch (cmd) { case RTGRAPHIC_CTRL_WAIT_VSYNC: command = AICFB_WAIT_FOR_VSYNC; break; case RTGRAPHIC_CTRL_GET_INFO: { struct rt_device_graphic_info *info; info = (struct rt_device_graphic_info *) args; info->pixel_format = AICFB_FORMAT; info->bits_per_pixel = fbi->bits_per_pixel; info->pitch = (rt_uint16_t)fbi->stride; info->framebuffer = (rt_uint8_t*)fbi->fb_start; info->width = (rt_uint16_t)fbi->width; info->height = (rt_uint16_t)fbi->height; info->smem_len = fbi->fb_size; return RT_EOK; } case RTGRAPHIC_CTRL_PAN_DISPLAY: command = AICFB_PAN_DISPLAY; break; case RTGRAPHIC_CTRL_POWERON: command = AICFB_POWERON; break; case RTGRAPHIC_CTRL_POWEROFF: command = AICFB_POWEROFF; break; default: command = cmd; break; } return aicfb_ioctl(command, args); } #ifdef RT_USING_DEVICE_OPS static const struct rt_device_ops aicfb_ops = { RT_NULL, RT_NULL, RT_NULL, RT_NULL, RT_NULL, aicfb_control }; #endif /* RT_USING_DEVICE_OPS */ static int aic_rt_fb_device_init(struct aicfb_info *fbi) { struct rt_device *device = &fbi->device; int ret; device->type = RT_Device_Class_Graphic; #ifdef RT_USING_DEVICE_OPS device->ops = &aicfb_ops; #else device->init = RT_NULL; device->open = RT_NULL; device->close = RT_NULL; device->read = RT_NULL; device->write = RT_NULL; device->control = aicfb_control; #endif /* RT_USING_DEVICE_OPS */ device->user_data = fbi; /* register to device manager */ ret = rt_device_register(device, "aicfb", RT_DEVICE_FLAG_RDWR); if(ret != RT_EOK) { pr_err("aicfb registered fail!\n"); return ret; } #ifdef RT_USING_PM rt_pm_device_register(device, &aicfb_pm_ops); #endif return RT_EOK; } #endif /* KERNEL_RTTHREAD */ static struct platform_driver *drivers[] = { #ifdef AIC_DISP_DE_DRV &artinchip_de_driver, #endif #ifdef AIC_DISP_RGB &artinchip_rgb_driver, #endif #ifdef AIC_DISP_LVDS &artinchip_lvds_driver, #endif #ifdef AIC_DISP_MIPI_DSI &artinchip_dsi_driver, #endif #ifdef AIC_DISP_MIPI_DBI &artinchip_dbi_driver, #endif }; static struct platform_driver * aicfb_find_component(struct platform_driver **drv, int id, int len) { struct platform_driver **driver = drv; int ret, i; for (i = 0; i < len; i++) { if (driver[i]->component_type == id) break; } if (i >= len || !driver[i] || !driver[i]->probe) return NULL; ret = driver[i]->probe(); if (ret) return NULL; pr_debug("find component: %s\n", driver[i]->name); return driver[i]; } static inline size_t aicfb_calc_fb_size(struct aicfb_info *fbi) { unsigned int draw_buf_num = 0; unsigned int disp_buf_num = 0; #ifdef AIC_FB_ROTATE_EN draw_buf_num = AIC_FB_DRAW_BUF_NUM; #else draw_buf_num = 0; #endif #ifdef AIC_PAN_DISPLAY disp_buf_num = 2; #else disp_buf_num = 1; #endif return fbi->fb_size * (disp_buf_num + draw_buf_num); } int aicfb_probe(void) { struct aicfb_info *fbi; #ifndef AIC_MPP_VIN_DEV size_t fb_size = 0; #endif int ret = -EINVAL; if (aicfb_probed) return 0; fbi = aicos_malloc(0, sizeof(*fbi)); if (!fbi) { pr_err("alloc fb_info failed\n"); return -ENOMEM; } memset(fbi, 0x0, sizeof(*fbi)); fbi->de = aicfb_find_component(drivers, AIC_DE_COM, ARRAY_SIZE(drivers)); if (!fbi->de) { pr_err("failed to find de component\n"); goto err; } fbi->di = aicfb_find_component(drivers, AIC_DI_TYPE, ARRAY_SIZE(drivers)); if (!fbi->di) { pr_err("failed to find di component\n"); goto err; } fbi->panel = aic_find_panel(AIC_DI_TYPE); if (!fbi->panel) { pr_err("failed to find panel component\n"); goto err; } aicfb_get_panel_info(fbi); aicfb_fb_info_setup(fbi); aicfb_register_panel_callback(fbi); #ifndef AIC_MPP_VIN_DEV fb_size = aicfb_calc_fb_size(fbi); /* fb_start must be cache line align */ fbi->fb_start = aicos_malloc_align(MEM_CMA, fb_size, CACHE_LINE_SIZE); if (!fbi->fb_start) { ret = -ENOMEM; pr_err("alloc frame buffer failed\n"); goto err; } pr_info("fb0 allocated at 0x%x\n", (u32)(uintptr_t)fbi->fb_start); fb_color_block(fbi, fb_size); #endif #if defined(KERNEL_RTTHREAD) ret = aic_rt_fb_device_init(fbi); if(ret != RT_EOK) goto err; #endif aicfb_probed = true; fbi->power_on = false; aicfb_set_drvdata(fbi); aicfb_enable_clk(fbi, AICFB_ON); aicfb_update_alpha(fbi); aicfb_update_layer(fbi); #if defined(AIC_DISP_COLOR_BLOCK) fbi->power_on = true; aicfb_enable_panel(fbi, AICFB_ON); #endif return 0; err: free(fbi); fbi = NULL; return ret; } #if defined(KERNEL_RTTHREAD) && !defined(AIC_MPP_VIN_DEV) INIT_DEVICE_EXPORT(aicfb_probe); #endif void aicfb_remove(void) { struct aicfb_info *fbi = aicfb_get_drvdata(); aicfb_probed = false; /* * FIXME: We should release fbi and framebuffer at the same time. * But we keep the fbi pointer to ensure that some ioctl still works properly. */ if (fbi->fb_start) aicos_free_align(MEM_CMA, fbi->fb_start); }