设计说明
源码说明
源代码位于 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 驱动需要和我司的 MPP 中间件配合一起使用,MDI Driver 层采用普通的 API 方式向上提供接口。
MPP VIN 模块对 APP 提供类似 ioctl 的接口封装,类似 Linux 中的 ioctl 接口定义。
MDI 需要用到多任务并发,暂不支持在裸机环境中运行。
关键流程设计
初始化流程
总体上看,MDI 驱动的初始化过程实现在 aic_mdi_open() 接口中,完成的操作有:
1.使能 MDI 时钟 2.打卡 MDI 中断 3.初始化 VideoBuf 链表 4.使能 DVP 控制器
Buffer 队列管理
Buffer 队列管理复用 MPP VIN Buffer 模块中的机制,通过三个链表来管理这些 Buffer。
MDI->DE 场景的 Buffer 管理
整个 Buffer 流转的过程如下图:
MDI->GE->DE 场景的 Buffer 管理
整个 Buffer 流转的过程如下图:
局部刷新场景中的 Buffer 管理
在 MCU 属性 SPI 屏幕的应用中,通常为了节省带宽,刷新屏幕的数据过程是:
最开始,先推一屏完整的数据
后面的帧都采用局部刷新方式,只推一个小图
该小图的位置信息有 2A、2B 命令提供
分析认为,此场景必须要用到 GE 来做数据搬运和局部填充,数据通路也是: MDI->GE->DE。
但和上面场景有一个关键的差别,运行时需要一直保持一个背景图的备份,流程如下:
中断处理流程
MDI 的中断处理中需要处理两件事情:Buf 队列的流转 和 DBI 命令响应。
对于图像数据的接收,MDI 硬件提供的中断状态中有两个比较重要:
1.HNUM Interrupt:
表示当前图像帧已经完成10行(驱动中默认配置)数据的刷新,用来标识硬件已经读走了当前的 Register 值(影子寄存器),软件可以传入下一个 Buf 的参数了。
2.Frame done:
表示当前帧的数据传输完成。
可见,HNUM Interrupt 会先于 Frame done 发生,驱动中用 HNUM Interrupt 判断当前 Register 是否可以修改,用 Frame done 判断当前 Buf 是否完成(done 状态),该 Buf 就可以从 QBuf list 切换到 DQbuf list 了。
按照 MDI 硬件设计的逻辑,HNUM Interrupt 和 Frame done 时间是交错发生:
HNUM Interrupt -> Frame done -> HNUM Interrupt -> Frame done -> HNUM Interrupt -> Frame done ...
下图 MDI IRQ 的处理流程:
自定义 DBI 命令设计
Interface Pixel Format(0x3A)
0x3A 命令是一个标准的 DBI 命令,我们需要为了区分一些特殊格式,对 BIT3 做了扩展定义。
针对 BIT3 的自定义如下:(对应其中的 Flag 定义,代码详见 drv_mdi.c 的接口 aic_mdi_in_fmt_set())
/* 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
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GE Control(0xAC)
0xAC 命令用来实现配置 1605 GE 的控制参数,支持拉伸、镜像、旋转。定义如下:
#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)
2
3
4
关于 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)
2
3
4
5
数据结构设计
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其中,引用到了关于 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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
DBI 命令的回调信息
属于 MPP VIN Dev 层的接口,定义了一个 DBI 命令对应的数据内容及处理函数:
struct aic_dbi_cmd {
u8 code;
u8 data_len;
char name[16];
int (*proc)(u8 code, u8 *data);
};
2
3
4
5
6
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MPP VIN Dev 层接口设计
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,失败 |
注意事项 |
mpp_vin_dev_deinit
函数原型 | void mpp_vin_dev_deinit(void) |
---|---|
功能说明 | MDI VIN Dev 的资源释放 |
参数定义 | 无 |
返回值 | 无 |
注意事项 |
Driver 层接口设计
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,失败 |
注意事项 |
aic_mdi_close
函数原型 | int aic_mdi_close(void) |
---|---|
功能说明 | 关闭时钟、关闭 MDI 控制器 |
参数定义 | 无 |
返回值 | 0,成功;<0,失败 |
注意事项 |
aic_mdi_in_fmt_set
函数原型 | int aic_mdi_in_fmt_set(enum dbi_mcu_if fmt, u8 flag) |
---|---|
功能说明 | 设置 MDI 的输入视频格式 |
参数定义 | fmt - MCU 接口的位宽格式 |
flag - 为了区分一些特殊格式,用此参数作为标记 | |
返回值 | 0,成功;<0,失败 |
注意事项 |
aic_mdi_out_pixel_set
函数原型 | void aic_mdi_out_pixel_set(u32 stride, u32 imagesize) |
---|---|
功能说明 | 设置 MDI 的输出图像格式 |
参数定义 | stride - 一行图像数据需要占用的字节个数 |
imagesize - 一帧图像数据需要占用的字节个数 | |
返回值 | 无 |
注意事项 |
aic_mdi_stream_on
函数原型 | int aic_mdi_stream_on(void) |
---|---|
功能说明 | 启动视频流 |
参数定义 | 无 |
返回值 | 0,成功;<0,失败 |
注意事项 |
aic_mdi_stream_off
函数原型 | int aic_mdi_stream_off(void) |
---|---|
功能说明 | 关闭视频流 |
参数定义 | 无 |
返回值 | 0,成功;<0,失败 |
注意事项 |
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,失败 |
注意事项 |
aic_mdi_q_buf
函数原型 | int aic_mdi_q_buf(u32 index) |
---|---|
功能说明 | 释放指定 index 的 Buf 进入空闲队列(queued_list) |
参数定义 | index - Buf 的索引号 |
返回值 | 0,成功;<0,失败 |
注意事项 |
aic_mdi_dq_buf
函数原型 | 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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120