/* * Copyright (C) 2025, ArtInChip Technology Co., Ltd * * SPDX-License-Identifier: Apache-2.0 * * Authors: huahui.mai@artinchip.com * * ArtInChip framebuffer helper functions */ #include "drv_fb_helper.h" void aicfb_enable_clk(struct aicfb_info *fbi, u32 on) { struct platform_driver *de = fbi->de; struct platform_driver *di = fbi->di; if (on == AICFB_ON) { de->de_funcs->clk_enable(); di->di_funcs->clk_enable(); } else { di->di_funcs->clk_disable(); de->de_funcs->clk_disable(); } } void aicfb_update_alpha(struct aicfb_info *fbi) { struct platform_driver *de = fbi->de; struct aicfb_alpha_config alpha = {0}; alpha.layer_id = AICFB_LAYER_TYPE_UI; de->de_funcs->get_alpha_config(&alpha); de->de_funcs->update_alpha_config(&alpha); } void aicfb_update_layer(struct aicfb_info *fbi) { struct platform_driver *de = fbi->de; struct aicfb_layer_data layer = {0}; layer.layer_id = AICFB_LAYER_TYPE_UI; layer.rect_id = 0; de->de_funcs->get_layer_config(&layer); layer.enable = 1; switch (fbi->fb_rotate) { case 0: layer.buf.size.width = fbi->width; layer.buf.size.height = fbi->height; layer.buf.stride[0] = fbi->stride; layer.buf.phy_addr[0] = (uintptr_t)fbi->fb_start; break; #ifdef AIC_FB_ROTATE_EN case 90: case 270: { unsigned int stride = ALIGN_8B(fbi->height * fbi->bits_per_pixel / 8); layer.buf.phy_addr[0] = (uintptr_t)fbi->fb_start + fbi->fb_size * AIC_FB_DRAW_BUF_NUM; layer.buf.size.width = fbi->height; layer.buf.size.height = fbi->width; layer.buf.stride[0] = stride; break; } case 180: layer.buf.phy_addr[0] = (uintptr_t)fbi->fb_start + fbi->fb_size * AIC_FB_DRAW_BUF_NUM; layer.buf.size.width = fbi->width; layer.buf.size.height = fbi->height; layer.buf.stride[0] = fbi->stride; break; #endif default: pr_err("Invalid rotation degree\n"); return; } layer.buf.crop_en = 0; layer.buf.format = AICFB_FORMAT; layer.buf.flags = 0; de->de_funcs->update_layer_config(&layer); } static void aicfb_puts_panel_info(struct aicfb_info *fbi) { const char *com_type[] = { "DE", "RGB", "LVDS", "DSI", "DBI" }; struct display_timing *timing; u32 vtotal, htotal; timing = fbi->desc ? fbi->desc->timings : fbi->panel->timings; vtotal = timing->vactive + timing->vback_porch + timing->vfront_porch + timing->vsync_len; htotal = timing->hactive + timing->hback_porch + timing->hfront_porch + timing->hsync_len; printf("%s type: %s pclk: %d Mhz h: %d v: %d fps: %d\n", fbi->panel->name, com_type[fbi->panel->connector_type], timing->pixelclock / 1000000, timing->hactive, timing->vactive, timing->pixelclock / (vtotal * htotal)); } void aicfb_enable_panel(struct aicfb_info *fbi, u32 on) { struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; struct aic_panel_funcs *funcs; funcs = desc ? desc->funcs : panel->funcs; if (on == AICFB_ON) { funcs->prepare(); funcs->enable(panel); aicfb_puts_panel_info(fbi); } else { funcs->disable(panel); funcs->unprepare(); } } void aicfb_get_panel_info(struct aicfb_info *fbi) { struct platform_driver *de = fbi->de; struct platform_driver *di = fbi->di; struct aic_panel *panel = fbi->panel; u32 bpp = 24, dither_en = 1; struct panel_desc *desc = NULL; unsigned int id; if (panel->desc && panel->match_num) { id = panel->match_id; desc = &panel->desc[id]; fbi->desc = desc; } if(di->di_funcs->attach_panel) di->di_funcs->attach_panel(panel, desc); if(di->di_funcs->get_output_bpp) bpp = di->di_funcs->get_output_bpp(); #ifdef AIC_DISABLE_DITHER dither_en = 0; #endif if(de->de_funcs->set_mode) de->de_funcs->set_mode(panel, desc, bpp, dither_en); } static void aicfb_get_hv_active(struct aicfb_info *fbi, u32 *active_w, u32 *active_h) { #ifdef AIC_SCREEN_CROP *active_w = AIC_SCREEN_CROP_WIDTH; *active_h = AIC_SCREEN_CROP_HEIGHT; #else struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; if (desc) { *active_w = desc->timings->hactive; *active_h = desc->timings->vactive; } else { *active_w = panel->timings->hactive; *active_h = panel->timings->vactive; } #endif } static inline int aicfb_format_bpp(enum mpp_pixel_format format) { switch (format) { case MPP_FMT_ARGB_8888: case MPP_FMT_ABGR_8888: case MPP_FMT_RGBA_8888: case MPP_FMT_BGRA_8888: case MPP_FMT_XRGB_8888: case MPP_FMT_XBGR_8888: case MPP_FMT_RGBX_8888: case MPP_FMT_BGRX_8888: return 32; case MPP_FMT_RGB_888: case MPP_FMT_BGR_888: return 24; case MPP_FMT_ARGB_1555: case MPP_FMT_ABGR_1555: case MPP_FMT_RGBA_5551: case MPP_FMT_BGRA_5551: case MPP_FMT_RGB_565: case MPP_FMT_BGR_565: case MPP_FMT_ARGB_4444: case MPP_FMT_ABGR_4444: case MPP_FMT_RGBA_4444: case MPP_FMT_BGRA_4444: return 16; default: break; } return 32; } void aicfb_fb_info_setup(struct aicfb_info *fbi) { u32 stride, bpp; u32 active_w; u32 active_h; size_t fb_size; bpp = aicfb_format_bpp(AICFB_FORMAT); #ifdef AIC_FB_ROTATE_EN fbi->fb_rotate = AIC_FB_ROTATE_DEGREE; #else fbi->fb_rotate = 0; #endif aicfb_get_hv_active(fbi, &active_w, &active_h); if (fbi->fb_rotate == 90 || fbi->fb_rotate == 270) { u32 tmp = active_w; active_w = active_h; active_h = tmp; } stride = ALIGN_8B(active_w * bpp / 8); fb_size = active_h * stride; fbi->width = active_w; fbi->height = active_h; fbi->bits_per_pixel = bpp; fbi->stride = stride; fbi->fb_size = fb_size; } void aicfb_register_panel_callback(struct aicfb_info *fbi) { struct platform_driver *de = fbi->de; struct platform_driver *di = fbi->di; struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; struct aic_panel_callbacks cb; cb.di_enable = di->di_funcs->enable; cb.di_disable = di->di_funcs->disable; cb.di_send_cmd = di->di_funcs->send_cmd; cb.di_set_videomode = di->di_funcs->set_videomode; cb.timing_enable = de->de_funcs->timing_enable; cb.timing_disable = de->de_funcs->timing_disable; if (desc) desc->funcs->register_callback(panel, &cb); else panel->funcs->register_callback(panel, &cb); } void fb_color_block(struct aicfb_info *fbi, size_t fb_size) { #ifdef AIC_DISP_COLOR_BLOCK #ifdef AIC_FB_ROTATE_EN if (!fb_size) return; memset(fbi->fb_start, 0x0, fb_size); aicos_dcache_clean_range((unsigned long *)fbi->fb_start, fb_size); #else u32 width, height; u32 i, j, index; unsigned char color[5][3] = { { 0x00, 0x00, 0xFF }, { 0x00, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0x00, 0x00, 0x00 }, { 0xFF, 0xFF, 0xFF }, }; unsigned char *pos = (unsigned char *)fbi->fb_start; (void)fb_size; width = fbi->width; height = fbi->height; switch (fbi->bits_per_pixel) { #if defined(AICFB_ARGB8888) || defined(AICFB_ABGR8888) || defined(AICFB_XRGB8888) case 32: { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { index = (i / 100 + j / 100) % 5; *(pos++) = color[index][0]; *(pos++) = color[index][1]; *(pos++) = color[index][2]; *(pos++) = 0; } } break; } #endif #if defined(AICFB_RGB888) case 24: { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { index = (i / 100 + j / 100) % 5; *(pos++) = color[index][0]; *(pos++) = color[index][1]; *(pos++) = color[index][2]; } } break; } #endif #if defined(AICFB_RGB565) case 16: { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { index = (i / 100 + j / 100) % 5; *(pos++) = (color[index][0] >> 3) | ((color[index][1] & 0x1c) << 3); *(pos++) = ((color[index][1] & 0xe0) >> 5) | (color[index][2] & 0xf8); } } break; } #endif default: *pos = color[0][0]; index = AICFB_FORMAT; i = width; j = height; pr_info("format(%d) do not support %dx%d color block.\n", index, i, j); return; } aicos_dcache_clean_range((unsigned long *)fbi->fb_start, fbi->fb_size); #endif /* AIC_FB_ROTATE_EN */ #endif /* AIC_DISP_COLOR_BLOCK */ #ifdef AIC_DISP_BLACK_SCREEN if (!fb_size) return; memset(fbi->fb_start, 0x0, fb_size); aicos_dcache_clean_range((unsigned long *)fbi->fb_start, fb_size); #endif } #ifdef AIC_DISPLAY_TEST static void aicfb_reset(struct aicfb_info *fbi) { struct aicfb_layer_data layer = {0}; struct platform_driver *de = fbi->de; struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; aicfb_get_panel_info(fbi); aicfb_fb_info_setup(fbi); 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); aic_delay_ms(20); aicfb_enable_clk(fbi, AICFB_ON); layer.enable = 1; layer.rect_id = 0; layer.buf.size.width = desc ? desc->timings->hactive : panel->timings->hactive; layer.buf.size.height = desc ? desc->timings->vactive : panel->timings->vactive; layer.buf.stride[0] = fbi->stride; de->de_funcs->update_layer_config(&layer); aicfb_enable_panel(fbi, AICFB_ON); } static int handle_dbi_data(struct aic_panel *panel, struct panel_desc *desc, struct panel_dbi *dbi, u32 *cmd_offset) { struct panel_dbi_commands *target = desc ? &desc->dbi->commands : &panel->dbi->commands; if (!target->buf) { target->buf = aicos_malloc(MEM_CMA, target->len); if (!target->buf) { pr_err("Malloc dbi buf failed!\n"); return -1; } } const u32 total = dbi->commands.len + *cmd_offset; if (total < target->len) { memcpy((u8*)target->buf + *cmd_offset, dbi->commands.buf, dbi->commands.len); *cmd_offset += dbi->commands.len; } else if (total == target->len) { memcpy((u8*)target->buf + *cmd_offset, dbi->commands.buf, dbi->commands.len); *cmd_offset = 0; } else { pr_err("Commands len is too long!\n"); *cmd_offset = 0; return -1; } return 0; } static int handle_dsi_data(struct aic_panel *panel, struct panel_desc *desc, struct panel_dsi *dsi, u32 *cmd_offset) { struct dsi_command *target = desc ? &desc->dsi->command : &panel->dsi->command; if (!target->buf) { target->buf = aicos_malloc(MEM_CMA, 4096); if (!target->buf) { pr_err("Malloc dsi buf failed!\n"); return -1; } } if ((*cmd_offset + dsi->command.len) > 4096) { pr_err("Command buffer overflow!\n"); return -1; } memcpy(target->buf + *cmd_offset, dsi->command.buf, dsi->command.len); *cmd_offset += dsi->command.len; target->len = *cmd_offset; target->command_on = 1; return 0; } static int handle_spi_rgb_data(struct aic_panel *panel, struct panel_desc *desc, struct panel_rgb *rgb, u32 *cmd_offset) { struct rgb_spi_command *target = desc ? &desc->rgb->spi_command : &panel->rgb->spi_command; if (!target->buf) { target->buf = aicos_malloc(MEM_CMA, 4096); if(!target->buf) { pr_err("Malloc rgb spi command buf failed\n"); return -1; } } if ((*cmd_offset + rgb->spi_command.len) > 4096) { pr_err("Command buffer overflow!\n"); return -1; } memcpy(target->buf + *cmd_offset, rgb->spi_command.buf, rgb->spi_command.len); *cmd_offset += rgb->spi_command.len; target->len = *cmd_offset; target->command_on = 1; return 0; } static bool handle_rgb_config(struct aicfb_info *fbi, struct aicfb_pq_config *config) { struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; struct panel_rgb *rgb = config->data; struct panel_rgb *target_rgb = desc ? desc->rgb : panel->rgb; static u32 rgb_spi_command_offset = 0; #define RGB_SPI_COMMAND_UPDATE 0 #define RGB_SPI_COMMAND_DISABLE 1 #define RGB_SPI_COMMAND_CLEAR 2 #define RGB_SPI_COMMAND_SEND 3 switch (rgb->spi_command.command_on) { case RGB_SPI_COMMAND_UPDATE: { if (handle_spi_rgb_data(panel, desc, rgb, &rgb_spi_command_offset) < 0) { pr_err("handld spi-rgb command error!\n"); } return true; } case RGB_SPI_COMMAND_DISABLE: { target_rgb->spi_command.command_on = 0; return true; } case RGB_SPI_COMMAND_CLEAR: { target_rgb->spi_command.command_on = 2; rgb_spi_command_offset = 0; return true; } default: { target_rgb->mode = rgb->mode; target_rgb->format = rgb->format; target_rgb->data_order = rgb->data_order; target_rgb->data_mirror = rgb->data_mirror; target_rgb->clock_phase = rgb->clock_phase; return false; } } } static bool handle_mipi_config(struct aicfb_info *fbi, struct aicfb_pq_config *config) { struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; struct panel_dsi *dsi = config->data; struct panel_dsi *target_dsi = desc ? desc->dsi : panel->dsi; static u32 dsi_commands_offset = 0; #define MIPI_DSI_DISABLE_COMMAND 0 #define MIPI_DSI_ADB_UPDATE_COMMAND 1 #define MIPI_DSI_SEND_COMMAND 2 #define MIPI_DSI_UART_UPDATE_COMMAND 3 #define MIPI_DSI_UART_COPY_COMMAND 4 switch (dsi->command.command_on) { case MIPI_DSI_ADB_UPDATE_COMMAND: { if (!target_dsi->command.buf) target_dsi->command.buf = aicos_malloc(MEM_CMA, 4096); target_dsi->command.command_on = 1; target_dsi->command.len = dsi->command.len; memcpy(target_dsi->command.buf, dsi->command.buf, dsi->command.len); return true; } case MIPI_DSI_UART_COPY_COMMAND: { if (handle_dsi_data(panel, desc, dsi, &dsi_commands_offset) < 0) { pr_err("handle dsi command error!\n"); } memcpy(target_dsi->command.buf + dsi_commands_offset, dsi->command.buf, dsi->command.len); target_dsi->command.len = dsi_commands_offset; target_dsi->command.command_on = 1; return true; } case MIPI_DSI_UART_UPDATE_COMMAND: { target_dsi->command.command_on = 2; dsi_commands_offset = 0; return true; } case MIPI_DSI_DISABLE_COMMAND: { target_dsi->command.command_on = 0; return true; } default: { target_dsi->mode = dsi->mode; target_dsi->format = dsi->format; target_dsi->lane_num = dsi->lane_num; target_dsi->dc_inv = dsi->dc_inv; target_dsi->vc_num = dsi->vc_num; target_dsi->ln_polrs = dsi->ln_polrs; target_dsi->ln_assign = dsi->ln_assign; return false; } } } void aicfb_pq_set_config(struct aicfb_info *fbi, struct aicfb_pq_config *config) { struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; switch (panel->connector_type) { case AIC_RGB_COM: { if (handle_rgb_config(fbi, config)) return; break; } case AIC_LVDS_COM: { memcpy(desc ? desc->lvds : panel->lvds, config->data, sizeof(struct panel_lvds)); break; } case AIC_MIPI_COM: { if (handle_mipi_config(fbi, config)) return; break; } case AIC_DBI_COM: { static u32 dbi_commands_offset = 0; struct panel_dbi *dbi = config->data; struct panel_dbi *target_dbi = desc ? desc->dbi : panel->dbi; #define MIPI_DBI_UPDATE_COMMAND 0 #define MIPI_DBI_SEND_COMMAND 1 if (dbi->commands.command_flag == MIPI_DBI_UPDATE_COMMAND) { if (handle_dbi_data(panel, desc, dbi, &dbi_commands_offset) < 0) { pr_err("handle dbi command error!\n"); return; } return; } else { target_dbi->type = dbi->type; target_dbi->format = dbi->format; target_dbi->first_line = dbi->first_line; target_dbi->other_line = dbi->other_line; if (target_dbi->spi != NULL) { target_dbi->spi->qspi_mode = dbi->spi->qspi_mode; target_dbi->spi->vbp_num = dbi->spi->vbp_num; target_dbi->spi->code1_cfg = dbi->spi->code1_cfg; target_dbi->spi->code[0] = dbi->spi->code[0]; target_dbi->spi->code[1] = dbi->spi->code[1]; target_dbi->spi->code[2] = dbi->spi->code[2]; } } break; } default: break; } if (desc) memcpy(desc->timings, config->timing, sizeof(struct display_timing)); else memcpy(panel->timings, config->timing, sizeof(struct display_timing)); aicfb_reset(fbi); } void aicfb_pq_get_config(struct aicfb_info *fbi, struct aicfb_pq_config *config) { struct aic_panel *panel = fbi->panel; struct panel_desc *desc = fbi->desc; if (desc) { memcpy(config->timing, desc->timings, sizeof(struct display_timing)); } else { memcpy(config->timing, panel->timings, sizeof(struct display_timing)); } if (config->connector_type != panel->connector_type) return; switch (panel->connector_type) { case AIC_RGB_COM: { if (desc) { memcpy(config->data, desc->rgb, sizeof(struct panel_rgb)); } else { memcpy(config->data, panel->rgb, sizeof(struct panel_rgb)); } break; } case AIC_LVDS_COM: { if (desc) { memcpy(config->data, desc->lvds, sizeof(struct panel_lvds)); } else { memcpy(config->data, panel->lvds, sizeof(struct panel_lvds)); } break; } case AIC_MIPI_COM: { struct panel_dsi *dsi = config->data; struct panel_dsi *src_dsi = desc ? desc->dsi : panel->dsi; dsi->mode = src_dsi->mode; dsi->format = src_dsi->format; dsi->lane_num = src_dsi->lane_num; dsi->dc_inv = src_dsi->dc_inv; dsi->vc_num = src_dsi->vc_num; dsi->ln_polrs = src_dsi->ln_polrs; dsi->ln_assign = src_dsi->ln_assign; break; } case AIC_DBI_COM: { struct panel_dbi *dbi = config->data; struct panel_dbi *src_dbi = desc ? desc->dbi : panel->dbi; dbi->type = src_dbi->type; dbi->format = src_dbi->format; if (src_dbi->first_line) { dbi->first_line = src_dbi->first_line; } else { dbi->first_line = 0x2C; } if (src_dbi->other_line) { dbi->other_line = src_dbi->other_line; } else { dbi->other_line = 0x3C; } if (src_dbi->spi != NULL) { dbi->spi->qspi_mode = src_dbi->spi->qspi_mode; dbi->spi->vbp_num = src_dbi->spi->vbp_num; dbi->spi->code1_cfg = src_dbi->spi->code1_cfg; dbi->spi->code[0] = src_dbi->spi->code[0]; dbi->spi->code[1] = src_dbi->spi->code[1]; dbi->spi->code[2] = src_dbi->spi->code[2]; } break; } default: pr_err("AIPQ get config failed!\n"); break; } } #endif /* AIC_DISPLAY_TEST */