// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020-2023 ArtInChip Technology Co., Ltd. * Authors: Matteo * * The device mode of MPP video in */ #include "aic_common.h" #include "aic_errno.h" #include "aic_log.h" #include "aic_osal.h" #include "mpp_vin.h" #include "mpp_vin_dev.h" #ifdef CONFIG_ARTINCHIP_MDI #include "hal_mdi.h" #include "mdi.h" #include "artinchip_fb.h" #include "display_support.h" #ifdef CONFIG_ARTINCHIP_GE #include "mpp_ge.h" #endif #define VIN_DEBUG_SHOW_FRAMERATE #define VIN_DEBUG_DISP_ENABLE struct mpp_vin_dev { struct mpp_size in_size; u32 in_stride; enum mpp_pixel_format out_fmt; /* only suport RGB565 & RGB888 */ u32 frame_cnt; struct vin_video_buf vb; u32 ge_enable; u32 scale_enable; #ifdef CONFIG_ARTINCHIP_GE u32 rot_angle; u32 ge_out_stride; struct mpp_size ge_out_size; struct mpp_ge *ge_dev; struct ge_bitblt ge_blt; dma_addr_t fb_buf[2]; #endif s32 de_inited; struct aicfb_layer_data de_layer; struct aic_panel *de_panel; aicos_mutex_t lock; aicos_thread_t thid; }; static struct mpp_vin_dev g_vin_dev = {0}; static char *g_vin_buf = NULL; /* Must 8Byte align */ static char *g_vin_buf_raw = NULL; /* Process the unaligned buf address */ static void mpp_mdi_update_out_cfg(void) { struct mpp_buf *de = &g_vin_dev.de_layer.buf; #ifdef CONFIG_ARTINCHIP_GE struct ge_bitblt *ge_blt = &g_vin_dev.ge_blt; #endif /* Case 1: MDI -> DE, so out_cfg is decided by DE */ if (!g_vin_dev.ge_enable) return; /* Case 2: MDI -> GE -> DE */ #ifdef CONFIG_ARTINCHIP_GE /* Case 2.1 Need scale. out_cfg is decided by DE */ if (g_vin_dev.scale_enable) { g_vin_dev.ge_out_size = de->size; g_vin_dev.ge_out_stride = de->stride[0]; } /* Case 2.2 No scale, no rotate or rotate 180. out_cfg is same as input */ else if (!g_vin_dev.rot_angle || g_vin_dev.rot_angle == MPP_ROTATION_180) { g_vin_dev.ge_out_size = g_vin_dev.in_size; g_vin_dev.ge_out_stride = g_vin_dev.in_stride; } /* Case 2.3 No scale, rotate 90/270. out_cfg swap the width and height */ else { g_vin_dev.ge_out_size.width = g_vin_dev.in_size.height; g_vin_dev.ge_out_size.height = g_vin_dev.in_size.width; if (de->format == MPP_FMT_RGB_565) g_vin_dev.ge_out_stride = ALIGN_8B(g_vin_dev.in_size.height * 2); else g_vin_dev.ge_out_stride = ALIGN_8B(g_vin_dev.in_size.height * 3); } ge_blt->src_buf.size = g_vin_dev.in_size; ge_blt->src_buf.stride[0] = g_vin_dev.in_stride; ge_blt->src_buf.format = de->format; ge_blt->dst_buf.crop_en = 1; ge_blt->dst_buf.crop.width = g_vin_dev.ge_out_size.width; ge_blt->dst_buf.crop.height = g_vin_dev.ge_out_size.height; ge_blt->dst_buf.stride[0] = de->stride[0]; ge_blt->dst_buf.format = de->format; printf("GE: %d(%d) * %d -> %d * %d, canvas %d(%d) * %d\n", ge_blt->src_buf.size.width, ge_blt->src_buf.stride[0], ge_blt->src_buf.size.height, ge_blt->dst_buf.crop.width, ge_blt->dst_buf.crop.height, ge_blt->dst_buf.size.width, ge_blt->dst_buf.stride[0], ge_blt->dst_buf.size.height); #endif } static int mpp_mdi_request_buf(void) { int i, imagesize = 0; char *video_buf = g_vin_buf; struct vin_video_buf *vb = &g_vin_dev.vb; struct mpp_buf *de = &g_vin_dev.de_layer.buf; if (g_vin_dev.ge_enable) { imagesize = de->size.height * de->stride[0]; g_vin_dev.fb_buf[0] = (dma_addr_t)g_vin_buf; g_vin_dev.fb_buf[1] = (dma_addr_t)(g_vin_buf + imagesize); video_buf = g_vin_buf + imagesize * 2; printf("\nAllocated double framebuffer for DE: %#lx, %#lx\n", (long)g_vin_dev.fb_buf[0], (long)g_vin_dev.fb_buf[1]); } if (aic_mdi_req_buf(video_buf, VIN_RESERVED_MEM_SIZE - imagesize * 2, vb)) return -1; printf("\nAllocated %d video buffer for MDI:\n", vb->num_buffers); printf("Buf Plane[0] size Plane[1] size\n"); for (i = 0; i < vb->num_buffers; i++) { printf("%3d %#x %8d %#x %8d\n", i, vb->planes[i * VIN_MAX_PLANE_NUM].buf, vb->planes[i * VIN_MAX_PLANE_NUM].len, vb->planes[i * VIN_MAX_PLANE_NUM + 1].buf, vb->planes[i * VIN_MAX_PLANE_NUM + 1].len); } /* Queue all the buffer, prepare to accept DBI data */ for (i = 0; i < vb->num_buffers; i++) { if (aic_mdi_q_buf(i) < 0) return -1; } return 0; } static void mpp_mdi_release_buf(void) { } static dma_addr_t mpp_mdi_get_tail_buf(void) { if (g_vin_dev.ge_enable) return g_vin_dev.fb_buf[1]; else return g_vin_dev.vb.planes[(g_vin_dev.vb.num_buffers - 1) * VIN_MAX_PLANE_NUM].buf; } static int dbi_cmd_soft_reset(u8 code, u8 *data) { aic_mdi_reset(); return 0; } static int dbi_cmd_disp_on(u8 code, u8 *data) { if (!g_vin_dev.de_inited) { #ifdef VIN_DEBUG_DISP_ENABLE struct aicfb_layer_data *layer = &g_vin_dev.de_layer; #endif /* 1. Prepare the video buffer & framebuffer */ if (mpp_mdi_request_buf()) return -1; /* 2.1 Open GE */ if (g_vin_dev.ge_enable) { #ifdef CONFIG_ARTINCHIP_GE g_vin_dev.ge_dev = mpp_ge_open(); if (!g_vin_dev.ge_dev) return -1; #endif } #ifdef VIN_DEBUG_DISP_ENABLE /* 2.2 Open DE */ display_panel_probe(); display_interface_probe(); display_engine_probe(0, 0); layer->enable = 1; /* Set a temporary buffer to DE, and the buffer should be valid */ layer->buf.phy_addr[0] = mpp_mdi_get_tail_buf(); display_ioctl(AICFB_UPDATE_LAYER_CONFIG, layer); #endif g_vin_dev.de_inited = 1; } #ifdef VIN_DEBUG_DISP_ENABLE /* 3. Open panel */ display_ioctl(AICFB_ENABLE_PANEL, NULL); #endif /* 4. Start the video buffer */ aic_mdi_stream_on(); /* 5. Start the MDI thread */ aicos_thread_resume(g_vin_dev.thid); pr_info("Turn on DE\n"); return 0; } static int dbi_cmd_disp_off(u8 code, u8 *data) { aicos_mutex_take(g_vin_dev.lock, AIC_OSAL_WAIT_FOREVER); if (!g_vin_dev.de_inited) { pr_err("Should init DE first.\n"); return -1; } g_vin_dev.de_inited = 0; g_vin_dev.de_layer.enable = 0; #ifdef VIN_DEBUG_DISP_ENABLE display_ioctl(AICFB_UPDATE_LAYER_CONFIG, &g_vin_dev.de_layer); #endif aicos_mutex_give(g_vin_dev.lock); aic_mdi_stream_off(); aicos_thread_suspend(g_vin_dev.thid); if (g_vin_dev.ge_enable) { #ifdef CONFIG_ARTINCHIP_GE mpp_ge_close(g_vin_dev.ge_dev); g_vin_dev.ge_dev = NULL; #endif } pr_info("Turn off DE\n"); return 0; } static int dbi_cmd_width_set(u8 code, u8 *data) { u16 start = dbi_byte2short(data[0], data[1]); u16 end = dbi_byte2short(data[2], data[3]); u16 size = 0; if (start >= end) { pr_err("Invalid width: %d -> %d\n", start, end); return -1; } size = end - start + 1; printf("Recv width: %d (%d - %d)\n", size, start, end); if (size > MDI_MAX_WIDTH) { pr_warn("Recv width %d is too large!\n", size); size = MDI_MAX_WIDTH; } aic_mdi_in_width_set(size); g_vin_dev.in_size.width = size; return 0; } static int dbi_cmd_height_set(u8 code, u8 *data) { u16 start = dbi_byte2short(data[0], data[1]); u16 end = dbi_byte2short(data[2], data[3]); u16 size = 0; if (start >= end) { pr_err("Invalid height: %d -> %d\n", start, end); return -1; } size = end - start + 1; printf("Recv height: %d (%d - %d)\n", size, start, end); if (size > MDI_MAX_HEIGHT) { pr_warn("Recv height %d is too large!\n", size); size = MDI_MAX_HEIGHT; } aic_mdi_in_height_set(size); g_vin_dev.in_size.height = size; return 0; } static int dbi_cmd_pixel_fmt_set(u8 code, u8 *data) { u8 dpi = getbits(DBI_CMD_DPI_MASK, DBI_CMD_DPI_SHIFT, data[0]); u8 dbi = getbits(DBI_CMD_DBI_MASK, DBI_CMD_DBI_SHIFT, data[0]); u8 flag = getbit(DBI_CMD_DBI_FMT_FLAG, data[0]); struct mpp_buf *de = &g_vin_dev.de_layer.buf; char *bits[] = {"", "", "", "", "", "16bits", "18bits", "24bits"}; u32 imagesize = 0; if ((dpi != DPI_RGB_IF_16BIT) && (dpi != DPI_RGB_IF_24BIT)) { pr_err("Unsupported DPI format %d\n", dpi); return -1; } if (dbi < DBI_MCU_IF_16BIT) { pr_err("Unsupported DBI format %d\n", dbi); return -1; } aicos_mutex_take(g_vin_dev.lock, AIC_OSAL_WAIT_FOREVER); if (dbi == DBI_MCU_IF_16BIT) g_vin_dev.in_stride = ALIGN_8B(g_vin_dev.in_size.width * 2); else g_vin_dev.in_stride = ALIGN_8B(g_vin_dev.in_size.width * 3); if (dpi == DPI_RGB_IF_16BIT) { de->format = MPP_FMT_RGB_565; de->stride[0] = ALIGN_8B(de->size.width * 2); imagesize = de->stride[0] * de->size.height; } else { de->format = MPP_FMT_RGB_888; de->stride[0] = ALIGN_8B(de->size.width * 3); imagesize = de->stride[0] * de->size.height; } g_vin_dev.out_fmt = de->format; mpp_mdi_update_out_cfg(); aicos_mutex_give(g_vin_dev.lock); printf("Recv DBI %s(%d) -> DPI %s(%d), Flag %d\n", bits[dbi], dbi, bits[dpi], dpi, flag); aic_mdi_in_fmt_set(dbi, flag); /* Consider the MDI output is DE temporary. * The output should changed to GE after CMD DBI_CMD_AIC_GE_CTL */ aic_mdi_out_pixel_set(de->stride[0], imagesize); return 0; } static int dbi_cmd_bright_set(u8 code, u8 *data) { pr_info("Unsupported cmd %#x\n", code); return 0; } static int dbi_cmd_ge_ctl(u8 code, u8 *data) { u8 rotate = data[0] & DBI_CMD_GE_ROT_MASK; u32 *flag = &g_vin_dev.ge_blt.ctrl.flags; *flag = 0; if (data[0] & DBI_CMD_GE_H_FLIP) *flag |= MPP_FLIP_H; if (data[0] & DBI_CMD_GE_V_FLIP) *flag |= MPP_FLIP_V; if (data[0] & DBI_CMD_GE_SCALE) g_vin_dev.scale_enable = 1; *flag |= rotate; g_vin_dev.rot_angle = rotate; g_vin_dev.ge_enable = 1; printf("Recv GE ctl: H flip %s, V flip %s, Scale %s, Rotate %d\n", *flag & MPP_FLIP_H ? "enable" : "disable", *flag & MPP_FLIP_V ? "enable" : "disable", g_vin_dev.scale_enable ? "enable" : "disable", rotate * 90); mpp_mdi_update_out_cfg(); aic_mdi_out_pixel_set(g_vin_dev.in_stride, g_vin_dev.in_stride * g_vin_dev.in_size.height); return 0; } static int dbi_cmd_fr_set(u8 code, u8 *data) { u8 fr = getbits(DBI_CMD_FR_MASK, DBI_CMD_FR_SHIFT, data[0]); u32 pixclk = fr; struct display_timing *t = g_vin_dev.de_panel->timing; aicos_mutex_take(g_vin_dev.lock, AIC_OSAL_WAIT_FOREVER); pixclk *= (t->hactive + t->hfront_porch + t->hback_porch + t->hsync_len) * (t->vactive + t->vfront_porch + t->vback_porch + t->vsync_len); if (pixclk > MDI_MAX_PIXCLK) { pr_info("pixclk %d is too large\n", pixclk); pixclk = MDI_MAX_PIXCLK; } t->pixelclock = pixclk; aicos_mutex_give(g_vin_dev.lock); printf("Recv framerate %d, pixclk should be %d\n", fr, pixclk); return 0; } static int dbi_cmd_porch_set(u8 code, u8 *data) { struct display_timing *t = g_vin_dev.de_panel->timing; aicos_mutex_take(g_vin_dev.lock, AIC_OSAL_WAIT_FOREVER); t->vfront_porch = data[0] & DBI_CMD_VP_MASK; t->vback_porch = data[1] & DBI_CMD_VP_MASK; t->hfront_porch = data[2]; t->hback_porch = data[3]; t->hsync_len = 2; t->vsync_len = 2; aicos_mutex_give(g_vin_dev.lock); printf("Recv blank porch:\n\t hfp %d, hbp %d, vfp %d, vbp %d\n", t->hfront_porch, t->hback_porch, t->vfront_porch, t->vback_porch); return 0; } static int dbi_cmd_unsupported(u8 code, u8 *data) { pr_info("Unsupported cmd %#x\n", code); return 0; } static struct aic_dbi_cmd g_dbi_cmds[] = { {DBI_CMD_SOFT_RESET, 0, "Soft reset", dbi_cmd_soft_reset}, {DBI_CMD_SLEEP_OUT, 0, "Sleep out", dbi_cmd_unsupported}, {DBI_CMD_DISP_OFF, 0, "Display off", dbi_cmd_disp_off}, {DBI_CMD_DISP_ON, 0, "Display on", dbi_cmd_disp_on}, {DBI_CMD_COL_ADDR_SET, 4, "Column addr set", dbi_cmd_width_set}, {DBI_CMD_PAGE_ADDR_SET, 4, "Page addr set", dbi_cmd_height_set}, {DBI_CMD_PIXEL_FMT, 1, "Pixel format", dbi_cmd_pixel_fmt_set}, {DBI_CMD_BRIGHTNESS, 1, "Brightness set", dbi_cmd_bright_set}, {DBI_CMD_AIC_GE_CTL, 1, "AIC GE control", dbi_cmd_ge_ctl}, {DBI_CMD_FR_CTL, 2, "Frame rate", dbi_cmd_fr_set}, {DBI_CMD_BLANK_PORCH_CTL, 4, "Blank porch", dbi_cmd_porch_set}, {0x33, 6, "ili94 Ver Scr", dbi_cmd_unsupported}, {0x35, 1, "ili94 Tear ON", dbi_cmd_unsupported}, {0x36, 1, "ili94 Mem Acc", dbi_cmd_unsupported}, {0x37, 2, "ili94 Ver Addr", dbi_cmd_unsupported}, {0x44, 2, "ili94 Wr Tear", dbi_cmd_unsupported}, {0x50, 0, "ili94 cmd", dbi_cmd_unsupported}, {0xc0, 2, "ili94 Pwr ctl1", dbi_cmd_unsupported}, {0xc1, 1, "ili94 Pwr ctl2", dbi_cmd_unsupported}, {0xc5, 3, "ili94 VCOM ctl", dbi_cmd_unsupported}, {0xb4, 1, "ili94 Disp Inv", dbi_cmd_unsupported}, {0xb6, 2, "ili94 Disp Func", dbi_cmd_unsupported}, // data len is 3? {0xb7, 1, "ili94 Entry Mod", dbi_cmd_unsupported}, {0xbe, 2, "ili94 HS Lane", dbi_cmd_unsupported}, {0xe0, 15, "ili94 Pos GAM", dbi_cmd_unsupported}, {0xe1, 15, "ili94 Neg GAM", dbi_cmd_unsupported}, {0xe9, 1, "ili94 Set Img", dbi_cmd_unsupported}, {0xec, 4, "ili94 cmd", dbi_cmd_unsupported}, {0xf4, 3, "ili94 cmd", dbi_cmd_unsupported}, {0xf7, 4, "ili94 Adj ctl", dbi_cmd_unsupported}, {0xe7, 1, "ST77903 cmd", dbi_cmd_unsupported}, {0xf0, 1, "ST77903 cmd", dbi_cmd_unsupported}, }; static s32 dbi_cmd_process(u8 code) { u8 i, ret; u8 data[DBI_DAT_MAX_LEN] = {0}; struct aic_dbi_cmd *cmd = g_dbi_cmds; for (i = 0; i < ARRAY_SIZE(g_dbi_cmds); i++, cmd++) { if (code != cmd->code) continue; pr_debug("Recv cmd %#x (%s)\n", cmd->code, cmd->name); if (!cmd->data_len) return cmd->proc(code, NULL); ret = aich_mdi_rd_fifo(data, cmd->data_len); if (!ret || ret != cmd->data_len) { pr_warn("CMD %#x (%s): Failed to read the data %d/%d\n", cmd->code, cmd->name, ret, cmd->data_len); return -1; } return cmd->proc(code, data); } pr_warn("unknown command %#x\n", code); aich_mdi_rd_fifo(data, DBI_DAT_MAX_LEN); return -1; } #ifdef CONFIG_ARTINCHIP_GE static int mpp_mdi_preprocess(s32 index, s32 pingpang) { s32 ret = 0; struct ge_bitblt *ge_blt = &g_vin_dev.ge_blt; ge_blt->src_buf.phy_addr[0] = g_vin_dev.vb.planes[index * VIN_MAX_PLANE_NUM].buf; ge_blt->dst_buf.phy_addr[0] = g_vin_dev.fb_buf[pingpang]; ret = mpp_ge_bitblt(g_vin_dev.ge_dev, ge_blt); if (ret < 0) { pr_err("GE bitblt failed!\n"); return ret; } ret = mpp_ge_emit(g_vin_dev.ge_dev); if (ret < 0) { pr_err("GE emit failed!\n"); return ret; } ret = mpp_ge_sync(g_vin_dev.ge_dev); if (ret < 0) { pr_err("GE sync failed!\n"); return ret; } return 0; } #endif #ifdef VIN_DEBUG_SHOW_FRAMERATE #define NS_PER_SEC 1000000000 static void show_fps(struct timespec *start, struct timespec *end, int cnt) { double diff; if (end->tv_nsec < start->tv_nsec) { diff = (double)(NS_PER_SEC + end->tv_nsec - start->tv_nsec)/NS_PER_SEC; diff += end->tv_sec - 1 - start->tv_sec; } else { diff = (double)(end->tv_nsec - start->tv_nsec)/NS_PER_SEC; diff += end->tv_sec - start->tv_sec; } printf("MDI frame rate: %.1f (%d / %.1fs)\n", (double)cnt/diff, cnt, diff); } #endif static void mpp_mdi_thread(void *arg) { u32 cnt = 0, index = 0; struct vin_video_buf *vb = &g_vin_dev.vb; struct aicfb_layer_data *layer = &g_vin_dev.de_layer; #ifdef CONFIG_ARTINCHIP_GE s32 pingpang = 1; #endif #ifdef VIN_DEBUG_SHOW_FRAMERATE struct timespec begin, now; clock_gettime(CLOCK_MONOTONIC, &begin); #endif while (1) { if (!g_vin_dev.de_inited) { aos_msleep(10); continue; } cnt++; if ((g_vin_dev.frame_cnt) && (cnt >= g_vin_dev.frame_cnt)) break; if (aic_mdi_dq_buf(&index) < 0) break; if (g_vin_dev.ge_enable) { #ifdef CONFIG_ARTINCHIP_GE pingpang = !pingpang; if (mpp_mdi_preprocess(index, pingpang)) break; #endif } aicos_mutex_take(g_vin_dev.lock, AIC_OSAL_WAIT_FOREVER); layer->enable = 1; if (g_vin_dev.ge_enable) layer->buf.phy_addr[0] = g_vin_dev.fb_buf[pingpang]; else layer->buf.phy_addr[0] = vb->planes[index * VIN_MAX_PLANE_NUM].buf; #ifdef VIN_DEBUG_DISP_ENABLE display_ioctl(AICFB_UPDATE_LAYER_CONFIG, &g_vin_dev.de_layer); display_ioctl(AICFB_WAIT_FOR_VSYNC, NULL); #endif aicos_mutex_give(g_vin_dev.lock); aic_mdi_q_buf(index); #ifdef VIN_DEBUG_SHOW_FRAMERATE if (cnt && (cnt % 1000 == 0)) { clock_gettime(CLOCK_MONOTONIC, &now); show_fps(&begin, &now, cnt); clock_gettime(CLOCK_MONOTONIC, &begin); cnt = 0; } #endif } #ifdef VIN_DEBUG_SHOW_FRAMERATE clock_gettime(CLOCK_MONOTONIC, &now); show_fps(&begin, &now, cnt); #endif mpp_mdi_release_buf(); mpp_vin_dev_deinit(); pr_info("MDI thread exit!\n"); } static int mpp_vin_malloc_align(void) { g_vin_buf_raw = (char *)aicos_malloc(MEM_CMA, VIN_RESERVED_MEM_SIZE + 8); if (!g_vin_buf_raw) { pr_err("Failed to malloc %d buffer\n", VIN_RESERVED_MEM_SIZE); return -1; } if ((long)g_vin_buf_raw % 8) g_vin_buf = (char *)((long)(g_vin_buf_raw + 7) & (~0x7)); else g_vin_buf = g_vin_buf_raw; memset(g_vin_buf, 0, VIN_RESERVED_MEM_SIZE); pr_debug("MPP VIN buffer base: %#lx\n", (long)g_vin_buf); return 0; } int mpp_vin_dev_init(u32 cnt) { struct mpp_buf *de = &g_vin_dev.de_layer.buf; #ifdef CONFIG_ARTINCHIP_GE struct ge_bitblt *ge_blt = &g_vin_dev.ge_blt; #endif if (aic_mdi_open(dbi_cmd_process)) return -1; if (g_vin_buf) { pr_info("MDI buffer is already alloced: %#lx\n", (long)g_vin_buf); return 0; } if (mpp_vin_malloc_align()) return -1; g_vin_dev.de_panel = aic_open_panel(); if (!g_vin_dev.de_panel) return -1; g_vin_dev.de_layer.layer_id = AICFB_LAYER_TYPE_UI; de->buf_type = MPP_PHY_ADDR; de->format = MPP_FMT_RGB_888; de->size.width = APP_FB_WIDTH; de->size.height = APP_FB_HEIGHT; de->stride[0] = ALIGN_8B(APP_FB_WIDTH * 3); #ifdef CONFIG_ARTINCHIP_GE ge_blt->src_buf.buf_type = MPP_PHY_ADDR; ge_blt->dst_buf.buf_type = MPP_PHY_ADDR; ge_blt->dst_buf.size = de->size; ge_blt->dst_buf.stride[0] = de->stride[0]; ge_blt->dst_buf.format = de->format; #endif g_vin_dev.frame_cnt = cnt; g_vin_dev.lock = aicos_mutex_create(); g_vin_dev.thid = aicos_thread_create("mpp_mdi", 4096, AOS_DEFAULT_APP_PRI, mpp_mdi_thread, NULL); if (g_vin_dev.thid == NULL) { pr_err("Failed to create MDI thread\n"); return -1; } aicos_thread_suspend(g_vin_dev.thid); return 0; } void mpp_vin_dev_deinit(void) { if (g_vin_buf) { aicos_free(MEM_CMA, g_vin_buf_raw); g_vin_buf = NULL; } aic_mdi_close(); } #endif