/* * 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; } int aicfb_ioctl(int cmd, void *args) { struct aicfb_info *fbi = aicfb_get_drvdata(); if (!fbi) return -EINVAL; switch (cmd) { case AICFB_WAIT_FOR_VSYNC: return fbi->de->de_funcs->wait_for_vsync(); case AICFB_GET_SCREENREG: { 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); } case AICFB_GET_SCREENINFO: { 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; break; } case AICFB_PAN_DISPLAY: return aicfb_pan_display(fbi, *(int *)args); case AICFB_POWERON: { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; if (fbi->power_on) break; 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; break; } case AICFB_POWEROFF: { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; if (!fbi->power_on) break; 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; break; } case AICFB_GET_LAYER_CONFIG: return fbi->de->de_funcs->get_layer_config(args); case AICFB_UPDATE_LAYER_CONFIG: return fbi->de->de_funcs->update_layer_config(args); case AICFB_UPDATE_LAYER_CONFIG_LISTS: return fbi->de->de_funcs->update_layer_config_list(args); case AICFB_GET_ALPHA_CONFIG: return fbi->de->de_funcs->get_alpha_config(args); case AICFB_UPDATE_ALPHA_CONFIG: return fbi->de->de_funcs->update_alpha_config(args); case AICFB_GET_CK_CONFIG: return fbi->de->de_funcs->get_ck_config(args); case AICFB_UPDATE_CK_CONFIG: return fbi->de->de_funcs->update_ck_config(args); case AICFB_SET_DISP_PROP: return fbi->de->de_funcs->set_display_prop(args); case AICFB_GET_DISP_PROP: { s32 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)); break; } case AICFB_GET_CCM_CONFIG: return fbi->de->de_funcs->get_ccm_config(args); case AICFB_UPDATE_CCM_CONFIG: return fbi->de->de_funcs->set_ccm_config(args); case AICFB_UPDATE_GAMMA_CONFIG: return fbi->de->de_funcs->set_gamma_config(args); case AICFB_GET_GAMMA_CONFIG: return fbi->de->de_funcs->get_gamma_config(args); case AICFB_PQ_SET_CONFIG: { struct aicfb_pq_config *config = args; aicfb_pq_set_config(fbi, config); break; } case AICFB_PQ_GET_CONFIG: { struct aicfb_pq_config *config = args; aicfb_pq_get_config(fbi, config); break; } default: pr_err("Invalid ioctl cmd %#x\n", cmd); return -EINVAL; } return 0; } #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; #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); #endif fb_color_block(fbi); #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); }