设计说明
源代码位于 bsp/artinchip/:
-
bsp/artinchip/drv/mdi/drv_mdi.c,MDI Driver 层实现
-
bsp/artinchip/hal/mdi/hal_mdi.c,MDI HAL 层实现
-
bsp/artinchip/include/hal/hal_mdi.h,MDI HAL 层接口头文件
模块架构
整个软件系统的架构图如下:

-
MDI 驱动需要和 ArtInChip 自制的 MPP 中间件配合使用,MDI Driver 层采用普通的 API 方式向上提供接口。
-
MPP VIN 模块对 APP 提供类似 ioctl 的接口封装,类似 Linux 中的 ioctl 接口定义。
-
MDI 需要用到多任务并发,暂不支持在裸机环境中运行。
关键流程设计
初始化流程
总体上看,MDI 驱动的初始化过程实现在 aic_mdi_open() 接口中,完成的操作有:
-
使能 MDI 时钟
-
打卡 MDI 中断
-
初始化 VideoBuf 链表
-
使能 DVP 控制器
Buffer 队列管理
Buffer 队列管理复用 MPP VIN Buffer 模块中的机制,通过三个链表来管理这些 Buffer。
整个 Buffer 流转的过程如下图:

整个 Buffer 流转的过程如下图:

在局部刷新场景中,为了节省带宽,通常采用以下流程:
-
先推一屏完整的数据。
-
后面帧都采用局部刷新方式,只推一个小图,位置信息由 2A、2B 命令提供。
-
将小图数据填充到空的 Buffer 中,并将这个 Buffer 提交给 GE 模块进行处理。
GE 做数据搬运和局部填充。
-
将处理后的 Buffer 提交给 DE 模块进行显示。

中断处理流程
MDI 的中断处理中需要处理 Buf 队列的流转和 Display Buffer Interface (DBI) 命令响应。
-
- HNUM Interrupt
-
用于判断当前 Register 是否可以修改。出现 HNUM Interrupt,表示当前图像帧已经完成 10 行(驱动中默认配置)数据的刷新,硬件已经读走了当前的 Register 值(影子寄存器),软件可以传入下一个 Buf 的参数了。
-
- Frame done
-
用于判断当前 buf 是否完成(done 状态)。出现 Frame done,表示当前帧的数据传输完成,此时该 buf 可以从 QBuf list 切换到 DQbuf list。
按照 MDI 硬件设计的逻辑,HNUM Interrupt 和 Frame done 会间隔产生,如下所示:
HNUM Interrupt -> Frame done-> HNUM Interrupt -> Frame
done-> HNUM Interrupt -> Frame done...

自定义 DBI 命令设计
Display Buffer Interface (DBI) 命令是用于控制显示设备(如液晶显示屏)的一组指令。这些命令通常由主控芯片 (MCU) 发送给显示控制器,以配置和控制显示参数。以下是一些常见的 DBI 命令及其功能:
0x3A:Interface Pixel Format
0x3A 命令是一个标准的 DBI 命令,用于设置显示接口的像素格式。

为了区分一些特殊格式,对 BIT3 新增了扩展定义。通过修改 BIT3,可以定义不同的颜色格式。
/* The flag is a extend bit of CMD 0x3A, which should be set by Host. * * Bus Mode MCU Interface Flag RGB sequence * ------------ ------------- ---- -------------- * SPI 16BIT x RGB565 * SPI 24BIT 0 RGB888 * SPI 24BIT 1 RGB888_2 * 8080/6080 x8 16BIT x X8 RGB565 * 8080/6080 x8 24BIT x X8 RGB888 * 8080/6080 x16 16BIT x X16 RGB565 * 8080/6080 x16 18BIT 0 X16 RGBX666 * 8080/6080 x16 18BIT 1 X16 XRGB666 * 8080/6080 x16 24BIT 0 X16 RGBX888 * 8080/6080 x16 24BIT 1 X16 RGBX */
0xAC:GE Control
#define DBI_CMD_GE_H_FLIP BIT(6) #define DBI_CMD_GE_V_FLIP BIT(5) #define DBI_CMD_GE_SCALE BIT(4) #define DBI_CMD_GE_ROT_MASK GENMASK(1, 0)
/* rotate flags for GE/VE ctrl */ #define MPP_ROTATION_0 (0 << 0) #define MPP_ROTATION_90 (1 << 0) #define MPP_ROTATION_180 (2 << 0) #define MPP_ROTATION_270 (3 << 0)
数据结构设计
struct aic_mdi_cfg
struct aic_mdi_cfg {
/* Input format, should be decided by some GPIO */
enum mdi_bus_mode bus_mode;
enum mdi_dat_endian big_endian;
struct mdi_bus_8080_cfg bus_8080;
struct mdi_bus_spi_cfg bus_spi;
/* Input pixel format, set by Host write command */
u32 width;
u32 height;
/* Output pixel format, depends on the panel */
u32 stride;
u32 sizeimage;
};/* Information of SPI bus */
struct mdi_bus_spi_cfg {
enum mdi_spi_mode mode;
enum mdi_spi_disp_fmt seq;
enum mdi_spi_rd_fmt rd_fmt;
};
/* Information of 8080/6800 bus */
struct mdi_bus_8080_cfg {
enum mdi_pin_ctl pin;
union {
enum mdi_x8_seq x8;
enum mdi_x16_seq x16;
} seq;
};DBI Bus 的格式定义
enum mdi_bus_mode {
MDI_BUS_MODE_8080 = 0,
MDI_BUS_MODE_6800 = 1,
MDI_BUS_MODE_SPI = 2
};
enum mdi_spi_disp_fmt {
MDI_SPI_DISP_FMT_RGB888 = 0,
MDI_SPI_DISP_FMT_RGB565 = 1,
MDI_SPI_DISP_FMT_RGB888_2 = 2, // only for 2SDA
};
enum mdi_spi_rd_fmt {
MDI_SPI_RD_FMT_8BIT = 0,
MDI_SPI_RD_FMT_24BIT = 1,
MDI_SPI_RD_FMT_32BIT = 2
};
enum mdi_spi_mode {
MDI_SPI_MODE_3WIRE = 0,
MDI_SPI_MODE_4WIRE = 1,
MDI_SPI_MODE_2SDA = 2,
MDI_SPI_MODE_4SDA = 3
};
enum mdi_x16_seq {
MDI_X16_SEQ_RGB888 = 0,
MDI_X16_SEQ_RGBX = 1,
MDI_X16_SEQ_RGBX666 = 2,
MDI_X16_SEQ_XRGB666 = 3,
MDI_X16_SEQ_RGB565 = 4
};
enum mdi_x8_seq {
MDI_X8_SEQ_RGB888 = 0,
MDI_X8_SEQ_RGB565 = 1
};
enum mdi_dat_endian {
MDI_DAT_ENDIAN_LOW_8 = 0,
MDI_DAT_ENDIAN_HIGH_8 = 1
};
enum mdi_pin_ctl {
MDI_PIN_CTL_X8 = 0,
MDI_PIN_CTL_X16 = 1
};DBI 命令的回调信息
struct aic_dbi_cmd {
u8 code;
u8 data_len;
char name[16];
int (*proc)(u8 code, u8 *data);
};struct aic_mdi
struct aic_mdi {
enum aic_mdi_status status;
struct aic_mdi_cfg cfg; /* The configuration of MDI HW */
unsigned int busy_pin_g;
unsigned int busy_pin_p;
bdi_cmd_cb cmd_cb;
/* Videobuf */
struct vb_queue queue;
struct list_head active_list;
aicos_mutex_t active_lock; /* lock of active buf list */
unsigned int hw_used_cnt;
unsigned int sequence;
unsigned int streaming;
aicos_mutex_t lock;
};MPP VIN Dev 层接口设计
| 函数原型 | int mpp_vin_dev_init(u32 cnt, struct aic_mdi_cfg *cfg) |
|---|---|
| 功能说明 | 完成 MDI VIN Dev 的状态初始化等 |
| 参数定义 |
cnt - 指定接收多少帧图像,传 0 表示数量不限
cfg - 配置 DBI Bus 的格式信息,如 8080、X16/X8 等
|
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | void mpp_vin_dev_deinit(void) |
|---|---|
| 功能说明 | MDI VIN Dev 的资源释放 |
| 参数定义 | 无 |
| 返回值 | 无 |
| 注意事项 | - |
Driver 层接口设计
| 函数原型 | int aic_mdi_open(bdi_cmd_cb cb, struct aic_mdi_cfg *cfg) |
|---|---|
| 功能说明 | 完成 MDI 的时钟设置、中断申请、Buf 状态初始化等 |
| 参数定义 |
cb - 当收到 DBI 命令时,处理 DBI 命令的回调函数
cfg - 配置 DBI Bus 的格式信息,如 8080、X16/X8 等
|
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_close(void) |
|---|---|
| 功能说明 | 关闭时钟、关闭 MDI 控制器 |
| 参数定义 | 无 |
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_in_fmt_set(enum dbi_mcu_if fmt, u8 flag) |
|---|---|
| 功能说明 | 设置 MDI 的输入视频格式 |
| 参数定义 |
fmt - MCU 接口的位宽格式
flag - 为了区分一些特殊格式,用此参数作为标记
|
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | void aic_mdi_out_pixel_set(u32 stride, u32 imagesize) |
|---|---|
| 功能说明 | 设置 MDI 的输出图像格式 |
| 参数定义 |
stride - 一行图像数据需要占用的字节个数
imagesize - 一帧图像数据需要占用的字节个数
|
| 返回值 | 无 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_stream_on(void) |
|---|---|
| 功能说明 | 启动视频流 |
| 参数定义 | 无 |
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_stream_off(void) |
|---|---|
| 功能说明 | 关闭视频流 |
| 参数定义 | 无 |
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_req_buf(char *buf, u32 size, struct vin_video_buf *vbuf) |
|---|---|
| 功能说明 | 按照给定的 Video Buf 配置信息从内存池中申请 Buf |
| 参数定义 |
buf - 指向内存池的指针
size - 内存池的总大小
vbuf - Video Buf 的配置信息
|
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_q_buf(u32 index) |
|---|---|
| 功能说明 | 释放指定 index 的 Buf 进入空闲队列(queued_list) |
| 参数定义 | index - Buf 的索引号 |
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
| 函数原型 | int aic_mdi_dq_buf(u32 *pindex) |
|---|---|
| 功能说明 | 从 DVP 处理完成后的队列(done_list)中获取一个 Buf |
| 参数定义 | pindex - 用于保存获取到的 Buf 索引号 |
| 返回值 | 0,成功;<0,失败 |
| 注意事项 | - |
APP Demo
test_mdi 命令的实现代码可以作为 APP 的设计参考,详见
bsp/examples/test-mdi/test_mdi.c:
static const char sopts[] = "c:h"; static const struct option lopts[] = { {"count", required_argument, NULL, 'c'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; /* Functions */ static void usage(char *program) { printf("Compile time: %s %s\n", __DATE__, __TIME__); printf("Usage: %s [options]: \n", program); printf("\t -c, --count\t\tthe number of capture frame \n"); printf("\t -h, --help \n"); printf("\n"); printf("Example: %s -c 1\n", program); } static long long int str2int(char *_str) { if (_str == NULL) { pr_err("The string is empty!\n"); return -1; } if (strncmp(_str, "0x", 2)) return atoi(_str); else return strtoll(_str, NULL, 16); } #define MDI_USE_8080_X16 // #define MDI_USE_8080_X8 // #define MDI_USE_6800_X16 // #define MDI_USE_6800_X8 // #define MDI_USE_SPI /* TODO: Should parse the input format from three GPIO * * GPIO1 GPIO2 GPIO3 Bus Mode * ----- ----- ----- --------- * 0 0 0 8080 X16 * 0 0 1 8080 X8 * 0 1 0 6800 X16 * 0 1 1 6800 X8 * 1 0 0 SPI 3-Wire * 1 0 1 SPI 4-Wire * 1 1 0 SPI 2-SDA * 1 1 1 SPI 4-SDA */ static int aic_mdi_bus_fmt_load(struct aic_mdi_cfg *cfg) { char *bus_mode[] = {"8080", "6800", "SPI"}; #ifdef MDI_USE_SPI char *spi_mode[] = {"3-Wire", "4-Wire", "2-SDA", "4-SDA"}; /* For SPI bus */ cfg->bus_mode = MDI_BUS_MODE_SPI; cfg->big_endian = MDI_DAT_ENDIAN_HIGH_8; cfg->bus_spi.mode = MDI_SPI_MODE_4SDA; printf("Bus mode: %s %s, Big endian: %d\n", bus_mode[cfg->bus_mode], spi_mode[cfg->bus_spi.mode], cfg->big_endian); return 0; #endif #ifdef MDI_USE_8080_X16 /* For 8080 bus */ cfg->bus_mode = MDI_BUS_MODE_8080; cfg->bus_8080.pin = MDI_PIN_CTL_X16; #endif #ifdef MDI_USE_8080_X8 cfg->bus_mode = MDI_BUS_MODE_8080; cfg->bus_8080.pin = MDI_PIN_CTL_X8; cfg->big_endian = MDI_DAT_ENDIAN_HIGH_8; #endif printf("Bus mode: %s, Pin: %s, Big endian: %d\n", bus_mode[cfg->bus_mode], cfg->bus_8080.pin ? "X16" : "X8", cfg->big_endian); return 0; } static void cmd_test_mdi(int argc, char **argv) { int c; u32 frame_cnt = 0; struct aic_mdi_cfg cfg = {0}; optind = 0; while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { switch (c) { case 'c': frame_cnt = str2int(optarg); continue; case 'h': usage(argv[0]); return; default: break; } } if (frame_cnt) pr_info("Capture %d frames from DBI\n", frame_cnt); if (aic_mdi_bus_fmt_load(&cfg) < 0) { pr_err("Failed to get the BUS format of MDI\n"); return; } mpp_vin_dev_init(frame_cnt, &cfg); } MSH_CMD_EXPORT_ALIAS(cmd_test_mdi, test_mdi, Test MDI module);
