Edit online

设计说明

5 Dec 2024
Read time: 12 minute(s)
源码说明

源代码位于 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 层接口头文件

模块架构

整个软件系统的架构图如下:


sw_system11

  • MDI 驱动需要和 ArtInChip 自制的 MPP 中间件配合使用,MDI Driver 层采用普通的 API 方式向上提供接口。

  • MPP VIN 模块对 APP 提供类似 ioctl 的接口封装,类似 Linux 中的 ioctl 接口定义。

  • MDI 需要用到多任务并发,暂不支持在裸机环境中运行。

Edit online

关键流程设计

初始化流程

总体上看,MDI 驱动的初始化过程实现在 aic_mdi_open() 接口中,完成的操作有:

  1. 使能 MDI 时钟

  2. 打卡 MDI 中断

  3. 初始化 VideoBuf 链表

  4. 使能 DVP 控制器

Buffer 队列管理

Buffer 队列管理复用 MPP VIN Buffer 模块中的机制,通过三个链表来管理这些 Buffer。

MDI->DE 场景的 Buffer 管理

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


mdi_de_buf_flow

1. MDI->DE 场景中的 Buffer 流转过程
MDI->GE->DE 场景的 Buffer 管理

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


mdi_ge_de_buf_flow

2. MDI->GE->DE 场景中的 Buffer 流转过程
局部刷新场景中的 Buffer 管理

在局部刷新场景中,为了节省带宽,通常采用以下流程:

  • 先推一屏完整的数据。

  • 后面帧都采用局部刷新方式,只推一个小图,位置信息由 2A、2B 命令提供。

  • 将小图数据填充到空的 Buffer 中,并将这个 Buffer 提交给 GE 模块进行处理。

    GE 做数据搬运和局部填充。

  • 将处理后的 Buffer 提交给 DE 模块进行显示。

数据通路: MDI → GE → DE。
注: 运行时需要一直保持一个背景图的备份

mdi_part_refresh_flow

3. MDI → GE → DE 局部刷新场景中的 Buffer 流转过程

中断处理流程

MDI 的中断处理中需要处理 Buf 队列的流转和 Display Buffer Interface (DBI) 命令响应。

对于图像数据的接收,MDI 硬件提供以下关键的中断状态:
注: HNUM Interrupt 会先于 Frame done 发生。
  1. HNUM Interrupt

    用于判断当前 Register 是否可以修改。出现 HNUM Interrupt,表示当前图像帧已经完成 10 行(驱动中默认配置)数据的刷新,硬件已经读走了当前的 Register 值(影子寄存器),软件可以传入下一个 Buf 的参数了。

  2. Frame done

    用于判断当前 buf 是否完成(done 状态)。出现 Frame done,表示当前帧的数据传输完成,此时该 buf 可以从 QBuf list 切换到 DQbuf list。

按照 MDI 硬件设计的逻辑,HNUM InterruptFrame done 会间隔产生,如下所示:

HNUM Interrupt -> Frame done-> HNUM Interrupt -> Frame done-> HNUM Interrupt -> Frame done...


mdi_irq_flow

4. MDI 驱动中 IRQ 处理流程
Edit online

自定义 DBI 命令设计

Display Buffer Interface (DBI) 命令是用于控制显示设备(如液晶显示屏)的一组指令。这些命令通常由主控芯片 (MCU) 发送给显示控制器,以配置和控制显示参数。以下是一些常见的 DBI 命令及其功能:

0x3A:Interface Pixel Format

0x3A 命令是一个标准的 DBI 命令,用于设置显示接口的像素格式。


dbi_cmd_3a

5. 0x3A 命令的原始定义

为了区分一些特殊格式,对 BIT3 新增了扩展定义。通过修改 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
 */
注: 对应其中的 Flag 定义,代码详见 drv_mdi.c 的接口 aic_mdi_in_fmt_set()

0xAC:GE Control

0xAC 命令用于配置图形引擎(GE)的控制参数,包括拉伸、镜像和旋转等操作。
0xAC 命令定义如下:
#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)
Rotation 的角度定义,复用 MPP 中的定义:
/* 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)
Edit online

数据结构设计

struct aic_mdi_cfg

属于 MPP VIN Dev 层的接口,定义了输入信号、显示输出的配置信息,由 APP 层在初始化时传入,包含以下字段:
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;
};
其中,引用到了关于 DBI BUS 的配置信息:
/* 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 的格式定义

属于 MPP VIN Dev 层的接口,定义了 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 命令的回调信息

属于 MPP VIN Dev 层的接口,定义了一个 DBI 命令对应的数据内容及处理函数:
struct aic_dbi_cmd {
    u8 code;
    u8 data_len;
    char name[16];
    int (*proc)(u8 code, u8 *data);
};

struct aic_mdi

属于 Driver 层内部接口,定义了 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;
};
Edit online

MPP VIN Dev 层接口设计

1. mpp_vin_dev_init
函数原型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,失败
注意事项-
2. mpp_vin_dev_deinit
函数原型void mpp_vin_dev_deinit(void)
功能说明MDI VIN Dev 的资源释放
参数定义
返回值
注意事项-
Edit online

Driver 层接口设计

3. aic_mdi_open
函数原型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,失败
注意事项-
4. aic_mdi_close
函数原型int aic_mdi_close(void)
功能说明关闭时钟、关闭 MDI 控制器
参数定义
返回值0,成功;<0,失败
注意事项-
5. aic_mdi_in_fmt_set
函数原型int aic_mdi_in_fmt_set(enum dbi_mcu_if fmt, u8 flag)
功能说明设置 MDI 的输入视频格式
参数定义
fmt - MCU 接口的位宽格式
flag - 为了区分一些特殊格式,用此参数作为标记
返回值0,成功;<0,失败
注意事项-
6. aic_mdi_out_pixel_set
函数原型void aic_mdi_out_pixel_set(u32 stride, u32 imagesize)
功能说明设置 MDI 的输出图像格式
参数定义
stride - 一行图像数据需要占用的字节个数
imagesize - 一帧图像数据需要占用的字节个数
返回值
注意事项-
7. aic_mdi_stream_on
函数原型int aic_mdi_stream_on(void)
功能说明启动视频流
参数定义
返回值0,成功;<0,失败
注意事项-
8. aic_mdi_stream_off
函数原型int aic_mdi_stream_off(void)
功能说明关闭视频流
参数定义
返回值0,成功;<0,失败
注意事项-
9. aic_mdi_req_buf
函数原型int aic_mdi_req_buf(char *buf, u32 size, struct vin_video_buf *vbuf)
功能说明按照给定的 Video Buf 配置信息从内存池中申请 Buf
参数定义
buf - 指向内存池的指针
size - 内存池的总大小
vbuf - Video Buf 的配置信息
返回值0,成功;<0,失败
注意事项-
10. aic_mdi_q_buf
函数原型int aic_mdi_q_buf(u32 index)
功能说明释放指定 index 的 Buf 进入空闲队列(queued_list)
参数定义index - Buf 的索引号
返回值0,成功;<0,失败
注意事项-
11. aic_mdi_dq_buf
函数原型int aic_mdi_dq_buf(u32 *pindex)
功能说明从 DVP 处理完成后的队列(done_list)中获取一个 Buf
参数定义pindex - 用于保存获取到的 Buf 索引号
返回值0,成功;<0,失败
注意事项-
Edit online

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);