• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 标准系统方案之瑞芯微RK3568移植案例
2
3​本文章是基于瑞芯微RK3568芯片的DAYU200开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。
4
5## 产品配置和目录规划
6
7### 产品配置
8
9在产品`//productdefine/common/device`目录下创建以rk3568名字命名的json文件,并指定CPU的架构。`//productdefine/common/device/rk3568.json`配置如下:
10
11```
12{
13    "device_name": "rk3568",
14    "device_company": "rockchip",
15    "target_os": "ohos",
16    "target_cpu": "arm",
17    "kernel_version": "",
18    "device_build_path": "device/board/hihope/rk3568",
19    "enable_ramdisk": true,   //是否支持ramdisk二级启动
20    "build_selinux": true    // 是否支持selinux权限管理
21}
22```
23
24在`//productdefine/common/products`目录下创建以产品名命名的rk3568.json文件。该文件用于描述产品所使用的SOC 以及所需的子系统。配置如下
25
26```
27{
28  "product_name": "rk3568",
29  "product_company" : "hihope",
30  "product_device": "rk3568",
31  "version": "2.0",
32  "type": "standard",
33  "parts":{
34    "ace:ace_engine_standard":{},
35    "ace:napi":{},
36    ...
37    "xts:phone_tests":{}
38  }
39}
40```
41
42主要的配置内容包括:
43
441. product_device:配置所使用的SOC。
452. type:配置系统的级别, 这里直接standard即可。
463. parts:系统需要启用的子系统。子系统可以简单理解为一块独立构建的功能块。
47
48已定义的子系统可以在`//build/subsystem_config.json`中找到。当然你也可以定制子系统。
49
50这里建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不适合rk3568。
51
52### 目录规划
53
54参考[Board和SoC解耦的设计思路](https://gitee.com/openharmony-sig/sig-content/blob/master/devboard/docs/board-soc-arch-design.md),并把芯片适配目录规划为:
55
56```
57device
58├── board                                --- 单板厂商目录
59│   └── hihope                           --- 单板厂商名字:
60│       └── rk3568                       --- 单板名:rk3568,主要放置开发板相关的驱动业务代码
61└── soc									 --- SoC厂商目录
62    └── rockchip                       --- SoC厂商名字:rockchip
63        └── rk3568						 --- SoC Series名:rk3568,主要为芯片原厂提供的一些方案,以及闭源库等
64
65
66```
67
68```
69vendor
70└── hihope
71    └── rk3568         			 --- 产品名字:产品、hcs以及demo相关
72```
73
74## **内核启动**
75
76### 二级启动
77
78二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。
79
80Rk3568适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作:
81
821.使能二级启动
83
84productdefine/common/device/rk3568.json 中使能enable_ramdisk。
85
86  ```
87  {
88      "device_name": "rk3568",
89      "device_company": "hihope",
90      "target_os": "ohos",
91      "target_cpu": "arm",
92      "kernel_version": "",
93      "device_build_path": "device/hihope/build",
94      "enable_ramdisk": true,
95      "build_selinux": true
96  }
97  ```
98
992.把主线编译出来的ramdsik.img 打包到boot_linux.img
100
101配置:
102
103由于rk 启动uboot 支持从ramdisk 启动,只需要在打包boot_linux.img 的配置文件中增加ramdisk.img ,因此没有使用主线的its格式,具体配置就是在内核编译脚本make-ohos.sh 中增加:
104
105```
106function make_extlinux_conf()
107{
108	dtb_path=$1
109	uart=$2
110	image=$3
111
112	echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF}
113	echo "	kernel /extlinux/${image}" >> ${EXTLINUX_CONF}
114	echo "	fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF}
115	if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then
116		echo "	initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF}
117	fi
118	cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4"
119	echo "  ${cmdline}" >> ${EXTLINUX_CONF}
120}
121```
122
123### 打包
124
125增加了打包boot镜像的脚本make-boot.sh,供编译完ramdisk,打包boot 镜像时调用, 主要内容:
126
127```
128genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img
129```
130
131调用make-boot.sh 的修改可以参考如下pr:
132
133https://gitee.com/openharmony/build/pulls/569/files
134
135### INIT配置
136
137init相关配置请参考[启动子系统的规范要求](https://gitee.com/openharmony/docs/blob/master/zh-cn/readme/%E5%90%AF%E5%8A%A8%E6%81%A2%E5%A4%8D%E5%AD%90%E7%B3%BB%E7%BB%9F.md)即可
138
139## **音频**
140
141### RK3568 Audio总体结构图
142
143![dayu200-audio-01.png](figures/dayu200/dayu200-audio-01.png)
144
145### ADM适配方案介绍
146
147#### RK3568平台适配ADM框架图
148
149![](figures/dayu200/dayu200-audio-02.png)
150
1511. ADM Drivers adapter
152
153   主要完成Codec/DMA/I2S驱动注册,使得ADM可以加载驱动节点;并注册ADM与Drivers交互的接口函数
154
1552. ADM Drivers impl
156
157   主要完成ADM Drivers adapter接口函数的实现,以及Codec_config.hcs/dai_config.hcs等配置信息的获取,并注册到对应的设备
158
1593. Linux Drivers
160
161   ADM Drivers impl可以直接阅读硬件手册,完成驱动端到端的配置;也可以借用Linux原生驱动实现与接口,减少开发者工作量。
162
163#### 目录结构
164
165```
166./device/board/hihope/rk3568/audio_drivers
167├── codec
168│   └── rk809_codec
169│       ├── include
170│       │   ├── rk809_codec_impl.h
171│       │   └── rk817_codec.h
172│       └── src
173│           ├── rk809_codec_adapter.c
174│           ├── rk809_codec_linux_driver.c
175│           └── rk809_codec_ops.c
176├── dai
177│   ├── include
178│   │   ├── rk3568_dai_linux.h
179│   │   └── rk3568_dai_ops.h
180│   └── src
181│       ├── rk3568_dai_adapter.c
182│       ├── rk3568_dai_linux_driver.c
183│       └── rk3568_dai_ops.c
184├── dsp
185│   ├── include
186│   │   └── rk3568_dsp_ops.h
187│   └── src
188│       ├── rk3568_dsp_adapter.c
189│       └── rk3568_dsp_ops.c
190├── include
191│   ├── audio_device_log.h
192│   └── rk3568_audio_common.h
193└── soc
194    ├── include
195    │   └── rk3568_dma_ops.h
196    └── src
197        ├── rk3568_dma_adapter.c
198        └── rk3568_dma_ops.c
199```
200
201### RK3568适配ADM详细过程
202
203#### 梳理平台Audio框架
204
205梳理目标平台的Audio结构,明确数据流与控制流通路。
206
2071. 针对RK3568平台,Audio的结构相对简单见RK3568 Audio总体结构图,Codec作为一个独立设备。I2C完成对设备的控制,I2S完成Codec设备与CPU之间的交互。
2082. 结合原理图整理I2S通道号,对应的引脚编号;I2C的通道号,地址等硬件信息。
2093. 获取Codec对应的datasheet,以及RK3568平台的Datasheet(包含I2S/DMA通道等寄存器的介绍)。
210
211#### 熟悉并了解ADM结构
212
213ADM结构框图如下,Audio Peripheral Drivers和Platform Drivers为平台适配需要完成的工作。
214
215![dayu200-audio-03.png](figures/dayu200/dayu200-audio-03.png)
216
217结合第1步梳理出来的Audio结构分析,Audio Peripheral Drivers包含Rk809的驱动,Platform Drivers包含DMA驱动和I2S驱动。
218
219| 需要适配的驱动 | ADM对应模块 | 接口文件路径                                         |
220| -------------- | ----------- | ---------------------------------------------------- |
221| RK809驱动      | Accessory   | drivers/framework/include/audio/audio_accessory_if.h |
222| DMA驱动        | platform    | drivers/framework/include/audio/audio_platform_if.h  |
223| I2S驱动        | DAI         | drivers/framework/include/audio/audio_dai_if.h.h     |
224
225#### 搭建驱动代码框架
226
227##### 配置HCS文件
228
229device_info.hcs文件中Audio下注册驱动节点
230
231```c
232        audio :: host {
233            hostName = "audio_host";
234            priority = 60;
235            device_dai0 :: device {
236                device0 :: deviceNode {
237                    policy = 1;
238                    priority = 50;
239                    preload = 0;
240                    permission = 0666;
241                    moduleName = "DAI_RK3568";
242                    serviceName = "dai_service";
243                    deviceMatchAttr = "hdf_dai_driver";
244                }
245            }
246            device_codec :: device {
247                device0 :: deviceNode {
248                    policy = 1;
249                    priority = 50;
250                    preload = 0;
251                    permission = 0666;
252                    moduleName = "CODEC_RK809";
253                    serviceName = "codec_service_0";
254                    deviceMatchAttr = "hdf_codec_driver";
255                }
256            }
257            device_codec_ex :: device {
258                device0 :: deviceNode {
259                    policy = 1;
260                    priority = 50;
261                    preload = 0;
262                    permission = 0666;
263                    moduleName = "CODEC_RK817";
264                    serviceName = "codec_service_1";
265                    deviceMatchAttr = "hdf_codec_driver_ex";
266                }
267            }
268            device_dsp :: device {
269                device0 :: deviceNode {
270                    policy = 1;
271                    priority = 50;
272                    preload = 0;
273                    permission = 0666;
274                    moduleName = "DSP_RK3568";
275                    serviceName = "dsp_service_0";
276                    deviceMatchAttr = "hdf_dsp_driver";
277                }
278            }
279            device_dma :: device {
280                device0 :: deviceNode {
281                    policy = 1;
282                    priority = 50;
283                    preload = 0;
284                    permission = 0666;
285                    moduleName = "DMA_RK3568";
286                    serviceName = "dma_service_0";
287                    deviceMatchAttr = "hdf_dma_driver";
288                }
289            }
290            ......
291        }
292
293```
294
295根据接入的设备,选择Codec节点还是Accessory节点,配置硬件设备对应的私有属性(包含寄存器首地址,相关control寄存器地址)涉及Codec_config.hcsDAI_config.hcs
296
297配置相关介绍见[Audio](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/driver/driver-peripherals-audio-des.md) hcs配置章节以及ADM框架的audio_parse模块代码。
298
299##### codec/accessory模块
300
3011. 将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
302
303   ```
304   struct HdfDriverEntry g_codecDriverEntry = {
305      .moduleVersion = 1,
306      .moduleName = "CODEC_HI3516",
307      .Bind = CodecDriverBind,
308      .Init = CodecDriverInit,
309      .Release = CodecDriverRelease,
310   };
311   HDF_INIT(g_codecDriverEntry);
312   ```
313
3142. Codec模块需要填充:
315
316   g_codecData:codec设备的操作函数集和私有数据集。
317
318   g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。
319
320   g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。
321
3223. 完成 bind、init和release函数的实现
323
3244.  验证
325
326   在bind和init函数加调试日志,编译版本并获取系统系统日志:
327
328   ```
329   [    1.548624] [E/"rk809_codec_adapter"]  [Rk809DriverBind][line:258]: enter
330   [    1.548635] [E/"rk809_codec_adapter"]  [Rk809DriverBind][line:260]: success
331   [    1.548655] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:270]: enter
332   [    1.549050] [E/"rk809_codec_adapter"]  [GetServiceName][line:226]: enter
333   [    1.549061] [E/"rk809_codec_adapter"]  [GetServiceName][line:250]: success
334   [    1.549072] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:316]: g_chip->accessory.drvAccessoryName = codec_service_1
335   [    1.549085] [E/audio_core]  [AudioSocRegisterDai][line:86]: Register [accessory_dai] success.
336   [    1.549096] [E/audio_core]  [AudioRegisterAccessory][line:120]: Register [codec_service_1] success.
337   [    1.549107] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:323]: success!
338   ```
339
340
341
342##### DAI模块
343
3441. 将I2S驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
345
346   ```c
347   struct HdfDriverEntry g_daiDriverEntry = {
348       .moduleVersion = 1,
349       .moduleName = "DAI_RK3568",
350       .Bind = DaiDriverBind,
351       .Init = DaiDriverInit,
352       .Release = DaiDriverRelease,
353   };
354   HDF_INIT(g_daiDriverEntry);
355   ```
356
3572. DAI模块填充:
358
359   ```c
360   struct AudioDaiOps g_daiDeviceOps = {
361       .Startup = Rk3568DaiStartup,
362       .HwParams = Rk3568DaiHwParams,
363       .Trigger = Rk3568NormalTrigger,
364   };
365
366   struct DaiData g_daiData = {
367       .Read = Rk3568DeviceReadReg,
368       .Write = Rk3568DeviceWriteReg,
369       .DaiInit = Rk3568DaiDeviceInit,
370       .ops = &g_daiDeviceOps,
371   };
372   ```
373
3743. 完成 bind、init和release函数的实现
375
3764. 验证
377
378bind/init函数加调试日志,编译版本并获取系统系统日志
379
380   ```
381   [    1.549193] [I/device_node] launch devnode dai_service
382   [    1.549204] [E/HDF_LOG_TAG]  [DaiDriverBind][line:38]: entry!
383   [    1.549216] [E/HDF_LOG_TAG]  [DaiDriverBind][line:55]: success!
384   [    1.549504] [E/audio_core]  [AudioSocRegisterDai][line:86]: Register [dai_service] success.
385   [    1.549515] [E/HDF_LOG_TAG]  [DaiDriverInit][line:116]: success.
386   ```
387
388##### Platform模块
389
3901. 将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
391
392   ```
393   struct HdfDriverEntry g_platformDriverEntry = {
394       .moduleVersion = 1,
395       .moduleName = "DMA_RK3568",
396       .Bind = PlatformDriverBind,
397       .Init = PlatformDriverInit,
398       .Release = PlatformDriverRelease,
399   };
400   HDF_INIT(g_platformDriverEntry);
401   ```
402
4032. DMA模块需要填充:
404
405   ```c
406   struct AudioDmaOps g_dmaDeviceOps = {
407       .DmaBufAlloc = Rk3568DmaBufAlloc,
408       .DmaBufFree = Rk3568DmaBufFree,
409       .DmaRequestChannel = Rk3568DmaRequestChannel,
410       .DmaConfigChannel = Rk3568DmaConfigChannel,
411       .DmaPrep = Rk3568DmaPrep,
412       .DmaSubmit = Rk3568DmaSubmit,
413       .DmaPending = Rk3568DmaPending,
414       .DmaPause = Rk3568DmaPause,
415       .DmaResume = Rk3568DmaResume,
416       .DmaPointer = Rk3568PcmPointer,
417   };
418
419   struct PlatformData g_platformData = {
420       .PlatformInit = AudioDmaDeviceInit,
421       .ops = &g_dmaDeviceOps,
422   };
423   ```
424
4253. 完成 bind、init和release函数的实现
426
4274. 验证
428
429   在bind和init函数加调试日志,编译版本并获取系统系统日志
430
431   ```
432   [    1.548469] [E/rk3568_platform_adapter]  [PlatformDriverBind][line:42]: entry!
433   [    1.548481] [E/rk3568_platform_adapter]  [PlatformDriverBind][line:58]: success!
434   [    1.548492] [E/rk3568_platform_adapter]  [PlatformDriverInit][line:100]: entry.
435   [    1.548504] [E/rk3568_platform_adapter]  [PlatformGetServiceName][line:67]: entry!
436   [    1.548515] [E/rk3568_platform_adapter]  [PlatformGetServiceName][line:91]: success!
437   [    1.548528] [E/audio_core]  [AudioSocRegisterPlatform][line:63]: Register [dma_service_0] success.
438   [    1.548536] [E/rk3568_platform_adapter]  [PlatformDriverInit][line:119]: success.
439   ```
440
441#### 驱动适配
442
443##### code/accessory模块
444
4451. 读取DTS文件,获取到对应设备节点,使用Linux原生的驱动注册函数,获取到对应device。
446
447   ```
448   static int rk817_platform_probe(struct platform_device *pdev) {
449       rk817_pdev = pdev;
450       dev_info(&pdev->dev, "got rk817-codec platform_device");
451       return 0;
452   }
453
454   static struct platform_driver rk817_codec_driver = {
455   	.driver = {
456   		   .name = "rk817-codec",                     // codec node in dts file
457   		   .of_match_table = rk817_codec_dt_ids,
458   		   },
459   	.probe = rk817_platform_probe,
460   	.remove = rk817_platform_remove,
461   };
462   ```
463
4642. 读写寄存器函数封装
465   根据上述获取到的device, 使用Linux的regmap函数,开发者不需要获取模块的基地址
466   获取rk817的regmap代码段
467
468   ```
469   g_chip = devm_kzalloc(&rk817_pdev->dev, sizeof(struct Rk809ChipData), GFP_KERNEL);
470       if (!g_chip) {
471           AUDIO_DEVICE_LOG_ERR("no memory");
472           return HDF_ERR_MALLOC_FAIL;
473       }
474       g_chip->pdev = rk817_pdev;
475
476       struct rk808 *rk808 = dev_get_drvdata(g_chip->pdev->dev.parent);
477       if (!rk808) {
478           AUDIO_DEVICE_LOG_ERR("%s: rk808 is NULL\n", __func__);
479           ret = HDF_FAILURE;
480           RK809ChipRelease();
481   		return ret;
482       }
483       g_chip->regmap = devm_regmap_init_i2c(rk808->i2c,
484   		&rk817_codec_regmap_config);
485       if (IS_ERR(g_chip->regmap)) {
486           AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap));
487           RK809ChipRelease();
488   		return ret;
489       }
490   ```
491
492   寄存器读写函数代码段
493
494   ```
495   int32_t Rk809DeviceRegRead(uint32_t reg, uint32_t *val)
496     {
497         if (regmap_read(g_chip->regmap, reg, val)) {
498             AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
499             return HDF_FAILURE;
500         }
501
502         return HDF_SUCCESS;
503     }
504
505     int32_t Rk809DeviceRegWrite(uint32_t reg, uint32_t value) {
506         if (regmap_write(g_chip->regmap, reg, value)) {
507             AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
508             return HDF_FAILURE;
509         }
510
511         return HDF_SUCCESS;
512     }
513
514     int32_t Rk809DeviceRegUpdatebits(uint32_t reg, uint32_t mask, uint32_t value) {
515         if (regmap_update_bits(g_chip->regmap, reg, mask, value)) {
516             AUDIO_DRIVER_LOG_ERR("update register bits fail: [%04x] = %04x", reg, value);
517             return HDF_FAILURE;
518         }
519
520         return HDF_SUCCESS;
521     }
522   ```
523
5243. 寄存器初始化函数
525
526   因为使用Linux的regmap函数,所以需要自行定义RegDefaultInit函数,读取hcs中initSeqConfig的寄存器以及数值来进行配置
527
528   RK809RegDefaultInit代码段
529
530   ```c
531   int32_t RK809RegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup)
532   {
533     int32_t i;
534     struct AudioAddrConfig *regAttr = NULL;
535
536     if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL ||
537        regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) {
538        AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
539
540        return HDF_ERR_INVALID_PARAM;
541     }
542
543     regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem;
544
545     for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) {
546        Rk809DeviceRegWrite(regAttr[i].addr, regAttr[i].value);
547     }
548
549     return HDF_SUCCESS;
550   }
551   ```
552
5534. 封装控制接口的读写函数
554
555   设置控制读写函数为RK809CodecReadReg和RK809CodecWriteReg
556
557   ```c
558   struct CodecData g_rk809Data = {
559       .Init = Rk809DeviceInit,
560       .Read = RK809CodecReadReg,
561       .Write = RK809CodecWriteReg,
562   };
563
564   struct AudioDaiOps g_rk809DaiDeviceOps = {
565       .Startup = Rk809DaiStartup,
566       .HwParams = Rk809DaiHwParams,
567   	.Trigger = RK809NormalTrigger,
568   };
569
570   struct DaiData g_rk809DaiData = {
571       .DaiInit = Rk809DaiDeviceInit,
572       .ops = &g_rk809DaiDeviceOps,
573   };
574   ```
575
576   封装控制接口的读写函数
577
578   因为原来的读写原型,涉及三个参数(unsigned long virtualAddress,uint32_t reg, uint32_t *val),其中virtualAddress我们并不需要用到,所以封装个接口即可,封装如下
579
580   ```c
581   int32_t RK809CodecReadReg(unsigned long virtualAddress,uint32_t reg, uint32_t *val)
582   {
583       if (val == NULL) {
584           AUDIO_DRIVER_LOG_ERR("param val is null.");
585           return HDF_FAILURE;
586       }
587       if (Rk809DeviceRegRead(reg, val)) {
588           AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
589           return HDF_FAILURE;
590       }
591       ADM_LOG_ERR("read reg 0x[%02x] = 0x[%02x]",reg,*val);
592       return HDF_SUCCESS;
593   }
594
595   int32_t RK809CodecWriteReg(unsigned long virtualAddress,uint32_t reg, uint32_t value)
596   {
597       if (Rk809DeviceRegWrite(reg, value)) {
598           AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
599           return HDF_FAILURE;
600       }
601       ADM_LOG_ERR("write reg 0x[%02x] = 0x[%02x]",reg,value);
602       return HDF_SUCCESS;
603   }
604   ```
605
6065. 其他ops函数
607
608  - Rk809DeviceInit,读取hcs文件,初始化Codec寄存器,同时将对应的control配置(/* reg, rreg, shift, rshift, min, max, mask, invert, value */添加到kcontrol,便于dispatch contro进行控制
609  - Rk809DaiStartup, 读取hcs文件,配置可选设备(codec/accessory)的控制寄存器
610  - Rk809DaiHwParams, 根据hal下发的audio attrs(采样率、format、channel等),配置对应的寄存器
611  - RK809NormalTrigger,根据hal下发的操作命令码,操作对应的寄存器,实现Codec的启动停止、录音和放音的切换等
612
613##### DAI(i2s)模块
614
6151. 读写寄存器函数
616   思路与Codec模块的一致,读取Linux DTS文件,使用Linux的regmap函数完成寄存器的读写操作
617
618   ```c
619   int32_t Rk3568DeviceReadReg(unsigned long regBase, uint32_t reg, uint32_t *val)
620    {
621        AUDIO_DEVICE_LOG_ERR("entry");
622        (void)regBase;
623        struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
624        if(dmaOfNode == NULL) {
625            AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
626        }
627        struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
628        struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
629
630        (void)regBase;
631        if (regmap_read(i2s_tdm->regmap, reg, val)) {
632            AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", reg);
633            return HDF_FAILURE;
634        }
635        return HDF_SUCCESS;
636    }
637
638    int32_t Rk3568DeviceWriteReg(unsigned long regBase, uint32_t reg, uint32_t value)
639    {
640        AUDIO_DEVICE_LOG_ERR("entry");
641        (void)regBase;
642        struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
643        if(dmaOfNode == NULL) {
644            AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
645        }
646        struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
647        struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
648        if (regmap_write(i2s_tdm->regmap, reg, value)) {
649            AUDIO_DEVICE_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
650            return HDF_FAILURE;
651        }
652        return HDF_SUCCESS;
653    }
654   ```
655
6562. 其他ops函数
657
658  - Rk3568DaiDeviceInit
659    原始框架,主要完成DAI_config.hcs参数列表的读取,与HwParams结合,完成参数的设置。
660
661  - Rk3568DaiHwParams
662    主要完成I2S MCLK/BCLK/LRCLK时钟配置。
663
664    1. 根据不同采样率计算MCLK
665
666    ```c
667        int32_t RK3568I2sTdmSetSysClk(struct rk3568_i2s_tdm_dev *i2s_tdm, const struct AudioPcmHwParams *param)
668        {
669            /* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */
670            uint32_t sampleRate = param->rate;
671            uint32_t mclk_parent_freq = 0;
672            switch (sampleRate) {
673                case AUDIO_DEVICE_SAMPLE_RATE_8000:
674                case AUDIO_DEVICE_SAMPLE_RATE_16000:
675                case AUDIO_DEVICE_SAMPLE_RATE_24000:
676                case AUDIO_DEVICE_SAMPLE_RATE_32000:
677                case AUDIO_DEVICE_SAMPLE_RATE_48000:
678                case AUDIO_DEVICE_SAMPLE_RATE_64000:
679                case AUDIO_DEVICE_SAMPLE_RATE_96000:
680                mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_192000;
681                break;
682                case AUDIO_DEVICE_SAMPLE_RATE_11025:
683                case AUDIO_DEVICE_SAMPLE_RATE_22050:
684                case AUDIO_DEVICE_SAMPLE_RATE_44100:
685
686                mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_176400;
687                break;
688                default:
689                AUDIO_DEVICE_LOG_ERR("Invalid LRCK freq: %u Hz\n", sampleRate);
690                    return HDF_FAILURE;
691            }
692            i2s_tdm->mclk_tx_freq = mclk_parent_freq;
693            i2s_tdm->mclk_rx_freq = mclk_parent_freq;
694
695            return HDF_SUCCESS;
696        }
697    ```
698
699    2. 根据获取的mclk,计算BCLK/LRclk分频系数
700
701  - Rk3568NormalTrigger
702    根据输入输出类型,以及cmd(启动/停止/暂停/恢复),完成一系列配置:
703
704    1. mclk的启停
705    2. DMA搬运的启停
706    3. 传输的启停
707       详细实现见代码,参考Linux原生I2s驱动对应接口函数
708
709    ```c
710        // 启动/恢复流程
711        if (streamType == AUDIO_RENDER_STREAM) {
712            clk_prepare_enable(i2s_tdm->mclk_tx);
713            regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
714                       I2S_DMACR_TDE_ENABLE,
715                       I2S_DMACR_TDE_ENABLE);
716        } else {
717            clk_prepare_enable(i2s_tdm->mclk_rx);
718            regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
719                       I2S_DMACR_RDE_ENABLE,
720                       I2S_DMACR_RDE_ENABLE);
721            if (regmap_read(i2s_tdm->regmap, I2S_DMACR, &val)) {
722                AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_DMACR);
723                return ;
724                }
725            AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_DMACR, val);
726        }
727
728        if (atomic_inc_return(&i2s_tdm->refcount) == 1) {
729            regmap_update_bits(i2s_tdm->regmap, I2S_XFER,
730                       I2S_XFER_TXS_START |
731                       I2S_XFER_RXS_START,
732                       I2S_XFER_TXS_START |
733                       I2S_XFER_RXS_START);
734            if (regmap_read(i2s_tdm->regmap, I2S_XFER, &val)) {
735                AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_XFER);
736                return ;
737                }
738            AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_XFER, val);
739        }
740    ```
741
742   ##### Platform(DMA)模块
743
744ops函数相关函数
745
7461. Rk3568DmaBufAlloc/Rk3568DmaBufFree
747
748    获取DMA设备节点,参考I2s设备获取方式,使用系统函数dma_alloc_wc/dma_free_wc,完成DMA虚拟内存与物理内存的申请/释放
749
7502.  Rk3568DmaRequestChannel
751
752    使用Linux DMA原生接口函数获取DMA传输通道,dma_request_slave_channel
753
754   ```
755   dmaRtd->dmaChn[streamType] = dma_request_slave_channel(dmaDevice, dmaChannelNames[streamType]);
756   ```
757
7583.  Rk3568DmaConfigChannel
759
760   ```
761      //设置通道配置参数
762      // 放音通道参数配置
763      slave_config.direction = DMA_MEM_TO_DEV;
764      slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
765      slave_config.dst_addr = I2S1_ADDR + I2S_TXDR;
766      slave_config.dst_maxburst = 8;
767      // 录音通道参数配置
768      slave_config.direction = DMA_DEV_TO_MEM;
769      slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
770      slave_config.src_addr = I2S1_ADDR + I2S_RXDR;
771      slave_config.src_maxburst = 8;
772
773      //使用Linux DMA原生接口函数完成DMA通道配置
774      ret = dmaengine_slave_config(dmaChan, &slave_config);
775      if (ret != 0) {
776          AUDIO_DEVICE_LOG_ERR("dmaengine_slave_config failed");
777          return HDF_FAILURE;
778      }
779   ```
780
7814. Rk3568DmaSubmit/Rk3568DmaPending
782
783      使用Linux DMA原生接口函数dmaengine_prep_dma_cyclic,初始化一个具体的周期性的DMA传输描述符dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。
784
7855. Rk3568PcmPointer
786
787第4步完成之后,ADM框架调用Rk3568PcmPointer,循环写cirBuf,计算pointer
788
789   ```
790
791      dma_chn = dmaRtd->dmaChn[DMA_TX_CHANNEL];
792      buf_size = data->renderBufInfo.cirBufSize;
793      dmaengine_tx_status(dma_chn, dmaRtd->cookie[DMA_TX_CHANNEL], &dma_state);
794      if (dma_state.residue) {
795          currentPointer = buf_size - dma_state.residue;
796          *pointer = BytesToFrames(data->pcmInfo.frameSize, currentPointer);
797      } else {
798          *pointer = 0;
799      }
800   ```
801
8026. Rk3568DmaPause
803
804      使用Linux DMA原生接口函数dmaengine_terminate_async,停止DMA传输
805
806   ```
807    dmaengine_terminate_async(dmaChan);
808   ```
809
8107. Rk3568DmaResume
811
812   暂停使用的DMA停止函数,对应恢复,相当于重启DMA传输,执行Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成
813
814##### 适配中遇到问题与解决方案
815
8161. 播放一段时间后,停止播放,持续有尖锐的很小的声音
817        问题原因:播放停止后,Codec相关器件没有下电
818        解决方案:注册Codec的trigger函数,当接收到Cmd为Stop时,对Codec进行下电
819
8202. 播放一段时间后,停止播放,然后重新播放没有声音
821         问题原因:DMA驱动的PAUSE接口函数,并未停止DMA传输
822         解决方案:暂停状态不再使用DMA的PAUSE函数,而是使用DAM传输停止接口; 相对应的,恢复函数的业务逻辑相当于重启DMA传输,执行             Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成
823
8243. 播放存在杂音
825          问题原因:DMA数据搬运pointer位置不正确
826          解决方案:Rk3568PcmPointer函数返回值为DMA搬运的内存位置,用缓存区buf与dma_state.residue的差值计算
827
8284. 可以放音,但Mclk引脚没有时钟信号
829           问题原因:DTS文件pin-ctrl没有配置mclk的引脚
830           解决方案:修改DTS文件
831
832### Camera
833
834**基本概念**
835
836OpenHarmony相机驱动框架模型对上实现相机HDI接口,对下实现相机Pipeline模型,管理相机各个硬件设备。各层的基本概念如下。
837
8381. HDI实现层:对上实现OHOS相机标准南向接口。
839
8402. 框架层:对接HDI实现层的控制、流的转发,实现数据通路的搭建、管理相机各个硬件设备等功能。
841
8423. 适配层:屏蔽底层芯片和OS差异,支持多平台适配。
843
844### Camera驱动框架介绍
845
846#### 源码框架介绍
847
848Camera 驱动框架所在的仓为:drivers_peripheral,源码目录为:“drivers/peripheral/camera”。
849
850```
851|-- README_zh.md
852|-- figures
853|  -- logic-view-of-modules-related-to-this-repository_zh.png
854|-- hal
855|  |-- BUILD.gn               #Camera驱动框架构建入口
856|  |-- adapter                 #平台适配层,适配平台
857|  |-- buffer_manager
858|  |-- camera.gni               #定义组件所使用的全局变量
859|  |-- device_manager
860|  |-- hdi_impl
861|  |-- include
862|  |-- init                   #demo sample
863|  |-- pipeline_core
864|  |-- test                   #测试代码
865|  |-- utils
866|-- hal_c                    #为海思平台提供专用C接口
867|  |-- BUILD.gn
868|  |-- camera.gni
869|  |-- hdi_cif
870|  |-- include
871|-- interfaces                  #HDI接口
872  |-- hdi_ipc
873|-- hdi_passthrough
874   |-- include
875```
876
877Camera hcs文件是每个chipset可配置的。所以放在chipset相关的仓下。以rk3568为例。仓名为: vendor_hihope,源码目录为:“vendor/hihope/rk3568/hdf_config/uhdf/camera”。
878
879    ├── hdi_impl
880    │   └── camera_host_config.hcs
881    └── pipeline_core
882        ├── config.hcs
883        ├── ipp_algo_config.hcs
884        └── params.hcs
885Camera chipset 相关代码路径以3568为例仓名为:device_hihope。路径为:device/board/hihope/rk3568/camera/
886```
887├── BUILD.gn
888├── demo
889│   └── include
890│       └── project_camera_demo.h
891├── device_manager
892│   ├── BUILD.gn
893│   ├── include
894│   │   ├── imx600.h
895│   │   ├── project_hardware.h
896│   │   └── rkispv5.h
897│   └── src
898│       ├── imx600.cpp
899│       └── rkispv5.cpp
900├── driver_adapter
901│   └── test
902│       ├── BUILD.gn
903│       ├── unittest
904│       │   ├── include
905│       │   │   └── utest_v4l2_dev.h
906│       │   └── src
907│       │       └── utest_v4l2_dev.cpp
908│       └── v4l2_test
909│           └── include
910│               └── project_v4l2_main.h
911└── pipeline_core
912    ├── BUILD.gn
913    └── src
914        ├── ipp_algo_example
915        │   └── ipp_algo_example.c
916        └── node
917            ├── rk_codec_node.cpp
918            └── rk_codec_node.h
919```
920  ####  Camera 驱动框架配置
921
922  RK3568 配置文件路径:
923
924vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs”。说明:其他平台可参考RK3568适配。
925
926```
927        hdi_server :: host {
928            hostName = "camera_host";
929            priority = 50;
930            caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];
931            camera_device :: device {
932                 device0 :: deviceNode {
933                     policy = 2;
934                     priority = 100;
935                     moduleName = "libcamera_hdi_impl.z.so";
936                     serviceName = "camera_service";
937                 }
938             }
939            ...
940        }
941```
942
943参数说明:
944       Host:一个host节点即为一个独立进程,如果需要独立进程,新增属于自己的host节点。
945       Policy: 服务发布策略,HDI服务请设置为“**2**”
946       moduleName: 驱动实现库名。
947       serviceName:服务名称,请保持全局唯一性。
948
949Camera_host驱动实现入口
950
951文件路径:drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp
952
953分发设备服务消息
954      cmd Id:请求消息命令字。
955      Data:其他服务或者IO请求数据。
956      Reply:存储返回消息内容数据。
957
958```
959static int32_t CameraServiceDispatch(struct HdfDeviceIoClient *client, int cmdId,
960    struct HdfSBuf *data, struct HdfSBuf *reply)
961{
962    HdfCameraService *hdfCameraService = CONTAINER_OF(client->device->service, HdfCameraService, ioservice);
963      return CameraHostServiceOnRemoteRequest(hdfCameraService->instance, cmdId, data, reply);
964 }
965```
966
967 绑定设备服务:初始化设备服务对象和资源对象。
968
969```
970int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject)
971{
972    HDF_LOGI("HdfCameraHostDriverBind enter!");
973    if (deviceObject == nullptr) {
974        HDF_LOGE("HdfCameraHostDriverBind: HdfDeviceObject is NULL !");
975        return HDF_FAILURE;
976}
977```
978
979驱动初始化函数: 探测并初始化驱动程序
980
981```
982int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject)
983{
984      return HDF_SUCCESS;
985}
986```
987
988驱动资源释放函数 : 如已经绑定的设备服务对象
989
990```
991  void HdfCameraHostDriverRelease(HdfDeviceObject *deviceObject)
992  {
993          if (deviceObject == nullptr || deviceObject->service == nullptr) {
994          HDF_LOGE("%{public}s deviceObject or deviceObject->service  is NULL!", __FUNCTION__);
995                return;
996      }
997          HdfCameraService *hdfCameraService = CONTAINER_OF(deviceObject->service, HdfCameraService, ioservice);
998      if (hdfCameraService == nullptr) {
999           HDF_LOGE("%{public}s hdfCameraService is NULL!", __FUNCTION__);
1000           return;
1001       }
1002```
1003
1004定义驱动描述符:将驱动代码注册给驱动框架。
1005
1006
1007     struct HdfDriverEntry g_cameraHostDriverEntry = {
1008          .moduleVersion = 1,
1009          .moduleName = "camera_service",
1010          .Bind = HdfCameraHostDriverBind,
1011          .Init = HdfCameraHostDriverInit,
1012          .Release = HdfCameraHostDriverRelease,
1013      };
1014
1015
1016####   Camera配置信息介绍
1017
1018Camera模块内部,所有配置文件使用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS解析接口解析HCB文件,获取配置文件中的信息。
1019
1020
1021     hc_gen("build_camera_host_config") {
1022        sources = [ rebase_path(
1023                      "$camera_product_name_path/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs") ]
1024      }
1025
1026      ohos_prebuilt_etc("camera_host_config.hcb") {
1027              deps = [ ":build_camera_host_config" ]
1028        hcs_outputs = get_target_outputs(":build_camera_host_config")
1029              source = hcs_outputs[0]
1030        relative_install_dir = "hdfconfig"
1031              install_images = [ chipset_base_dir ]
1032        subsystem_name = "hdf"
1033              part_name = "camera_device_driver"
1034      }
1035
1036### Camera适配介绍
1037
1038####   新产品平台适配简介
1039
1040
1041drivers/peripheral/camera/hal/camera.gni 文件中可根据编译时传入的product_company product_name和device_name调用不同chipset的product.gni
1042
1043      if (defined(ohos_lite)) {
1044              import("//build/lite/config/component/lite_component.gni")
1045        import(
1046                  "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
1047      } else {
1048              import("//build/ohos.gni")
1049        if ("${product_name}" == "ohos-arm64") {
1050                import(
1051              "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
1052              } else if ("${product_name}" == "Hi3516DV300") {
1053          import(
1054                    "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
1055        } else if ("${product_name}" == "watchos") {
1056                import(
1057              "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
1058              } else {
1059          import(
1060                    "//device/board/${product_company}/${device_name}/camera/product.gni")
1061        }
1062            }
1063
1064在如下路径的product.gni指定了编译不同chipset相关的代码的路径:
1065
1066```
1067 device/${product_company}/${device_name}/camera/
1068```
1069
1070如下是rk3568的product.gni1071
1072      camera_device_name_path = "//device/board/${product_company}/${device_name}"
1073            is_support_v4l2 = true
1074      if (is_support_v4l2) {
1075              is_support_mpi = false
1076        defines += [ "SUPPORT_V4L2" ]
1077              chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
1078        camera_device_manager_deps =
1079                  "$camera_device_name_path/camera/src/device_manager:camera_device_manager"
1080        camera_pipeline_core_deps =
1081                  "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
1082      }
1083
1084product.gni中指定了chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps 三个代码编译路径。该路径在drivers/peripheral/camera/hal/BUILD.gn中会被使用
1085
1086####  框架适配介绍
1087
1088        ![dayu200-camera-01.png](figures/dayu200/dayu200-camera-01.png)
10891090
1091以V4l2为例,pipeline的连接方式是在HCS配置文件中配置连接,数据源我们称之为SourceNode,主要包括硬件设备的控制、数据流的轮转等。
1092 ISPNode可根据需要确定是否添加此Node,因为在很多操作上其都可以和SensorNode统一为SourceNode。SinkNode为pipeline中数据传输的重点,到此处会将数据传输回buffer queue中。
1093
1094​    pipeline中的Node是硬件/软件模块的抽象,所以对于其中硬件模块Node,其是需要向下控制硬件模块的,在控制硬件模块前,需要先获取其对应硬件模块的deviceManager,通过deviceManager向下传输控制命令/数据buffer,所以deviceManager中有一个v4l2 device manager抽象模块,用来创建各个硬件设备的manager、controller.如上sensorManager、IspManager,sensorController等,所以v4l2 device manager其实是各个硬件设备总的一个管理者。
1095
1096deviceManager中的controller和驱动适配层直接交互。
1097
1098基于以上所描述,如需适配一款以linux v4l2框架的芯片平台,只需要修改适配如上图中颜色标记模块及HCS配置文件(如为标准v4l2框架,基本可以延用当前已适配代码),接下来单独介绍修改模块。
1099
1100主要适配添加如下目录:
1101
1102​     “vendor/hihope/rk3568/hdf_config/uhdf/camera/”:当前芯片产品的HCS配置文件目录。
1103
1104​     “device/hihope/rk3568/camera/”:当前芯片产品的代码适配目录。
1105
1106​     “drivers/peripheral/camera/hal/adapter/platform/v4l2”:平台通用公共代码。
1107
1108####  HCS配置文件适配介绍
1109
1110```
1111  ├── hdi_impl
1112  │   └── camera_host_config.hcs
1113  └── pipeline_core
1114      ├── config.hcs
1115      ├── ipp_algo_config.hcs
1116      └── params.hcs
1117```
1118
1119以RK3568开发板为例,其hcs文件应该放在对应的路径中。
1120
1121```
1122 vendor/${product_company}/${product_name}/ hdf_config/uhdf/camera/
1123```
1124
1125  ```
1126  template ability {
1127    logicCameraId = "lcam001";
1128    physicsCameraIds = [
1129    "CAMERA_FIRST",
1130    "CAMERA_SECOND"
1131    ];
1132  metadata {
1133     aeAvailableAntiBandingModes = [
1134         "OHOS_CONTROL_AE_ANTIBANDING_MODE_OFF",
1135         "OHOS_CONTROL_AE_ANTIBANDING_MODE_50HZ",
1136         "OHOS_CONTROL_AE_ANTIBANDING_MODE_60HZ",
1137         "OHOS_CONTROL_AE_ANTIBANDING_MODE_AUTO"
1138          ];
1139
1140  ```
1141
1142 hdi_impl下的“camera_host_config.hcs”为物理/逻辑Camera配置、能力配置,此处的物理/逻辑Camera配置,需要在hal内部使用,逻辑Camera及能力配置需要上报给上层,请按照所适配的芯片产品添加其能力配置。其中所用的能力值为键值对,定义在//drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h中。
1143
1144```
1145      normal_preview :: pipeline_spec {
1146      name = "normal_preview";
1147            v4l2_source :: node_spec {
1148          name = "v4l2_source#0";
1149                status = "new";
1150          out_port_0 :: port_spec {
1151                    name = "out0";
1152              peer_port_name = "in0";
1153                    peer_port_node_name = "sink#0";
1154              direction = 1;
1155                    width = 0;
1156              height = 0;
1157                    format = 0;
1158          }
1159            }
1160      sink :: node_spec {
1161                name = "sink#0";
1162          status = "new";
1163                stream_type = "preview";
1164          in_port_0 :: port_spec {
1165                    name = "in0";
1166              peer_port_name = "out0";
1167                    peer_port_node_name = "v4l2_source#0";
1168              direction = 0;
1169                }
1170      }
1171    }
1172```
1173
1174 pipeline_core下的“config.hcs”为pipeline的连接方式,按场景划分每一路流由哪些Node组成,其连接方式是怎样的。
1175
1176上面为preview场景的示例,normal_preview为该场景的名称,source和sink为Node,source为数据数据源端,sink为末端,source为第一个node,node的名称是source#0,status、in/out_port分别为Node状态及输入/输出口的配置。
1177
1178 以in_port_0为例,name = “in0”代表它的输入为“port0”,它的对端为source node的port口out0口,direction为它的源Node和对端Node是否为直连方式。如新添加芯片产品,必须按实际连接方式配置此文件。
1179
1180新增功能node时需继承NodeBase类,且在cpp文件中注册该node。具体可参考//drivers/peripheral/camera/hal/pipeline_core/nodes/src下已经实现的node。
1181
1182
1183     root {
1184      module = "";
1185            template stream_info {
1186          id = 0;
1187                name = "";
1188      }
1189            template scene_info {
1190          id = 0;
1191                name = "";
1192      }
1193            preview :: stream_info {
1194          id = 0;
1195                name = "preview";
1196      }
1197            video :: stream_info {
1198          id = 1;
1199                name = "video";
1200      }
1201
1202
1203param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处需要添加定义。
1204
1205####  Chipset 和Platform适配介绍
1206
1207platform为平台性公共代码,如linux标准v4l2适配接口定义,为v4l2框架适配的通用node.以及为v4l2框架适配的通用device_manager等。目录结构如下:
1208
1209      drivers/peripheral/camera/hal/adapter/platform
1210      ├── mpp
1211      │   └── src
1212      │       ├── device_manager
1213      │       └── pipeline_core
1214      └── v4l2
1215          └── src
1216              ├── device_manager
1217              ├── driver_adapter
1218              └── pipeline_core
1219
1220“platform”目录下的“v4l2”包含了“src”, “src”中“driver_adapter”为linux v4l2标准适配接口,如有定制化功能需求,可继承driver_adapter,将定制化的具体功能接口放在chipset中实现。如无芯片定制化功能,可直接使用已有的driver_adapter。
1221
1222platform目录下的Nodes为依据linux v4l2标准实现的硬件模块v4l2_source_node和uvc_node(usb热插拔设备,此模块也为linux标准接口,可直接使用),如下图为v4l2_source_node的接口声明头文件。
1223
1224
1225     namespace OHOS::Camera {
1226      class V4L2SourceNode : public SourceNode {
1227            public:
1228          V4L2SourceNode(const std::string& name, const std::string& type);
1229                ~V4L2SourceNode() override;
1230          RetCode Init(const int32_t streamId) override;
1231                RetCode Start(const int32_t streamId) override;
1232          RetCode Flush(const int32_t streamId) override;
1233                RetCode Stop(const int32_t streamId) override;
1234          RetCode GetDeviceController();
1235                void SetBufferCallback() override;
1236          RetCode ProvideBuffers(std::shared_ptr<FrameSpec> frameSpec) override;
1237
1238      private:
1239                std::mutex                              requestLock_;
1240          std::map<int32_t, std::list<int32_t>>   captureRequests_ = {};
1241                std::shared_ptr<SensorController>       sensorController_ = nullptr;
1242          std::shared_ptr<IDeviceManager>     deviceManager_ = nullptr;
1243            };
1244      } // namespace OHOS::Camera
1245
1246 Init接口为模块初始化接口。
1247
1248Start为使能接口,比如start stream功能等。
1249
1250 Stop为停止接口。
1251
1252 GetDeviceController为获取deviceManager对应的controller接口。
1253
1254chipset为具体某芯片平台相关代码,例如,如和“rk3568”开发板 为例。device_manager目录下可存放该开发板适配过的sensor的相关配置文件。pipeline_core路径下可以存放由chipset开发者为满足特点需求增加的pipeline node等。
1255
1256```
1257 device/board/hihope/rk3568/camera
1258  ├── BUILD.gn
1259  ├── camera_demo
1260  │   └── project_camera_demo.h
1261  ├── include
1262  │   └── device_manager
1263  ├── product.gni
1264  └── src
1265      ├── device_manager
1266      ├── driver_adapter
1267      └── pipeline_core
1268```
1269
1270device/board/hihope/rk3568/camera/目录包含了“include”和“src”,“camera_demo”“src”中“device­­_manager”中包含了chipset 适配的sensor的文件,配合platform下device_manager的设备管理目录,主要对接pipeline,实现平台特有的硬件处理接口及数据buffer的下发和上报、metadata的交互。
1271
1272下图为device_manager的实现框图,pipeline控制管理各个硬件模块,首先要获取对应设备的manager,通过manager获取其对应的controller,controller和对应的驱动进行交互 。
1273
1274 ![img](figures/dayu200/dayu200-camera-02.png)
1275
1276deviceManager中需要实现关键接口介绍。
1277
1278```
1279      class SensorController : public IController {
1280      public:
1281          SensorController();
1282          explicit SensorController(std::string hardwareName);
1283          virtual ~SensorController();
1284          RetCode Init();
1285          RetCode PowerUp();
1286          RetCode PowerDown();
1287          RetCode Configure(std::shared_ptr<CameraStandard::CameraMetadata> meta);
1288          RetCode Start(int buffCont, DeviceFormat& format);
1289          RetCode Stop();
1290          RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer);
1291          void SetNodeCallBack(const NodeBufferCb cb);
1292          void SetMetaDataCallBack(const MetaDataCb cb);
1293          void BufferCallback(std::shared_ptr<FrameSpec> buffer);
1294          void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);
1295    }
1296```
1297
1298   PowerUp为上电接口,OpenCamera时调用此接口进行设备上电操作。
1299   PowerDown为下电接口,CloseCamera时调用此接口进行设备下电操作。
1300   Configures为Metadata下发接口,如需设置metadata参数到硬件设备,可实现此接口进行解析及下发。
1301   Start为硬件模块使能接口,pipeline中的各个node进行使能的时候,会去调用,可根据需要定义实现,比如sensor的起流操作就可放在此处进行实现。
1302   Stop和Start为相反操作,可实现停流操作。
1303   SendFrameBuffer为每一帧buffer下发接口,所有和驱动进行buffer交互的操作,都是通过此接口进行的。
1304   SetNodeCallBack为pipeline,通过此接口将buffer回调函数设置到devicemanager。
1305   SetMetaDataCallBack为metadata回调接口,通过此接口将从底层获取的metadata数据上报给上层。
1306   BufferCallback上传每一帧已填充数据buffer的接口,通过此接口将buffer上报给pipeline。
1307   SetAbilityMetaDataTag设置需要从底层获取哪些类型的metadata数据,因为框架支持单独获取某一类型或多类型的硬件设备信息,所以可以通过此接口,获取想要的metadata数据。
1308
1309  其余接口可参考“drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/”
1310
1311#### IPP适配介绍
1312
1313IPP是pipeline 中的一个算法插件模块,由ippnode加载,对流数据进行算法处理,ippnode支持同时多路数据输入,只支持一路数据输出。ippnode加载算法插件通过如下hcs文件指定:
1314vendor/${product_company}/${product_name}/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs 其中:
1315
1316```
1317  root {
1318     module="sample";
1319     ipp_algo_config {
1320     algo1 {
1321          name = "example";
1322          description = "example algorithm";
1323          path = "libcamera_ipp_algo_example.z.so";
1324          mode = "IPP_ALGO_MODE_NORMAL";
1325     }
1326     }
1327  }
1328
1329```
1330
1331  name:算法插件名称
1332  description:描述算法插件的功能
1333  path:算法插件所在路径
1334  mode:算法插件所运行的模式
1335
1336算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据需要进行扩展。
1337
1338```
1339  enum IppAlgoMode {
1340      IPP_ALGO_MODE_BEGIN,
1341      IPP_ALGO_MODE_NORMAL = IPP_ALGO_MODE_BEGIN,
1342      IPP_ALGO_MODE_BEAUTY,
1343      IPP_ALGO_MODE_HDR,
1344      IPP_ALGO_MODE_END
1345  };
1346```
1347
1348算法插件由gn文件 device/${product_company}/${device_name}/camera/BUILD.gn进行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用:
1349
1350      typedef struct IppAlgoFunc {
1351          int (*Init)(IppAlgoMeta* meta);
1352          int (*Start)();
1353          int (*Flush)();
1354          int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta);
1355          int (*Stop)();
1356      } IppAlgoFunc;
1357
1358  1) Init : 算法插件初始化接口,在起流前被ippnode 调用,其中IppAlgoMeta 定义在ipp_algo.h 中,为ippnode和算法插件提供非图像数据的传递通道,如当前运行的场景,算法处理后输出的人脸坐标等等,可根据实际需求进行扩展。
1359  2) Start:开始接口,起流时被ippnode 调用
1360  3) Flush:刷新数据的接口,停流之前被ippnode 调用。此接口被调用时,算法插件需尽可能快地停止处理。
1361  4) Process: 数据处理接口,每帧数据都通过此接口输入至算法插件进行处理。inBuffer是一组输入buffer,inBufferCount是输入buffer 的个数,outBuffer是输出buffer,meta是算法处理时产生的非图像数据,IppAlgoBuffer在ipp_algo.h中定义
1362  5) Stop:停止处理接口,停流时被ippnode调用
1363
1364
1365```
1366typedef struct IppAlgoBuffer {
1367      void* addr;
1368      unsigned int width;
1369      unsigned int height;
1370      unsigned int stride;
1371      unsigned int size;
1372      int id;
1373  } IppAlgoBuffer;
1374```
1375
1376其中上边代码中的id指的是和ippnode对应的port口id,比如inBuffer[0]的id为0,则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空,此时其中一个输入buffer 被ippnode作为输出buffer传递到下个node,inBuffer至少有一个buffer不为空。输入输出buffer 由pipeline配置决定。
1377比如在普通预览场景无算法处理且只有一路拍照数据传递到ippnode的情况下,输入buffer只有一个,输出buffer为空,即对于算法插件输入buffer 进行了透传;
1378比如算法插件进行两路预览图像数据进行合并的场景,第一路buffer需要预览送显示。把第二路图像拷贝到第一路的buffer即可,此时输入buffer有两个,输出buffer为空;
1379比如在算法插件中进行预览数据格式转换的场景,yuv转换为RGBA,那么只有一个yuv格式的输入buffer的情况下无法完成RGBA格式buffer的输出,此时需要一个新的buffer,那么ippnode的输出port口buffer作为outBuffer传递到算法插件。也即输入buffer只有一个,输出buffer也有一个。
1380
1381ippnode的port口配置请查看3.3小节的config.hcs的说明。
1382
1383#### 适配V4L2驱动实例
1384
1385本章节目的是在v4l2框架下适配RK3568开发板。
1386
1387 区分V4L2 platform相关代码并将其放置“drivers/peripheral/camera/hal/adapter/platform/v4l2”目录下,该目录中包含了“device_manager”“driver_adapter”和“pipeline_core”三个目录。其中“driver_adapter”目录中存放着v4l2协议相关代码。可通过它们实现与v4l2底层驱动交互。该目录下“Pipeline_core”目录与“drivers/peripheral/camera/hal/pipeline_core”中代码组合为pipeline框架。v4l2_source_node 和 uvc_node为v4l2专用Node。device_manager目录存放着向北与pipeline向南与v4l2 adapter交互的代码
1388
1389```
1390  drivers/peripheral/camera/hal/adapter/platform/v4l2/src/
1391  ├── device_manager
1392  │   ├── enumerator_manager.cpp
1393  │   ├── flash_controller.cpp
1394  │   ├── flash_manager.cpp
1395  │   ├── idevice_manager.cpp
1396  │   ├── include
1397  │   ├── isp_controller.cpp
1398  │   ├── isp_manager.cpp
1399  │   ├── sensor_controller.cpp
1400  │   ├── sensor_manager.cpp
1401  │   └── v4l2_device_manager.cpp
1402  ├── driver_adapter
1403  │   ├── BUILD.gn
1404  │   ├── include
1405  │   ├── main_test
1406  │   └── src
1407  └── pipeline_core
1408      └── nodes
1409```
1410
1411 区分V4L2 chipset相关代码并将其放置在“device/ ${product_company}/${device_name} /camera”目录下。
1412
1413```
1414  ├── BUILD.gn
1415  ├── camera_demo
1416  │   └── project_camera_demo.h
1417  ├── include
1418  │   └── device_manager
1419  ├── product.gni
1420  └── src
1421      ├── device_manager
1422      ├── driver_adapter
1423      └── pipeline_core
1424```
1425
1426其中“driver_adapter”目录中包含了关于RK3568 driver adapter的测试用例头文件。Camera_demo目录存放了camera hal 中demo测试用例的chipset相关的头文件。device_manager存放了RK3568适配的camera sensor 读取设备能力的代码 其中,project_hardware.h 比较关键,存放了device_manager支持当前chipset的设备列表。如下:
1427
1428```
1429 namespace OHOS::Camera {
1430    std::vector<HardwareConfiguration> hardware = {
1431        {CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "rkisp_v5"},
1432        {CAMERA_FIRST, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
1433        {CAMERA_FIRST, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"},
1434        {CAMERA_SECOND, DM_M_SENSOR, DM_C_SENSOR, (std::string) "Imx600"},
1435        {CAMERA_SECOND, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
1436        {CAMERA_SECOND, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"}
1437   };
1438  } // namespace OHOS::Camera
1439```
1440
1441修改编译选项来达到根据不同的编译chipset来区分v4l2和其他框架代码编译。增加device/${product_company}/${device_name}/camera/product.gni
1442
1443```
1444  camera_product_name_path = "//vendor/${product_company}/${product_name}"
1445  camera_device_name_path = "//device/board/${product_company}/${device_name}"
1446  is_support_v4l2 = true
1447  if (is_support_v4l2) {
1448      is_support_mpi = false
1449      defines += [ "SUPPORT_V4L2" ]
1450      chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
1451      camera_device_manager_deps =
1452          "$camera_device_name_path/camera/src/device_manager:camera_device_manager"
1453      camera_pipeline_core_deps =
1454          "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
1455  }
1456```
1457
1458当“product.gni”被// drivers/peripheral/camera/hal/camera.gni加载,就说明要编译v4l2相关代码。在//drivers/peripheral/camera/hal/camera.gni中根据编译时传入的product_name和device_name名来加载相应的gni文件。
1459
1460  ```
1461    import("//build/ohos.gni")
1462    if ("${product_name}" == "ohos-arm64") {
1463      import(
1464          "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
1465    } else if ("${product_name}" == "Hi3516DV300") {
1466      import(
1467          "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
1468  ```
1469
1470drivers/peripheral/camera/hal/BUILD.gn”中会根据 chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps来编译不同的chipset
1471
1472         print("product_name : , ${product_name}")
1473          group("camera_hal") {
1474            if (is_standard_system) {
1475              deps = [
1476                "$camera_path/../interfaces/hdi_ipc/client:libcamera_client",
1477                "buffer_manager:camera_buffer_manager",
1478                "device_manager:camera_device_manager",
1479                "hdi_impl:camera_hdi_impl",
1480                "init:ohos_camera_demo",
1481                "pipeline_core:camera_pipeline_core",
1482                "utils:camera_utils",
1483              ]
1484              deps += [ "${chipset_build_deps}" ]
1485            }
1486
1487
1488Camera hal层向下屏蔽了平台及芯片差异,对外(Camera service或者测试程序)提供统一接口,其接口定义在“drivers/peripheral/camera/interfaces/include”目录下:
1489
1490            ├── icamera_device_callback.h
1491            ├── icamera_device.h
1492            ├── icamera_host_callback.h
1493            ├── icamera_host.h
1494            ├── ioffline_stream_operator.h
1495            ├── istream_operator_callback.h
1496            ├── istream_operator.h
1497
1498测试时,只需要针对所提供的对外接口进行测试,即可完整测试Camera hal层代码,具体接口说明,可参考“drivers/peripheral/camera/interfaces”目录下的“README_zh.md”和头文件接口定义。具体的调用流程,可参考测试demo:drivers/peripheral/camera/hal/init1499
1500###  camera适配过程中问题以及解决方案
1501
1502#### 修改SUBWINDOW_TYPE和送显format
1503
1504修改RGBA888送显,模式由video 改为 SUBWINDOW_TYPE为normal模式:
1505
1506由于openharmony 较早实现的是3516平台camera, 该平台采用PIXEL_FMT_YCRCB_420_SP格式送显,而RK3568需将预览流由yuv420转换为PIXEL_FMT_RGBA_8888送上屏幕才可被正确的显示。具体需修改foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp 文件中如下内容,该文件被编译在libace.z.so1507
1508
1509      #ifdef PRODUCT_RK
1510          previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_RGBA_8888));
1511          previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
1512                                       std::to_string(OHOS_CAMERA_FORMAT_RGBA_8888));
1513      #else
1514          previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_YCRCB_420_SP));
1515          previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
1516                                       std::to_string(OHOS_CAMERA_FORMAT_YCRCB_420_SP));
1517      #endif
1518
1519foundation/multimedia/camera_standard/services/camera_service/src/hstream_repeat.cpp 文件中如下内容,该文件被编译在libcamera_service.z.so1520
1521```
1522void HStreamRepeat::SetStreamInfo(std::shared_ptr<Camera::StreamInfo> streamInfo)
1523    {
1524        int32_t pixelFormat;
1525        auto it = g_cameraToPixelFormat.find(format_);
1526        if (it != g_cameraToPixelFormat.end()) {
1527            pixelFormat = it->second;
1528        } else {
1529    #ifdef RK_CAMERA
1530            pixelFormat = PIXEL_FMT_RGBA_8888;
1531    #else
1532            pixelFormat = PIXEL_FMT_YCRCB_420_SP;
1533    #endif
1534```
1535
1536如上3516平台是使用VO通过VO模块驱动直接送显,所以在ace中配置的subwindows模式为SUBWINDOW_TYPE_VIDEO. 需在foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp文件中做如下修改,该文件被编译在libace.z.so1537
1538      #ifdef PRODUCT_RK
1539          option->SetWindowType(SUBWINDOW_TYPE_NORMAL);
1540      #else
1541          option->SetWindowType(SUBWINDOW_TYPE_VIDEO);
1542      #endif
1543
1544#### 增加rk_codec_node
1545
1546在该node中完成rgb转换,jpeg和h264压缩编解码前文讲过camera hal的pipeline模型的每一个node都是camera数据轮转过程中的一个节点,由于当前camera hal v4l2 adapter只支持一路流进行数据轮转,那么拍照和录像流就必须从单一的预览流中拷贝。现阶段openharmony也没有专门的服务端去做codec和rgb转换jpeg压缩的工作。那么只能在camera hal中开辟一个专有node去做这些事情,也就是rk_codec_node。
1547Hcs中增加rk_codec_node连接模型:
1548修改vendor/hihope/rk3568/hdf_config/uhdf/camera/pipeline_core/config.hcs文件
1549
1550
1551              normal_preview_snapshot :: pipeline_spec {
1552                    name = "normal_preview_snapshot";
1553                    v4l2_source :: node_spec {
1554                        name = "v4l2_source#0";
1555                        status = "new";
1556                        out_port_0 :: port_spec {
1557                            name = "out0";
1558                            peer_port_name = "in0";
1559                            peer_port_node_name = "fork#0";
1560                            direction = 1;
1561                        }
1562                    }
1563                    fork :: node_spec {
1564                        name = "fork#0";
1565                        status = "new";
1566                        in_port_0 :: port_spec {
1567                            name = "in0";
1568                            peer_port_name = "out0";
1569                            peer_port_node_name = "v4l2_source#0";
1570                            direction = 0;
1571                        }
1572                        out_port_0 :: port_spec {
1573                            name = "out0";
1574                            peer_port_name = "in0";
1575                            peer_port_node_name = "RKCodec#0";
1576                            direction = 1;
1577                        }
1578                        out_port_1 :: port_spec {
1579                            name = "out1";
1580                            peer_port_name = "in0";
1581                            peer_port_node_name = "RKCodec#1";
1582                            direction = 1;
1583                        }
1584                    }
1585                    RKCodec_1 :: node_spec {
1586                        name = "RKCodec#0";
1587                        status = "new";
1588                        in_port_0 :: port_spec {
1589                            name = "in0";
1590                            peer_port_name = "out0";
1591                            peer_port_node_name = "fork#0";
1592                            direction = 0;
1593                        }
1594                        out_port_0 :: port_spec {
1595                            name = "out0";
1596                            peer_port_name = "in0";
1597                            peer_port_node_name = "sink#0";
1598                            direction = 1;
1599                        }
1600                    }
1601                    RKCodec_2 :: node_spec {
1602                        name = "RKCodec#1";
1603
1604
1605以预览加拍照双路流为列,v4l2_source_node为数据源,流向了fork_node,rork_node将预览数据直接送给RKCodec node, 将拍照数据流拷贝一份也送给RKCodec node进行转换。转换完成的数据将送给sink node后交至buffer的消费端。
1606
1607device/board/hihope/rk3568/camera/src/pipeline_core/BUILD.gn中添加rk_codec_node.cpp和相关依赖库的编译。其中librga为yuv到rgb格式转换库,libmpp为yuv到H264编解码库,libjpeg为yuv到jpeg照片的压缩库。
1608
1609
1610        ohos_shared_library("camera_pipeline_core") {
1611            sources = [
1612              "$camera_device_name_path/camera/src/pipeline_core/node/rk_codec_node.cpp",
1613              "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/uvc_node/uvc_node.cpp",
1614                      "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/v4l2_source_node/v4l2_source_node.cpp",
1615               deps = [
1616                "$camera_path/buffer_manager:camera_buffer_manager",
1617                "$camera_path/device_manager:camera_device_manager",
1618                "//device/soc/rockchip/hardware/mpp:libmpp",
1619                "//device/soc/rockchip/hardware/rga:librga",
1620                "//foundation/multimedia/camera_standard/frameworks/native/metadata:metadata",
1621                "//third_party/libjpeg:libjpeg_static",
1622
1623
1624 openharmony/device/board/hihope/rk3568/camera/src/pipeline_core/node/rk_codec_node.cpp主要接口:
1625
1626
1627       void RKCodecNode::DeliverBuffer(std::shared_ptr<IBuffer>& buffer)
1628        {
1629            if (buffer == nullptr) {
1630                CAMERA_LOGE("RKCodecNode::DeliverBuffer frameSpec is null");
1631                return;
1632            }
1633
1634            int32_t id = buffer->GetStreamId();
1635            CAMERA_LOGE("RKCodecNode::DeliverBuffer StreamId %{public}d", id);
1636            if (buffer->GetEncodeType() == ENCODE_TYPE_JPEG) {
1637                Yuv420ToJpeg(buffer);
1638            } else if (buffer->GetEncodeType() == ENCODE_TYPE_H264) {
1639                Yuv420ToH264(buffer);
1640            } else {
1641                Yuv420ToRGBA8888(buffer);
1642            }
1643
1644由fork_node出来的数据流将会被deliver到rk_codec_node的DeliverBuffer接口中,该接口会根据不同的EncodeType去做不同的转换处理。经过转换过的buffers再deliver到下一级node中处理。直到deliver到buffer消费者手中。
1645
1646####  H264帧时间戳和音频时间戳不同步问题。
1647
1648问题点:Ace在CreateRecorder时会同时获取音频和视频数据并将他们合成为.mp4文件。但在实际合成过程当中需要检查音视频信息中的时间戳是否一致,如不一致将会Recorder失败。表现出的现象是camera app点击录像按钮后无法正常停止,强行停止后发现mp4文件为空。
1649
1650解决方法:首先需找到audio模块对于音频时间戳的获取方式。
1651
1652```
1653   int32_t AudioCaptureAsImpl::GetSegmentInfo(uint64_t &start)
1654    {
1655        CHECK_AND_RETURN_RET(audioCapturer_ != nullptr, MSERR_INVALID_OPERATION);
1656        AudioStandard::Timestamp timeStamp;
1657        auto timestampBase = AudioStandard::Timestamp::Timestampbase::MONOTONIC;
1658        CHECK_AND_RETURN_RET(audioCapturer_->GetAudioTime(timeStamp, timestampBase), MSERR_UNKNOWN);
1659        CHECK_AND_RETURN_RET(timeStamp.time.tv_nsec >= 0 && timeStamp.time.tv_sec >= 0, MSERR_UNKNOWN);
1660        if (((UINT64_MAX - timeStamp.time.tv_nsec) / SEC_TO_NANOSECOND) <= static_cast<uint64_t>(timeStamp.time.tv_sec)) {
1661            MEDIA_LOGW("audio frame pts too long, this shouldn't happen");
1662        }
1663        start = timeStamp.time.tv_nsec + timeStamp.time.tv_sec * SEC_TO_NANOSECOND;
1664        MEDIA_LOGI("timestamp from audioCapturer: %{public}" PRIu64 "", start);
1665        return MSERR_OK;
1666    }
1667```
1668
1669可以看到,audio_capture_as_impl.cpp 文件中。audio模块用的是CLOCK_MONOTONIC,即系统启动时开始计时的相对时间。而camera 模块使用的是CLOCK_REALTIME,即系统实时时间。
1670
1671
1672                mppStatus_ = 1;
1673                buf_size = ((MpiEncTestData *)halCtx_)->frame_size;
1674
1675                ret = hal_mpp_encode(halCtx_, dma_fd, (unsigned char *)buffer->GetVirAddress(), &buf_size);
1676                SearchIFps((unsigned char *)buffer->GetVirAddress(), buf_size, buffer);
1677
1678                buffer->SetEsFrameSize(buf_size);
1679                clock_gettime(CLOCK_MONOTONIC, &ts);
1680                timestamp = ts.tv_nsec + ts.tv_sec * TIME_CONVERSION_NS_S;
1681                buffer->SetEsTimestamp(timestamp);
1682                CAMERA_LOGI("RKCodecNode::Yuv420ToH264 video capture on\n");
1683
1684 解决方法:修改camera hal中rk_codec_node.cpp中的获取时间类型为CLOCK_MONOTONIC即可解决问题。
1685
1686####  time_t改为64位以后匹配4.19 kernel问题。
1687
1688背景介绍:RK3568在遇到这个问题时的环境是上层运行的32位系统,底层是linux4.19  64位kernel。在32位系统环境下time_t这个typedef是long类型的,也就是32位。但在下面这个提交中将time_t 改成_Int64位。这样就会导致camera v4l2在ioctl时发生错误。
1689
1690      TYPEDEF _Int64 time_t;
1691      TYPEDEF _Int64 suseconds_t;
1692
1693  具体错误以及临时修改方案:
1694
1695  1,发生错误时在hilog中搜索camera_host 会发现在V4L2AllocBuffer接口中下发VIDIOC_QUERYBUF的CMD时上报了一个Not a tty的错误。如下:
1696
1697```
1698V4L2AllocBuffer error:ioctl VIDIOC_QUERYBUF failed: Not a tty
1699
1700```
1701
1702    RetCode HosV4L2Buffers::V4L2AllocBuffer(int fd, const std::shared_ptr<FrameSpec>& frameSpec)
1703    {
1704        struct v4l2_buffer buf = {};
1705        struct v4l2_plane planes[1] = {};
1706        CAMERA_LOGD("V4L2AllocBuffer\n");
1707
1708        if (frameSpec == nullptr) {
1709            CAMERA_LOGE("V4L2AllocBuffer frameSpec is NULL\n");
1710            return RC_ERROR;
1711        }
1712
1713        switch (memoryType_) {
1714            case V4L2_MEMORY_MMAP:
1715                // to do something
1716                break;
1717            case V4L2_MEMORY_USERPTR:
1718                buf.type = bufferType_;
1719                buf.memory = memoryType_;
1720                buf.index = (uint32_t)frameSpec->buffer_->GetIndex();
1721
1722                if (bufferType_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
1723                    buf.m.planes = planes;
1724                    buf.length = 1;
1725                }
1726                CAMERA_LOGD("V4L2_MEMORY_USERPTR Print the cnt: %{public}d\n", buf.index);
1727
1728                if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
1729                    CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno));
1730                    return RC_ERROR;
1731 2,我们知道,一般ioctl系统调用的CMD都是以第三个参数的sizeof为CMD值主要组成传递进内核去寻找内核中相对应的switch case. 如下图,v4l2_buffer为VIDIOC_QUERYBUF宏的值得主要组成部分,那么v4l2_buffer的size发生变化,VIDIOC_QUERYBUF的值也会发生变化。
1732
1733```
1734  #define VIDIOC_S_FMT        _IOWR('V',  5, struct v4l2_format)
1735  #define VIDIOC_REQBUFS      _IOWR('V',  8, struct v4l2_requestbuffers)
1736  #define VIDIOC_QUERYBUF     _IOWR('V',  9, struct v4l2_buffer)
1737  #define VIDIOC_G_FBUF        _IOR('V', 10, struct v4l2_framebuffer)
1738```
1739
1740  3,当kernel 打开CONFIG_COMPAT这个宏时,可以实现32位系统到64位kernel的兼容,对于32位系统下发的ioctl会先进入下面截图中的接口里去做cmd值由32到64位的转换。
1741
1742      long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
1743      {
1744          struct video_device *vdev = video_devdata(file);
1745          long ret = -ENOIOCTLCMD;
1746
1747          if (!file->f_op->unlocked_ioctl)
1748              return ret;
1749
1750          if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
1751              ret = do_video_ioctl(file, cmd, arg);
1752          else if (vdev->fops->compat_ioctl32)
1753              ret = vdev->fops->compat_ioctl32(file, cmd, arg);
1754
1755 4,那么在kernel中会定义一个kernel认为的VIDIOC_QUERYBUF的值。
1756
1757        #define VIDIOC_S_FMT32      _IOWR('V',  5, struct v4l2_format32)
1758        #define VIDIOC_QUERYBUF32   _IOWR('V',  9, struct v4l2_buffer32)
1759        #define VIDIOC_QUERYBUF32_TIME32 _IOWR('V',  9, struct v4l2_buffer32_time32)
17605,前文提到过,上层musl中time_t已经由32位被改为64位,v4l2_buffer结构体中的struct timeval中就用到了time_t。那么应用层的v4l2_buffer的大小就会跟kernel层的不一致,因为kernel的struct timeval 中编译时使用的是kernel自己在time.h中定义的 kernel_time_t。这就导致应用和驱动层对于v4l2_buffer的sizeof计算不一致从而调用到内核态后找不到cmd的错误。
1761
1762```
1763   struct v4l2_buffer {
1764            __u32           index;
1765             __u32           type;
1766            __u32           bytesused;
1767            __u32           flags;
1768            __u32           field;
1769            struct timeval      timestamp;
1770            struct v4l2_timecode    timecode;
1771            __u32           sequence;
1772```
17736,临时解决方案是修改videodev2.h中的struct timeval为自己临时定义的结构体, 保证上下层size一致。如下:
1774
1775```
1776            struct timeval1 {
1777                long tv_sec;
1778                long tv_usec;
1779            }
1780            struct v4l2_buffer {
1781                __u32           index;
1782                __u32           type;
1783                __u32           bytesused;
1784                __u32           flags;
1785                __u32           field;
1786                struct timeval1      timestamp;
1787                struct v4l2_timecode    timecode;
1788```
1789
1790 根本解决方案:
1791
1792 如需要根本解决这个问题,只有两种方法。第一将系统升级为64位系统,保证用户态和内核态对于time_t变量的size保持一致。第二,升级5.10之后版本的kernel
1793 因为5.10版本的kernel在videodev2.h文件中解决了这个情况。目前我们已在5.10的kernel上验证成功,如下图,可以看到在编译kernel时考虑到了64位time_t的问题。
1794
1795```
1796struct v4l2_buffer {
1797            __u32           index;
1798            __u32           type;
1799            __u32           bytesused;
1800            __u32           flags;
1801            __u32           field;
1802        #ifdef __KERNEL__
1803            struct __kernel_v4l2_timeval timestamp;
1804        #else
1805            struct timeval      timestamp;
1806        #endif
1807            struct v4l2_timecode    timecode;
1808 }
1809
1810 struct __kernel_v4l2_timeval {
1811      long long   ._sec;
1812  #if defined(__sparc__) && defined(__arch64__)
1813      int     tv_usec;
1814      int     __pad;
1815  #else
1816      long long   tv_usec;
1817  #endif
1818  };
1819```
1820####  H264 关键帧获取上报
1821
1822 H264除了需要上报经过编解码的数据外,还需上报关键帧信息。即这一帧是否为关键帧?mp4编码时需要用到这些信息,那么怎么分析那一帧是关键帧那?主要是分析NALU头信息。Nalu type & 0x1f就代表该帧的类型。Nalu头是以0x00000001或0x000001为起始标志的。  该图为nal_unit_type为不同数值时的帧类型。我们主要关心type为5也就是IDR帧信息。
1823         ![1647875911244](figures/dayu200/dayu200-camera-03.png)
1824
1825
1826  rk_cedec_node.cpp文件里对IDR帧分析进行了代码化:
1827
1828        static constexpr uint32_t nalBit = 0x1F;
1829        #define NAL_TYPE(value)             ((value) & nalBit)
1830        void RKCodecNode::SearchIFps(unsigned char* buf, size_t bufSize, std::shared_ptr<IBuffer>& buffer)
1831        {
1832            size_t nalType = 0;
1833            size_t idx = 0;
1834            size_t size = bufSize;
1835            constexpr uint32_t nalTypeValue = 0x05;
1836
1837            if (buffer == nullptr || buf == nullptr) {
1838                CAMERA_LOGI("RKCodecNode::SearchIFps parameter == nullptr");
1839                return;
1840            }
1841
1842            for (int i = 0; i < bufSize; i++) {
1843                int ret = findStartCode(buf + idx, size);
1844                if (ret == -1) {
1845                    idx += 1;
1846                    size -= 1;
1847                } else {
1848                    nalType = NAL_TYPE(buf[idx + ret]);
1849                    CAMERA_LOGI("ForkNode::ForkBuffers nalu == 0x%{public}x buf == 0x%{public}x \n", nalType, buf[idx + ret]);
1850
1851每经过一个h264转换过的buffer都会被传入SearchIFps接口中寻找IDR帧。其中findStartCode()接口会对buffer中的内容逐个字节扫描,知道寻找出NALU头来
1852
1853  ```
1854     int RKCodecNode::findStartCode(unsigned char *data, size_t dataSz)
1855        {
1856            constexpr uint32_t dataSize = 4;
1857            constexpr uint32_t dataBit2 = 2;
1858            constexpr uint32_t dataBit3 = 3;
1859
1860            if (data == nullptr) {
1861                CAMERA_LOGI("RKCodecNode::findStartCode parameter == nullptr");
1862                return -1;
1863            }
1864
1865            if ((dataSz > dataSize) && (data[0] == 0) && (data[1] == 0) && \
1866                (data[dataBit2] == 0) && (data[dataBit3] == 1)) {
1867                return 4; // 4:start node
1868            }
1869
1870            return -1;
1871        }
1872  ```
1873当找到NALU头后就会对&0x1F 找出nal_unit_type,如果type为5标记关键帧信息并通过buffer->SetEsKeyFrame(1);接口上报。
1874
1875##     TP
1876
1877### TP驱动模型
1878
1879主要包含Input模块HDI(Hardware Driver Interface)接口定义及其实现,对上层输入服务提供操作input设备的驱动能力接口,HDI接口主要包括如下三大类:
1880
1881- InputManager:管理输入设备,包括输入设备的打开、关闭、设备列表信息获取等;
1882- InputReporter:负责输入事件的上报,包括注册、注销数据上报回调函数等;
1883- InputController:提供input设备的业务控制接口,包括获取器件信息及设备类型、设置电源状态等。
1884
1885**图 1** INPUT模块HDI接口层框架图
1886
1887![dayu200-tp-01.png](figures/dayu200/dayu200-tp-01.png)
1888
1889相关目录下源代码目录结构如下所示
1890
1891```
1892/drivers/peripheral/input
1893├── hal                # input模块的hal层代码
1894│   └── include       # input模块hal层内部的头文件
1895│   └── src           # input模块hal层代码的具体实现
1896├── interfaces         # input模块对上层服务提供的驱动能力接口
1897│   └── include       # input模块对外提供的接口定义
1898├── test               # input模块的测试代码
1899│   └── unittest      # input模块的单元测试代码
1900```
1901
1902详细请参考input子系统[README](https://gitee.com/openharmony/drivers_peripheral/blob/master/input/README_zh.md)
1903
1904### TP HDF驱动适配
1905
1906#### TP驱动涉及的文件及目录
1907
1908dayu200平台默认支持GT5688这颗TP IC。
1909
1910开发板移植touch驱动涉及的文件及目录:
1911
19121、 Makefile文件: drivers\adapter\khdf\linux\model\input\Makefile
1913
19142、 vendor\hihope\rk3568\hdf_config\khdf\device_info\device_info.hcs
1915
19163、 vendor\hihope\rk3568\hdf_config\khdf\input\input_config.hcs
1917
19184、 drivers\framework\model\input\driver\touchscreen
1919
1920TP驱动的适配涉及TP驱动和hcs配置
1921
1922tp驱动的适配依赖hdf的input模型,hdf的input模型提供了TP,KEY,HID等场景的设备注册,管理,数据转发层,hcs解析等场景的支持能力。hdf的input模型可大致抽象为驱动管理层、公共驱动层以及器件驱动三层。
1923
1924从功能的角度看hdf input模块的框架如下:
1925
1926![dayu200-tp-02.png](figures/dayu200/dayu200-tp-02.png)
1927
1928因为hdf input模型的高度抽象集成,TP驱动的适配驱动主要涉及器件驱动层的适配。
1929
1930在适配前,需要先明确tp所需要的的资源。
1931
1932对于硬件资源,tp模组需要主机上的如下资源:
1933
19341.中断引脚
1935
19362.Reset引脚
1937
19383.使用的哪一组i2c,从设备的地址是什么
1939
19404.TP的初始化固件(这个通常由IC厂商提供)
1941
19425.触摸屏的分辨率
1943
1944对于软件资源,在hdf上适配tp,需要依赖如下几个hdf基础模组:
1945
19461.Hdf gpio子系统 用于设置gpio pin脚以及一些中断资源
1947
19482.Hdf i2c 子系统 用于进行i2c通信
1949
19503.Input模型
1951
1952器件驱动主要围绕如下结构体展开
1953
1954```
1955static struct TouchChipOps g_gt911ChipOps = {
1956    .Init = ChipInit,
1957    .Detect = ChipDetect,
1958    .Resume = ChipResume,
1959    .Suspend = ChipSuspend,
1960    .DataHandle = ChipDataHandle,
1961    .UpdateFirmware = UpdateFirmware,
1962    .SetAbility = SetAbility,
1963};
1964```
1965
1966ChipInit负责器件驱动的初始化动作
1967
1968ChipDetect负责初始化后的器件有效性检测
1969
1970SetAbility设置按键属性
1971
1972ChipDataHandle负责解析键值
1973
1974UpdateFirmware负责升级固件
1975
1976ChipSuspend负责器件的休眠
1977
1978ChipResume负责器件的唤醒
1979
1980按照器件的特性实现如上接口回调,并将该结构体注册进input模型即可
1981
1982#### HCS 配置
1983
1984device_info.hcs中加入新的器件节点
1985
1986```
1987device_touch_chip :: device {
1988                device0 :: deviceNode {
1989                    policy = 0;
1990                    priority = 180;
1991                    preload = 0;//0表示默认加载
1992                    permission = 0660;
1993                    moduleName = "HDF_TOUCH_GT911";//需要和器件driver中保持一致
1994                    serviceName = "hdf_touch_gt911_service";
1995                    deviceMatchAttr = "zsj_gt911_5p5";
1996                }
1997            }
1998```
1999
2000input_config.hcs中加入器件的特性
2001
2002```
2003chipConfig {
2004                    template touchChip {
2005                        match_attr = "";
2006                        chipName = "gt911";
2007                        vendorName = "zsj";
2008                        chipInfo = "AAAA11222";  // 4-ProjectName, 2-TP IC, 3-TP Module
2009                        /* 0:i2c 1:spi*/
2010                        busType = 0;
2011                        deviceAddr = 0x5D;
2012                        /* 0:None 1:Rising 2:Failing 4:High-level 8:Low-level */
2013                        irqFlag = 2;
2014                        maxSpeed = 400;
2015                        chipVersion = 0; //parse Coord TypeA
2016                        powerSequence {
2017                            /* [type, status, dir , delay]
2018                                <type> 0:none 1:vcc-1.8v 2:vci-3.3v 3:reset 4:int
2019                                <status> 0:off or low  1:on or high  2:no ops
2020                                <dir> 0:input  1:output  2:no ops
2021                                <delay> meanings delay xms, 20: delay 20ms
2022                             */
2023                            powerOnSeq = [4, 0, 1, 5,
2024                                         3, 0, 1, 10,
2025                                         3, 1, 1, 60,
2026                                         4, 2, 0, 50];
2027                            suspendSeq = [3, 0, 2, 10];
2028                            resumeSeq = [3, 1, 2, 10];
2029                            powerOffSeq = [3, 0, 2, 10,
2030                                           1, 0, 2, 20];
2031                        }
2032                    }
2033```
2034
2035## 显示适配
2036
2037显示适配需要完成的工作:图形服务HDI接口适配、GPU适配、LCD驱动适配
2038
2039### 显示HDI
2040
2041[显示HDI](https://gitee.com/openharmony/drivers_peripheral/blob/master/display/README_zh.md)对图形服务提供显示驱动能力,包括显示图层的管理、显示内存的管理及硬件加速等。 显示HDI需要适配两部分:gralloc 和 display_device。
2042
2043#### gralloc适配
2044
2045gralloc模块提供显示内存管理功能,OpenHarmony提供了使用与Hi3516DV300参考实现,厂商可根据实际情况参考适配,该实现基于drm开发,[源码链接](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard)2046
2047drm设备节点定义在//drivers_peripheral/display/hal/default_standard/srd/display_gralloc/display_gralloc_gbm.c文件中,可根据实际情况修改
2048
2049```
2050const char *g_drmFileNode = "/dev/dri/card0";
2051```
2052
2053该实现中存在一个海思的私有ioctl命令码 DRM_IOCTL_HISILICON_GEM_FD_TO_PHYADDR 定义在//drivers_peripheral/display/hal/default_standard/src/display_gralloc/hisilicon_drm.h 文件中, 在//drivers_peripheral/display/hal/default_standard/src/display_gralloc/display_gralloc_gbm.c文件中调用,属于海思的私有功能,适配时根据实际情况修改
2054
2055```
2056...
2057    InitBufferHandle(bo, fd, info, priBuffer);
2058    priBuffer->hdl.phyAddr = GetPhysicalAddr(grallocManager->drmFd, fd);
2059    *buffer = &priBuffer->hdl;
2060...
2061```
2062
2063####  display device适配
2064
2065display device模块提供显示设备管理、layer管理、硬件加速等功能。
2066
2067OpenHarmony提供了[基于drm的Hi3516DV300芯片的参考实现](https://gitee.com/openharmony/drivers_peripheral/tree/master/display/hal/default_standard/src/display_device),该实现默认支持硬件合成;
2068
2069如开发板不支持硬件合成,需要在drm_display.cpp文件中跳过gfx的初始化,
2070
2071```
2072drivers_peripheral/blob/master/display/hal/default_standard/src/display_device/drm/drm_display.cpp
2073int32_t DrmDisplay::Init()
2074{
2075    ...
2076    ...
2077    ret = HdiDisplay::Init();
2078    DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("init failed"));
2079    auto preComp = std::make_unique<HdiGfxComposition>();
2080    DISPLAY_CHK_RETURN((preComp == nullptr), DISPLAY_FAILURE,
2081        DISPLAY_LOGE("can not new HdiGfxComposition errno %{public}d", errno));
2082    ret = preComp->Init();                                                                                          // gfx初始化,这里需要跳过
2083    DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("can not init HdiGfxComposition"));  // 或者不判断返回值
2084
2085    ...
2086}
2087```
2088
2089同时在//drivers_peripheral/display/hal/default_standard/src/display_device/hdi_gfx_composition.cpp文件中修改set_layers方法,全部使用CPU合成显示
2090
2091```
2092int32_t HdiGfxComposition::SetLayers(std::vector<HdiLayer *> &layers, HdiLayer &clientLayer)
2093{
2094    DISPLAY_LOGD("layers size %{public}zd", layers.size());
2095    mClientLayer = &clientLayer;
2096    mCompLayers.clear();
2097    for (auto &layer : layers) {
2098        if (CanHandle(*layer)) {
2099#if 0                                      // CPU合成
2100            layer->SetDeviceSelect(COMPOSITION_CLIENT);
2101#else
2102            if ((layer->GetCompositionType() != COMPOSITION_VIDEO) &&
2103                (layer->GetCompositionType() != COMPOSITION_CURSOR)) {
2104                layer->SetDeviceSelect(COMPOSITION_DEVICE);
2105            } else {
2106                layer->SetDeviceSelect(layer->GetCompositionType());
2107            }
2108#endif
2109            mCompLayers.push_back(layer);
2110        }
2111    }
2112    DISPLAY_LOGD("composer layers size %{public}zd", mCompLayers.size());
2113    return DISPLAY_SUCCESS;
2114}
2115```
2116
2117#### 测试验证
2118
2119hello_composer测试模块:Rosen图形框架提供的测试程序,主要显示流程,HDI接口等功能是否正常。默认随系统编译。
2120
2121代码路径:
2122
2123```
2124foundation/graphic/graphic/rosen/samples/composer/
2125├── BUILD.gn
2126├── hello_composer.cpp
2127├── hello_composer.h
2128├── layer_context.cpp
2129├── layer_context.h
2130└── main.cpp
2131```
2132
2133具体验证如下:
2134
21351. 关闭render service
2136
2137  ```
2138  service_control stop render_service
2139  ```
2140
21412. 关闭 foundation进程
2142
2143  ```
2144  service_control stop foundation
2145  ```
2146
21473. 运行hello_composer 测试相关接口
2148
2149   ```
2150   ./hello_composer
2151   ```
2152
2153devicetest测试:HDI显示模块提供的测试模块,主要测试HDI接口、显示buffer、驱动等能力,测试时也需要关闭render service和 foundation进程。
2154
2155代码路径:/drivers/peripheral/display/test/unittest/standard
2156
2157```
2158├── BUILD.gn
2159├── common
2160│   ├── display_test.h
2161│   ├── display_test_utils.cpp
2162│   └── display_test_utils.h
2163├── display_device
2164│   ├── hdi_composition_check.cpp
2165│   ├── hdi_composition_check.h
2166│   ├── hdi_device_test.cpp
2167│   ├── hdi_device_test.h
2168│   ├── hdi_test_device_common.h
2169│   ├── hdi_test_device.cpp
2170│   ├── hdi_test_device.h
2171│   ├── hdi_test_display.cpp
2172│   ├── hdi_test_display.h
2173│   ├── hdi_test_layer.cpp
2174│   ├── hdi_test_layer.h
2175│   ├── hdi_test_render_utils.cpp
2176│   └── hdi_test_render_utils.h
2177└── display_gralloc
2178    ├── display_gralloc_test.cpp
2179    └── display_gralloc_test.h
2180```
2181
2182### GPU
2183
2184编译器clang
2185
2186```
2187prebuilts/clang/ohos/linux-x86_64/llvm
2188```
2189
2190musl库
2191
2192```
2193./build.sh --product-name rk3568 --build-target musl_all
2194```
2195
2196编译完成后,会在 out/{product_name}/obj/third_party/musl/usr/lib目录下生成对应的头文件和库:
2197
2198```
219932位对应arm-linux-ohos
2200
220164位对应aarch64-linux-ohos
2202```
2203
2204源码目录:
2205
2206```
2207third_party/musl
2208```
2209
2210GPU 编译参数参考
2211
2212```
2213TARGET_CFLAGS=" -march=armv7-a -mfloat-abi=softfp -mtune=generic-armv7-a -mfpu=neon -mthumb --target=arm-linux-ohosmusl -fPIC -ftls-model=global-dynamic -mtls-direct-seg-refs -DUSE_MUSL"
2214```
2215
2216## LCD
2217
2218dayu200平台默认支持一个mipi接口的lcd屏幕
2219
2220LCD的适配主要依赖于HDF显示模型,显示驱动模型基于 HDF 驱动框架、Platform 接口及 OSAL 接口开发,可以屏蔽不同内核形态(LiteOS、Linux)差异,适用于不同芯片平台,为显示屏器件提供统一的驱动平台。
2221
2222如图为 HDF Display驱动模型层次关系
2223
2224![640](figures/dayu200/dayu200-lcd-01.png)
2225
2226当前驱动模型主要部署在内核态中,向上对接到 Display 公共 hal 层,辅助 HDI 的实现。显示驱动通过 Display-HDI 层对图形服务暴露显示屏驱动能力;向下对接显示屏 panel 器件,驱动屏幕正常工作,自上而下打通显示全流程通路。
2227
2228所以LCD的适配主要在于LCD panel器件驱动的适配
2229
2230器件驱动的适配分为2部分:panel驱动和hcs配置
2231
2232涉及的文件有:
2233
2234```
2235drivers/framework/model/display/driver/panel
2236
2237vendor/hihope/rk3568/hdf_config/khdf/device_info
2238
2239vendor/hihope/rk3568/hdf_config/khdf/input
2240```
2241
2242### panel驱动
2243
2244器件驱动主要围绕如下接口展开:
2245
2246```
2247struct PanelData {
2248    struct HdfDeviceObject *object;
2249    int32_t (*init)(struct PanelData *panel);
2250    int32_t (*on)(struct PanelData *panel);
2251    int32_t (*off)(struct PanelData *panel);
2252    int32_t (*prepare)(struct PanelData *panel);
2253    int32_t (*unprepare)(struct PanelData *panel);
2254    struct PanelInfo *info;
2255    enum PowerStatus powerStatus;
2256    struct PanelEsd *esd;
2257    struct BacklightDev *blDev;
2258    void *priv;
2259};
2260```
2261
2262驱动中在初始化接口中实例化该结构体:
2263
2264    panelSimpleDev->panel.init = PanelSimpleInit;
2265    panelSimpleDev->panel.on = PanelSimpleOn;
2266    panelSimpleDev->panel.off = PanelSimpleOff;
2267    panelSimpleDev->panel.prepare = PanelSimplePrepare;
2268    panelSimpleDev->panel.unprepare = PanelSimpleUnprepare;
2269PanelSimpleInit负责panel的软件初始化
2270
2271PanelSimpleOn负责亮屏
2272
2273PanelSimpleOff负责灭屏
2274
2275PanelSimplePrepare负责亮屏的硬件时序初始化
2276
2277PanelSimpleUnprepare负责灭屏的硬件时序初始化
2278
2279实例化后使用RegisterPanel接口向display模型注册该panel驱动即可
2280
2281需要说明的是,dayu200上的这款lcd 使用的是DRM显示框架
2282
2283### hcs配置
2284
2285```
2286device4 :: deviceNode {
2287                    policy = 0;
2288                    priority = 100;
2289                    preload = 0;
2290                    moduleName = "LCD_PANEL_SIMPLE";
2291                }
2292```
2293
2294## 背光
2295
2296基于HDF框架开发的 背光驱动模型
2297
2298![dayu200-backlight-01.png](figures/dayu200/dayu200-backlight-01.png)
2299
2300rk3568背光是通过pwm控制占空比实现的,具体使用的是pwm4
2301
2302原生背光驱动代码路径
2303
2304```c
2305linux-5.10/drivers/video/backlight/pwm_bl.c
2306linux-5.10/drivers/video/backlight/backlight.c
2307linux-5.10/drivers/pwm/pwm-rockchip.c
2308```
2309
2310使用HDF框架下的背光驱动,需要关闭原生驱动
2311
2312```c
2313# CONFIG_BACKLIGHT_PWM is not set
2314```
2315
2316### HDF实现
2317
2318代码路径
2319
2320```c
2321drivers/framework/model/display/driver/backlight/hdf_bl.c
2322```
2323
2324HDF BL 入口函数
2325
2326```c
2327static int32_t BacklightInit(struct HdfDeviceObject *object)
2328{
2329    if (object == NULL) {
2330        HDF_LOGE("%s: object is null!", __func__);
2331        return HDF_FAILURE;
2332    }
2333    HDF_LOGI("%s success", __func__);
2334    return HDF_SUCCESS;
2335}
2336
2337struct HdfDriverEntry g_blDevEntry = {
2338    .moduleVersion = 1,
2339    .moduleName = "HDF_BL",
2340    .Init = BacklightInit,
2341    .Bind = BacklightBind,
2342};
2343
2344HDF_INIT(g_blDevEntry);
2345```
2346
2347代码路径:
2348
2349```c
2350drivers/framework/model/display/driver/backlight/pwm_bl.c
2351```
2352
2353HDF PWM 入口函数
2354
2355```c
2356struct HdfDriverEntry g_pwmBlDevEntry = {
2357    .moduleVersion = 1,
2358    .moduleName = "PWM_BL",
2359    .Init = BlPwmEntryInit,
2360};
2361
2362HDF_INIT(g_pwmBlDevEntry);
2363```
2364
2365具体控制背光的接口:
2366
2367```c
2368static int32_t BlPwmUpdateBrightness(struct BacklightDev *blDev, uint32_t brightness)
2369{
2370    int32_t ret;
2371    uint32_t duty;
2372    struct BlPwmDev *blPwmDev = NULL;
2373
2374    blPwmDev = ToBlDevPriv(blDev);
2375    if (blPwmDev == NULL) {
2376        HDF_LOGE("%s blPwmDev is null", __func__);
2377        return HDF_FAILURE;
2378    }
2379    if (blPwmDev->props.maxBrightness == 0) {
2380        HDF_LOGE("%s maxBrightness is 0", __func__);
2381        return HDF_FAILURE;
2382    }
2383    if (brightness == 0) {
2384        return PwmDisable(blPwmDev->pwmHandle);
2385    }
2386    duty = (brightness * blPwmDev->config.period) / blPwmDev->props.maxBrightness;
2387    ret = PwmSetDuty(blPwmDev->pwmHandle, duty);
2388    if (ret != HDF_SUCCESS) {
2389        HDF_LOGE("%s: PwmSetDuty failed, ret %d", __func__, ret);
2390        return HDF_FAILURE;
2391    }
2392    return PwmEnable(blPwmDev->pwmHandle);
2393}
2394
2395static struct BacklightOps g_blDevOps = {
2396    .updateBrightness = BlPwmUpdateBrightness,
2397};
2398```
2399
2400其实使用的就是HDF PWM 实现的对接内核pwm的接口
2401
2402![dayu200-backlight-02.png](figures/dayu200/dayu200-backlight-02.png)
2403
2404在LCD HDF器件驱动注册背光
2405
2406代码路径
2407
2408```c
2409drivers/framework/model/display/driver/panel/ili9881c_boe.c
2410```
2411
2412```c
2413ili9881cBoeDev->panel.blDev = GetBacklightDev("hdf_pwm");
2414if (ili9881cBoeDev->panel.blDev == NULL) {
2415    HDF_LOGE("%s GetBacklightDev fail", __func__);
2416    goto FAIL;
2417}
2418```
2419
2420### HCS配置
2421
2422驱动hcs配置
2423
2424```c
2425device_pwm_bl :: device {
2426    device0 :: deviceNode {
2427        policy = 0;
2428        priority = 95;
2429        preload = 0;
2430        moduleName = "PWM_BL";
2431        deviceMatchAttr = "pwm_bl_dev";
2432    }
2433}
2434device_backlight :: device {
2435    device0 :: deviceNode {
2436        policy = 2;
2437        priority = 90;
2438        preload = 0;
2439        permission = 0660;
2440        moduleName = "HDF_BL";
2441        serviceName = "hdf_bl";
2442    }
2443}
2444```
2445
2446pwm背光的hcs配置
2447
2448```c
2449root {
2450    backlightConfig {
2451        pwmBacklightConfig {
2452            match_attr = "pwm_bl_dev";
2453            pwmDevNum = 1;
2454            pwmMaxPeriod = 25000;
2455            backlightDevName = "hdf_pwm";
2456            minBrightness = 0;
2457            defBrightness = 127;
2458            maxBrightness = 255;
2459        }
2460    }
2461}
2462```
2463
2464### 测试
2465
2466cat /sys/kernel/debug/pwm 来查看hdf pwm 是否申请到pwm4
2467
2468申请成功有如下结果:
2469
2470requested 代表申请成功
2471
2472enabled 代表pwm4使能成功
2473
2474```c
2475# cat /sys/kernel/debug/pwm
2476
2477platform/fe6e0000.pwm, 1 PWM device
2478 pwm-0   ((null)              ): requested enabled period: 25000 ns duty: 9705 ns polarity: normal
2479```
2480
2481## **WIFI**
2482
2483### WIFI HDF化思路
2484
2485主要参考[《OpenHarmony HDF WLAN驱动分析》](https://mp.weixin.qq.com/s/iiE97pqPtzWIZadcjrQtsw)与使用 这篇文章,熟悉HDF WLAN的框架以及需要实现的主要接口,包括HDF驱动初始化接口、WLAN控制侧接口集、AP模式接口集、STA模式接口集、网络侧接口集、事件上报接口的实现。
2486
2487接下来熟悉HCS文件的格式以及"HDF WIFI”核心驱动框架的代码启动初始化过程,参考hi3881的代码进行改造。
2488
2489HDF WiFi框架总体框架图
2490
2491​                       ![image-20220320160720306](figures/dayu200/dayu200-wifi-01.png)
2492
2493### ap6275s驱动代码流程分析
2494
2495####  驱动模块初始化流程分析
2496
2497![dayu200-wifi-02.png](figures/dayu200/dayu200-wifi-02.png)
2498
2499Ap6275s 是一款SDIO设备WiFi模组驱动,使用标准Linux的SDIO设备驱动。内核模块初始化入口module_init()调用dhd_wifi_platform_load_sdio()函数进行初始化工作,这里调用wifi_platform_set_power()进行GPIO上电,调用dhd_wlan_set_carddetect()进行探测SDIO设备卡,最后调用sdio_register_driver(&bcmsdh_sdmmc_driver);进行SDIO设备驱动的注册,SDIO总线已经检测到WiFi模块设备 根据设备号和厂商号与该设备驱动匹配, 所以立即回调该驱动的bcmsdh_sdmmc_probe()函数,这里进行WiFi模组芯片的初始化工作,最后创建net_device网络接口wlan0,然后注册到Linux内核协议栈中。
2500
2501l 创建net_device网络接口wlan0对象
2502
2503dhd_allocate_if()会调用alloc_etherdev()创建net_device对象,即wlan0网络接口。
2504
2505l 将wlan0注册到内核协议栈
2506
2507调用dhd_register_if()函数,这里调用register_netdev(net);将wlan0网络接口注册到协议栈。
2508
2509### 整改代码适配HDF WiFi框架
2510
2511对于系统WiFi功能的使用,需要实现AP模式、STA模式、P2P三种主流模式,这里使用wpa_supplicant应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现STA模式和P2P模式的功能,使用hostapd应用程序通过HDF WiFi框架与WiFi驱动进行交互,实现AP模式和P2P模式的功能。
2512
2513Ap6275s WiFi6内核驱动依赖platform能力,主要包括SDIO总线的通讯能力;与用户态通信依赖HDF WiFi框架的能力,在确保上述能力功能正常后,即可开始本次WiFi驱动的HDF适配移植工作。本文档基于已经开源的rk3568开源版代码为基础版本,来进行此次移植。
2514
2515适配移植ap6275s WiFi6驱动涉及到的文件和目录如下:
2516
25171). 编译配置文件
2518
2519drivers/adapter/khdf/linux/model/network/wifi/Kconfig
2520
2521drivers/adapter/khdf/linux/model/network/wifi/vendor/Makefile
2522
25232). WiFi驱动源码目录
2524
2525原生驱动代码存放于:
2526
2527linux-5.10/drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd_wifi6/
2528
2529在原生驱动上增加以及修改的代码文件位于:
2530
2531device/hihope/rk3568/wifi/bcmdhd_wifi6/
2532
2533目录结构:
2534
2535```
2536./device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf
2537├── hdf_bdh_mac80211.c
2538├── hdf_driver_bdh_register.c
2539├── hdfinit_bdh.c
2540├── hdf_mac80211_ap.c
2541├── hdf_mac80211_sta.c
2542├── hdf_mac80211_sta.h
2543├── hdf_mac80211_sta_event.c
2544├── hdf_mac80211_sta_event.h
2545├── hdf_mac80211_p2p.c
2546├── hdf_public_ap6275s.h
2547├── net_bdh_adpater.c
2548├── net_bdh_adpater.h
2549```
2550
2551其中hdf_bdh_mac80211.c主要对g_bdh6_baseOps所需函数的填充, hdf_mac80211_ap.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_sta.c主要对g_bdh6_staOps所需函数进行填充,hdf_mac80211_p2p.c主要对g_bdh6_p2pOps所需函数进行填充,在openharmony/drivers/framework/include/wifi/wifi_mac80211_ops.h里有对wifi基本功能所需api的说明。
2552
2553####  驱动文件编写
2554
2555HDF WLAN驱动框架由Module、NetDevice、NetBuf、BUS、HAL、Client 和 Message 这七个部分组成。开发者在WiFi驱动HDF适配过程中主要实现以下几部分功能:
2556
25571)  适配HDF WLAN框架的驱动模块初始化
2558
2559代码流程框图如下:
2560
2561![dayu200-wifi-03.png](figures/dayu200/dayu200-wifi-03.png)
2562
2563代码位于device/hihope/rk3568/wifi/bcmdhd_wifi6/hdf_driver_bdh_register.c
2564
2565```
2566struct HdfDriverEntry g_hdfBdh6ChipEntry = {
2567  .moduleVersion = 1,
2568  .Bind = HdfWlanBDH6DriverBind,
2569  .Init = HdfWlanBDH6ChipDriverInit,
2570  .Release = HdfWlanBDH6ChipRelease,
2571  .moduleName = "HDF_WLAN_CHIPS"
2572};
2573HDF_INIT(g_hdfBdh6ChipEntry);
2574```
2575
2576在驱动初始化时会实现SDIO主控扫描探卡、WiFi芯片初始化、主接口的创建和初始化等工作。
2577
25782)  HDF WLAN Base控制侧接口的实现
2579
2580代码位于hdf_bdh_mac80211.c
2581
2582```
2583static struct HdfMac80211BaseOps g_bdh6_baseOps = {
2584  .SetMode = BDH6WalSetMode,
2585  .AddKey = BDH6WalAddKey,
2586  .DelKey = BDH6WalDelKey,
2587  .SetDefaultKey = BDH6WalSetDefaultKey,
2588  .GetDeviceMacAddr = BDH6WalGetDeviceMacAddr,
2589  .SetMacAddr = BDH6WalSetMacAddr,
2590  .SetTxPower = BDH6WalSetTxPower,
2591  .GetValidFreqsWithBand = BDH6WalGetValidFreqsWithBand,
2592  .GetHwCapability = BDH6WalGetHwCapability,
2593  .SendAction = BDH6WalSendAction,
2594  .GetIftype = BDH6WalGetIftype,
2595};
2596```
2597
2598上述实现的接口供STA、AP、P2P三种模式中所调用。
2599
26003)  HDF WLAN STA模式接口的实现
2601
2602STA模式调用流程图如下:
2603
2604​                ![image-20220320161412663](figures/dayu200/dayu200-wifi-04.png)
2605
2606代码位于hdf_mac80211_sta.c
2607
2608```
2609struct HdfMac80211STAOps g_bdh6_staOps = {
2610  .Connect = HdfConnect,
2611  .Disconnect = HdfDisconnect,
2612  .StartScan = HdfStartScan,
2613  .AbortScan = HdfAbortScan,
2614  .SetScanningMacAddress = HdfSetScanningMacAddress,
2615};
2616```
2617
26184)  HDF WLAN AP模式接口的实现
2619
2620AP模式调用流程图如下:
2621
2622 ![image-20220320161432068](figures/dayu200/dayu200-wifi-05.png)
2623
2624代码位于hdf_mac80211_ap.c
2625
2626```
2627struct HdfMac80211APOps g_bdh6_apOps = {
2628  .ConfigAp = WalConfigAp,
2629  .StartAp = WalStartAp,
2630  .StopAp = WalStopAp,
2631  .ConfigBeacon = WalChangeBeacon,
2632  .DelStation = WalDelStation,
2633  .SetCountryCode = WalSetCountryCode,
2634  .GetAssociatedStasCount = WalGetAssociatedStasCount,
2635  .GetAssociatedStasInfo = WalGetAssociatedStasInfo
2636};
2637```
2638
26395) HDF WLAN P2P模式接口的实现
2640
2641P2P模式调用流程图如下:
2642
2643 ![image-20220320161442845](figures/dayu200/dayu200-wifi-06.png)
2644
2645```
2646struct HdfMac80211P2POps g_bdh6_p2pOps = {
2647  .RemainOnChannel = WalRemainOnChannel,
2648  .CancelRemainOnChannel = WalCancelRemainOnChannel,
2649  .ProbeReqReport = WalProbeReqReport,
2650  .AddIf = WalAddIf,
2651  .RemoveIf = WalRemoveIf,
2652  .SetApWpsP2pIe = WalSetApWpsP2pIe,
2653  .GetDriverFlag = WalGetDriverFlag,
2654};
2655```
2656
26576) HDF WLAN框架事件上报接口的实现
2658
2659WiFi驱动需要通过上报事件给wpa_supplicant和hostapd应用程序,比如扫描热点结果上报,新STA终端关联完成事件上报等等,HDF WLAN事件上报的所有接口请参考drivers/framework/include/wifi/hdf_wifi_event.h2660
2661事件上报HDF WLAN接口主要有:
2662
2663| 头文件  hdf_wifi_event.h接口名称    | 功能描述                 |
2664| ----------------------------------- | ------------------------ |
2665| HdfWifiEventNewSta()                | 上报一个新的sta事件      |
2666| HdfWifiEventDelSta()              | 上报一个删除sta事件      |
2667| HdfWifiEventInformBssFrame()      | 上报扫描Bss事件          |
2668| HdfWifiEventScanDone()            | 上报扫描完成事件         |
2669| HdfWifiEventConnectResult()       | 上报连接结果事件         |
2670| HdfWifiEventDisconnected()        | 上报断开连接事件         |
2671| HdfWifiEventMgmtTxStatus()        | 上报发送状态事件         |
2672| HdfWifiEventRxMgmt()              | 上报接受状态事件         |
2673| HdfWifiEventCsaChannelSwitch()    | 上报Csa频段切换事件      |
2674| HdfWifiEventTimeoutDisconnected() | 上报连接超时事件         |
2675| HdfWifiEventEapolRecv()           | 上报Eapol接收事件        |
2676| HdfWifiEventResetResult()         | 上报wlan驱动复位结果事件 |
2677| HdfWifiEventRemainOnChannel()     | 上报保持信道事件         |
2678| HdfWifiEventCancelRemainOnChannel   | 上报取消保持信道事件     |
2679
2680### 所有关键问题总结
2681
2682#### 调试AP模块时,启动AP模式的方法
2683
2684调试AP模块时,无法正常开启AP功能的解决方法
2685
2686需要使用到busybox和hostapd配置ap功能,操作步骤如下:
2687
2688```
2689ifconfig wlan0 up
2690ifconfig wlan0 192.168.12.1 netmask 255.255.255.0
2691busybox udhcpd /data/udhcpd.conf
2692./hostapd -d /data/hostapd.conf
2693```
2694
2695#### 调试STA模块时,启动STA模式的方法
2696
2697```
2698wpa_supplicant -iwlan0 -c /data/l2tool/wpa_supplicant.conf -d &
2699./busybox udhcpc -i wlan0 -s /data/l2tool/dhcpc.sh
2700```
2701
2702#### 扫描热点事件无法上报到wap_supplicant的解决办法
2703
2704wpa_supplicant 这个应用程序启动时不能加 -B参数后台启动,-B后台启动的话,调用poll()等待接收事件的线程会退出,所以无法接收上报事件,
2705
2706wpa_supplicant -iwlan0 -c /data/wpa_supplicant.conf & 这样后台启动就可以了。
2707
2708#### wpa2psk方式无法认证超时问题解决方法
2709
2710分析流程发现 hostapd没有接收到WIFI_WPA_EVENT_EAPOL_RECV = 13这个事件,原来是驱动没有将接收到的EAPOL报文通过HDF WiFi框架发送给hostapd进程,在驱动接收报文后,调用netif_rx()触发软中断前将EAPOL报文发送给HDF WiFi框架,认证通过了。
2711
2712#### P2P模式连接不成功问题定位分析
2713
2714在调试P2P连接接口时,发现手机P2P直连界面总是处于已邀请提示,无法连接成功,通过抓取手机和WiFi模组正常连接成功报文和HDF适配后连接失败的报文进行比对,在失败的报文组中,发现手机侧多回复了一帧ACTION报文,提示无效参数,然后终止了P2P连接。
2715
2716 ![image-20220320161303057](figures/dayu200/dayu200-wifi-07.png)
2717
2718最后比对WiFi模组向手机发送的ACTION报文内容,发现填充的P2P Device Info的MAC地址值不对,如下:
2719
2720正确帧内容:
2721
2722 ![image-20220320161314006](figures/dayu200/dayu200-wifi-08.png)
2723
2724错误帧内容:
2725
2726 ![image-20220320161318995](figures/dayu200/dayu200-wifi-09.png)
2727
2728最后经过分析MAC地址的填充部分代码,这个MAC地址是wpa_supplicant 根据p2p0的MAC地址填充的,所以将wdev对象(即p2p-dev-wlan0)的MAC地址更新给p2p0接口,二者保持一致即可,见代码wl_get_vif_macaddr(cfg, 7, p2p_hnetdev->macAddr);的调用。
2729
2730### 连接成功日志
2731
2732#### STA模式连接成功日志
2733
2734```
2735WPA: Key negotiation completed with 50:eb:f6:02:8e6:d4 [PTK=CCMP GTK=CCMP]
2736 06 wlan0: State: GROUP_HANDSHAKEc -> COMPLETED
2737wlan0: CTRL-E4VENT-CONNECTED - Connection to 50:eb:f6:02:8e:d4 completed 3[id=0 id_str=]
2738WifiWpaReceived eEapol done
2739```
2740
2741#### AP模式连接成功日志
2742
2743```
2744wlan0: STA 96:27:b3:95:b7:6e IEEE 802.1X: au:thorizing port
2745wlan0: STA 96:27:b3:95:b7:6e WPA: pairwise key handshake completed (RSN)
2746WifiWpaReceiveEapol done
2747```
2748
2749#### P2P模式连接成功日志
2750
2751```
2752P2P: cli_channels:
2753EAPOL: External notificationtion - portValid=1
2754EAPOL: External notification:tion - EAP success=1
2755EAPOL: SUPP_PAE entering state AUTHENTIwCATING
2756EAPOL: SUPP_BE enterilng state SUCCESS
2757EAP: EAP ent_ering state DISABLED
2758EAPOL: SUPP_PAE entering state AUTHENTICATED
2759EAPOL:n Supplicant port status: Authoorized
2760EAPOL: SUPP_BE entertaining IDLE
2761WifiWpaReceiveEapol donepleted - result=SUCCESS
2762
2763\# ifconfig
2764
2765lo    Link encap:Local Loopback
2766     inet addr:127.0.0.1 Mask:255.0.0.0
2767     inet6 addr: ::1/128 Scope: Host
2768     UP LOOPBACK RUNNING MTU:65536 Metric:1
2769     RX packets:12 errors:0 dropped:0 overruns:0 frame:0
2770     TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
2771     collisions:0 txqueuelen:1000
2772     RX bytes:565 TX bytes:565
2773
2774wlan0   Link encap:Ethernet HWaddr 10:2c:6b:11:61:e0 Driver bcmsdh_sdmmc
2775     inet6 addr: fe80::122c:6bff:fe11:61e0/64 Scope: Link
2776     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
2777     RX packets:0 errors:0 dropped:0 overruns:0 frame:0
2778     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
2779     collisions:0 txqueuelen:1000
2780     RX bytes:0 TX bytes:0
2781
2782p2p0   Link encap:Ethernet HWaddr 12:2c:6b:11:61:e0
2783     inet6 addr: fe80::102c:6bff:fe11:61e0/64 Scope: Link
2784     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
2785     RX packets:0 errors:0 dropped:0 overruns:0 frame:0
2786     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
2787     collisions:0 txqueuelen:1000
2788     RX bytes:0 TX bytes:0
2789
2790p2p-p2p0-0 Link encap:Ethernet HWaddr 12:2c:6b:11:21:e0 Driver bcmsdh_sdmmc
2791     inet6 addr: fe80::102c:6bff:fe11:21e0/64 Scope: Link
2792     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
2793     RX packets:0 errors:0 dropped:9 overruns:0 frame:0
2794     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
2795     collisions:0 txqueuelen:1000
2796     RX bytes:0 TX bytes:0
2797```
2798
2799## **BT**
2800
2801### HCI接口
2802
2803蓝牙整体硬件架构上分为主机(计算机或MCU)和主机控制器(实际蓝牙芯片组)两部分;主机和控制器之间的通信遵循主机控制器接口(HCI),如下所示:
2804
2805![dayu200-bt-01.png](figures/dayu200/dayu200-bt-01.png)
2806
2807HCI定义了如何交换命令,事件,异步和同步数据包。异步数据包(ACL)用于数据传输,而同步数据包(SCO)用于带有耳机和免提配置文件的语音。
2808
2809### 硬件连接
2810
2811从RK3568芯片描述中看,该芯片并不没有集成WIFI/蓝牙功能,都需要外接蓝牙芯片才能支持蓝牙功能,这也符合上述逻辑架构。那主机和控制器之间物理具体怎么连接呢?查看开发板规格书可以看的更清楚:
2812
2813![](figures/dayu200/dayu200-bt-02.png)
2814
2815其中,28-36号管脚就是UART(串口);同时还可以看到有几个管脚分别做电源和休眠控制。
2816
2817### 蓝牙VENDORLIB适配
2818
2819#### vendorlib是什么
2820
2821vendorlib部署在主机侧,可以认为是主机侧对蓝牙芯片驱动层,屏蔽不同蓝牙芯片的技术细节。从代码层面解读,其主要功能有两个:
2822
28231、为协议栈提供蓝牙芯片之间的通道(串口的文件描述符)
2824
28252、提供特定芯片的具体控制方法
2826
2827#### 代码层面解读vendorlib
2828
2829bt_vendor_lib.h 路径:
2830
2831```
2832foundation/communication/bluetooth/services/bluetooth_standard/hardware/include
2833```
2834
2835该文件定义了协议栈和vendor_lib交互接口,分为两组:
2836
28371、 vendorlib实现,协议栈调用
2838
2839```c
2840typedef struct {
2841    /**
2842     * Set to sizeof(bt_vendor_interface_t)
2843     */
2844    size_t size;
2845    /**
2846     * Caller will open the interface and pass in the callback routines
2847     * to the implementation of this interface.
2848     */
2849    int (*init)(const bt_vendor_callbacks_t* p_cb, unsigned char* local_bdaddr);
2850
2851    /**
2852     * Vendor specific operations
2853     */
2854    int (*op)(bt_opcode_t opcode, void* param);
2855
2856    /**
2857     * Closes the interface
2858     */
2859    void (*close)(void);
2860} bt_vendor_interface_t;
2861```
2862
2863协议栈启动时的基本流程如下:
2864
28651.1、协议栈动态打开libbt_vendor.z.so,并调用init函数,初始化vendorlib
2866
28671.2、协议栈调用op函数,分别调用BT_OP_POWER_ON、BT_OP_HCI_CHANNEL_OPEN、BT_OP_INIT三个opcode;原则上BT_OP_INIT成功后说明芯片初始化完成。
2868
28692、协议栈实现,vendorlib调用(回调函数)
2870
2871```c
2872typedef struct {
2873    /**
2874   * set to sizeof(bt_vendor_callbacks_t)
2875    */
2876    size_t size;
2877
2878    /* notifies caller result of init request */
2879    init_callback init_cb;
2880
2881    /* buffer allocation request */
2882    malloc_callback alloc;
2883
2884    /* buffer free request */
2885    free_callback dealloc;
2886
2887    /* hci command packet transmit request */
2888    cmd_xmit_callback xmit_cb;
2889} bt_vendor_callbacks_t;
2890```
2891
2892init_cb在BT_OP_INIT完成后调用
2893
2894alloc/dealloc用于发送HCI消息时申请/释放消息控件
2895
2896xmit_cb发送HCI Commands
2897
2898vendor_lib实现的几个重要函数
2899
29001、 init函数
2901
2902```c
2903static int init(const bt_vendor_callbacks_t *p_cb, unsigned char *local_bdaddr)
2904{
2905     /* * ... */
2906    userial_vendor_init();
2907    upio_init();
2908
2909	vnd_load_conf(VENDOR_LIB_CONF_FILE);
2910
2911    /* store reference to user callbacks */
2912    bt_vendor_cbacks = (bt_vendor_callbacks_t *)p_cb;
2913        /* This is handed over from the stack */
2914    return memcpy_s(vnd_local_bd_addr, BD_ADDR_LEN, local_bdaddr, BD_ADDR_LEN);
2915}
2916```
2917
2918vendorlib被调用的第一个函数,vendorlib保存好协议栈的callback和mac地址即可。
2919
29202、 BT_OP_POWER_ON对应处理
2921
2922观名知意,这个操作理论上需要拉高电源管脚电平;该函数中使用rfill设备来处理,并没有直接调用驱动拉高电平
2923
2924```c
2925int upio_set_bluetooth_power(int on)
2926{
2927    int sz;
2928    int fd = -1;
2929    int ret = -1;
2930    char buffer = '0';
2931
2932    switch (on) {
2933        case UPIO_BT_POWER_OFF:
2934            buffer = '0';
2935            break;
2936
2937        case UPIO_BT_POWER_ON:
2938            buffer = '1';
2939            break;
2940        default:
2941            return 0;
2942    }
2943
2944    /* check if we have rfkill interface */
2945    if (is_rfkill_disabled()) {
2946        return 0;
2947    }
2948
2949    if (rfkill_id == -1) {
2950        if (init_rfkill()) {
2951            return ret;
2952        }
2953    }
2954
2955    fd = open(rfkill_state_path, O_WRONLY);
2956    if (fd < 0) {
2957        return ret;
2958    }
2959
2960    sz = write(fd, &buffer, 1);
2961    /* ... */
2962    return ret;
2963}
2964```
2965
29663、BT_OP_HCI_CHANNEL_OPEN对应处理
2967
2968```c
2969case BT_OP_HCI_CHANNEL_OPEN: { // BT_VND_OP_USERIAL_OPEN
2970            int(*fd_array)[] = (int(*)[])param;
2971            int fd, idx;
2972            fd = userial_vendor_open((tUSERIAL_CFG *)&userial_init_cfg);
2973            if (fd != -1) {
2974                for (idx = 0; idx < HCI_MAX_CHANNEL; idx++)
2975                    (*fd_array)[idx] = fd;
2976                retval = 1;
2977        }
2978        /* retval contains numbers of open fd of HCI channels */
2979        break;
2980```
2981
2982userial_vendor_open函数打开串口设备(UART)得到文件描述符(fd),通过op的参数param返回该fd
2983
2984该串口设备在系统中的名字应该在开发板中预定义了,本次开发板上设备为/dev/ttyS8
2985
29864、BT_OP_INIT对应处理
2987
2988该操作码要求对蓝牙芯片进行初始化,具体要进行的处理和蓝牙芯片强相关。以本次调测的AP6257S芯片为例,初始化过程中主要是下发蓝牙固件。
2989
2990初始化结束后,必须调用init_cb回调函数(参见bt_vendor_callbacks_t)通知协议栈初始化结果,否则会阻塞协议栈线程导致蓝牙相关功能无法正常使用。协议栈的具体处理如下:
2991
2992协议栈调用BT_OP_INIT后会等待信号量,该信号量由init_cb函数置位
2993
2994```c
2995static int HciInitHal()
2996{
2997    int result = BT_NO_ERROR;
2998
2999    g_waitHdiInit = SemaphoreCreate(0);
3000    int ret = g_hdiLib->hdiInit(&g_hdiCallbacks);
3001    if (ret == SUCCESS) {
3002        SemaphoreWait(g_waitHdiInit);
3003    }
3004}
3005```
3006
3007### vendorlib移植问题
3008
30091、 vendorlib的so命名
3010
3011vendorlib必须是libbt_vendor.z.so;因为协议栈打开动态链接库就是这个名字
3012
30132、 固件问题
3014
3015开发时一定要关注芯片固件,有些蓝牙芯片可能无需升级固件,有些则必须升级固件;本次AP6257S适配过程中最开始没有下发固件,导致蓝牙接收信号很差。固件下发时需要注意如下两点:
3016
30172.1、对于AP6257S芯片,因为蓝牙芯片内并没有类似flash存储,要求芯片上下电后必须重新下发
3018
30192.2、按照芯片本身的要求处理,最好能找到厂商的参考代码;以Broadcom系列芯片为例,其固件下发过程比较复杂,通过一个状态机驱动;共如下9个状态
3020
3021```c
3022/ Hardware Configuration State */
3023enum {
3024  HW_CFG_START = 1,
3025  HW_CFG_SET_UART_CLOCK,
3026  HW_CFG_SET_UART_BAUD_1,
3027  HW_CFG_READ_LOCAL_NAME,
3028  HW_CFG_DL_MINIDRIVER,
3029  HW_CFG_DL_FW_PATCH,
3030  HW_CFG_SET_UART_BAUD_2,
3031  HW_CFG_SET_BD_ADDR,
3032  HW_CFG_READ_BD_ADDR
3033};
3034```
3035
3036在收到BT_OP_INIT后初始化状态机,然后发送HCI_REST命令,切换状态为HW_CFG_START;
3037
3038```c
3039void hw_config_start(void)
3040{
3041    HC_BT_HDR *p_buf = NULL;
3042    uint8_t *p;
3043    hw_cfg_cb.state = 0;
3044    hw_cfg_cb.fw_fd = -1;
3045    hw_cfg_cb.f_set_baud_2 = FALSE;
3046
3047    if (bt_vendor_cbacks) {
3048        p_buf = (HC_BT_HDR *)bt_vendor_cbacks->alloc(BT_HC_HDR_SIZE +
3049                                                     HCI_CMD_PREAMBLE_SIZE);
3050    }
3051
3052    if (p_buf) {
3053        p_buf->event = MSG_STACK_TO_HC_HCI_CMD;
3054        p_buf->offset = 0;
3055        p_buf->layer_specific = 0;
3056        p_buf->len = HCI_CMD_PREAMBLE_SIZE;
3057
3058        p = (uint8_t *)(p_buf + 1);
3059        UINT16_TO_STREAM(p, HCI_RESET);
3060        *p = 0;
3061
3062        hw_cfg_cb.state = HW_CFG_START;
3063        bt_vendor_cbacks->xmit_cb(HCI_RESET, p_buf);
3064    } else {
3065        if (bt_vendor_cbacks) {
3066            HILOGE("vendor lib fw conf aborted [no buffer]");
3067            bt_vendor_cbacks->init_cb(BTC_OP_RESULT_FAIL);
3068        }
3069    }
3070}
3071```
3072
3073收到芯片返回的HCI_RESET完成事件后,继续切换到下一个状态机并发送下一个COMMAND,一直到状态机完成固件下发。
3074
3075详细实现请参见hw_config_cback函数。
3076
30773、 关注系统间接口差异
3078
3079不同系统的接口可能有一些细微差异,需要重点关注;对比其他系统和OHOS的接口,vendorlib调用xmit_cb发送HCI命令的函数定义略有差异
3080
3081其他系统:
3082
3083```c
3084/* define callback of the cmd_xmit_cb
3085 *
3086
3087The callback function which HCI lib will call with the return of command
3088
3089complete packet. Vendor lib is responsible for releasing the buffer passed
3090
3091in at the p_mem parameter by calling dealloc callout function.
3092*/
3093typedef void (*tINT_CMD_CBACK)(void* p_mem);
3094typedef uint8_t (*cmd_xmit_cb)(uint16_t opcode, void* p_buf, tINT_CMD_CBACK p_cback);
3095```
3096
3097OHOS:
3098
3099```c
3100/**
3101
3102hci command packet transmit callback
3103
3104Vendor lib calls cmd_xmit_cb function in order to send a HCI Command
3105
3106packet to BT Controller.
3107*
3108
3109The opcode parameter gives the HCI OpCode (combination of OGF and OCF) of
3110
3111HCI Command packet. For example, opcode = 0x0c03 for the HCI_RESET command
3112
3113packet. */
3114
3115typedef uint8_t (*cmd_xmit_callback)(uint16_t opcode, void* p_buf);
3116```
3117
3118也就是说vendorlib中发送命令后,其他系统会直接调用callback通知芯片返回的消息,OHOS则是通过BT_OP_EVENT_CALLBACK操作码(参见bt_opcode_t定义)通知芯片返回的消息;vendorlib需要解析报文中的消息码确认芯片是处理的哪个消息,然后调用对应的处理函数。
3119
3120```c
3121void hw_process_event(HC_BT_HDR *p_buf)
3122{
3123    uint16_t opcode;
3124    uint8_t *p = (uint8_t *)(p_buf + 1) + HCI_EVT_CMD_CMPL_OPCODE;
3125    STREAM_TO_UINT16(opcode, p);
3126    switch (opcode) {
3127    case HCI_VSC_WRITE_BD_ADDR:
3128    #if (USE_CONTROLLER_BDADDR == TRUE)
3129        case HCI_READ_LOCAL_BDADDR:
3130    #endif
3131        case HCI_READ_LOCAL_NAME:
3132        case HCI_VSC_DOWNLOAD_MINIDRV:
3133        case HCI_VSC_WRITE_FIRMWARE:
3134        case HCI_VSC_LAUNCH_RAM:
3135        case HCI_RESET:
3136        case HCI_VSC_WRITE_UART_CLOCK_SETTING:
3137        case HCI_VSC_UPDATE_BAUDRATE:
3138            hw_config_cback(p_buf);
3139            break;
3140```
3141
3142另外,OHOS返回的是发送消息的字节数,<=0为发送失败,和其他系统接口的返回值也不同
3143
31444、 snoop日志
3145
3146其他系统中记录了HCI交互消息,OHOS同样有记录;OHOS系统生成文件为/data/log/bluetooth/snoop.log,通过wireshark或其它报文分析工具可以看到Host和Controller之间的交互流程,有助于问题分析
3147
3148##    Sensor
3149
3150  基于HDF(Hardware Driver Foundation)驱动框架开发的Sensor驱动模型
3151
3152![dayu200-sensor-01.png](figures/dayu200/dayu200-sensor-01.png)
3153
3154rk3568 支持accel sensor,整体的驱动框架openharmony 主线已经具备,只需要实现具体的器件驱动即可。
3155
3156### mcx5566xa HDF驱动实现
3157
3158RK3568平台支持加速度传感器,型号是MXC6655XA,具体配置可以查看该器件的datasheet。 移植HDF前,需要确认内核该sensor的编译使能是关闭的。
3159
3160配置文件路径kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig
3161
3162```c
3163# CONFIG_GS_MXC6655XA is not set
3164```
3165
3166代码路径:
3167
3168```c
3169drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.c
3170drivers/framework/model/sensor/driver/chipset/accel/accel_mxc6655xa.h
3171```
3172
3173编译宏
3174
3175```c
3176CONFIG_DRIVERS_HDF_SENSOR_ACCEL_MXC6655XA=y
3177```
3178
3179Mxc6655xa 加速度计驱动入口函数实现
3180
3181```c
3182struct HdfDriverEntry g_accelMxc6655xaDevEntry = {
3183    .moduleVersion = 1,
3184    .moduleName = "HDF_SENSOR_ACCEL_MXC6655XA",
3185    .Bind = Mxc6655xaBindDriver,
3186    .Init = Mxc6655xaInitDriver,
3187    .Release = Mxc6655xaReleaseDriver,
3188};
3189
3190HDF_INIT(g_accelMxc6655xaDevEntry);
3191```
3192
3193接下来就是差异化适配函数
3194
3195```c
3196struct AccelOpsCall {
3197int32_t (*Init)(struct SensorCfgData *data);
3198int32_t (*ReadData)(struct SensorCfgData *data);
3199};
3200```
3201
3202获取x, y, z三轴数据接口
3203
3204```c
3205int32_t ReadMxc6655xaData(struct SensorCfgData *cfg, struct SensorReportEvent *event)
3206{
3207    int32_t ret;
3208    struct AccelData rawData = { 0, 0, 0 };
3209    static int32_t tmp[ACCEL_AXIS_NUM];
3210
3211    CHECK_NULL_PTR_RETURN_VALUE(cfg, HDF_ERR_INVALID_PARAM);
3212    CHECK_NULL_PTR_RETURN_VALUE(event, HDF_ERR_INVALID_PARAM);
3213
3214    ret = ReadMxc6655xaRawData(cfg, &rawData, &event->timestamp);
3215    if (ret != HDF_SUCCESS) {
3216        HDF_LOGE("%s: MXC6655XA read raw data failed", __func__);
3217        return HDF_FAILURE;
3218    }
3219
3220    event->sensorId = SENSOR_TAG_ACCELEROMETER;
3221    event->option = 0;
3222    event->mode = SENSOR_WORK_MODE_REALTIME;
3223
3224    rawData.x = rawData.x * MXC6655XA_ACC_SENSITIVITY_2G;
3225    rawData.y = rawData.y * MXC6655XA_ACC_SENSITIVITY_2G;
3226    rawData.z = rawData.z * MXC6655XA_ACC_SENSITIVITY_2G;
3227
3228    tmp[ACCEL_X_AXIS] = (rawData.x * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
3229    tmp[ACCEL_Y_AXIS] = (rawData.y * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
3230    tmp[ACCEL_Z_AXIS] = (rawData.z * SENSOR_CONVERT_UNIT) / SENSOR_CONVERT_UNIT;
3231
3232    ret = SensorRawDataToRemapData(cfg->direction, tmp, sizeof(tmp) / sizeof(tmp[0]));
3233    if (ret != HDF_SUCCESS) {
3234        HDF_LOGE("%s: MXC6655XA convert raw data failed", __func__);
3235        return HDF_FAILURE;
3236    }
3237
3238    event->dataLen = sizeof(tmp);
3239    event->data = (uint8_t *)&tmp;
3240
3241    return ret;
3242}
3243```
3244
3245初始化
3246
3247```c
3248static int32_t InitMxc6655xa(struct SensorCfgData *data)
3249{
3250    int32_t ret;
3251
3252    CHECK_NULL_PTR_RETURN_VALUE(data, HDF_ERR_INVALID_PARAM);
3253    ret = SetSensorRegCfgArray(&data->busCfg, data->regCfgGroup[SENSOR_INIT_GROUP]);
3254    if (ret != HDF_SUCCESS) {
3255        HDF_LOGE("%s: MXC6655XA sensor init config failed", __func__);
3256        return HDF_FAILURE;
3257    }
3258    return HDF_SUCCESS;
3259}
3260```
3261
3262### hcs配置
3263
3264Mxc6655xa accel sensor 驱动HCS配置
3265
3266```c
3267device_sensor_mxc6655xa :: device {
3268    device0 :: deviceNode {
3269        policy = 1;
3270        priority = 120;
3271        preload = 0;
3272        permission = 0664;
3273        moduleName = "HDF_SENSOR_ACCEL_MXC6655XA";
3274        serviceName = "hdf_accel_mxc6655xa";
3275        deviceMatchAttr = "hdf_sensor_accel_mxc6655xa_driver";
3276    }
3277}
3278```
3279
3280Mxc6655xa accel sensor 寄存器组配置信息
3281
3282```c
3283#include "../sensor_common.hcs"
3284root {
3285    accel_mxc6655xa_chip_config : sensorConfig {
3286        match_attr = "hdf_sensor_accel_mxc6655xa_driver";
3287        sensorInfo :: sensorDeviceInfo {
3288            sensorName = "accelerometer";
3289            vendorName = "memsi_mxc6655xa"; // max string length is 16 bytes
3290            sensorTypeId = 1; // enum SensorTypeTag
3291            sensorId = 1; // user define sensor id
3292            power = 230;
3293        }
3294        sensorBusConfig :: sensorBusInfo {
3295            busType = 0; // 0:i2c 1:spi
3296            busNum = 5;
3297            busAddr = 0x15;
3298            regWidth = 1; // 1byte
3299        }
3300        sensorIdAttr :: sensorIdInfo {
3301            chipName = "mxc6655xa";
3302            chipIdRegister = 0x0f;
3303            chipIdValue = 0x05;
3304        }
3305        sensorDirection {
3306            direction = 5; // chip direction range of value:0-7
3307            /* <sign> 1:negative  0:positive
3308               <map> 0:AXIS_X  1:AXIS_Y  2:AXIS_Z
3309            */
3310            /* sign[AXIS_X], sign[AXIS_Y], sign[AXIS_Z], map[AXIS_X], map[AXIS_Y], map[AXIS_Z] */
3311            convert = [
3312                0, 0, 0, 0, 1, 2,
3313                1, 0, 0, 1, 0, 2,
3314                0, 0, 1, 0, 1, 2,
3315                0, 1, 0, 1, 0, 2,
3316                1, 0, 1, 0, 1, 2,
3317                0, 0, 1, 1, 0, 2,
3318                0, 1, 1, 0, 1, 2,
3319                1, 1, 1, 1, 0, 2
3320            ];
3321        }
3322        sensorRegConfig {
3323            /*  regAddr: register address
3324                value: config register value
3325                len: size of value
3326                mask: mask of value
3327                delay: config register delay time (ms)
3328                opsType: enum SensorOpsType 0-none 1-read 2-write 3-read_check 4-update_bit
3329                calType: enum SensorBitCalType 0-none 1-set 2-revert 3-xor 4-left shift 5-right shift
3330                shiftNum: shift bits
3331                debug: 0-no debug 1-debug
3332                save: 0-no save 1-save
3333            */
3334            /* regAddr, value, mask, len, delay, opsType, calType, shiftNum, debug, save */
3335            initSeqConfig = [
3336                0x7e,    0xb6, 0xff,   1,     5,       2,       0,        0,     0,    0,
3337                0x7e,    0x10, 0xff,   1,     5,       2,       0,        0,     0,    0
3338            ];
3339            enableSeqConfig = [
3340                0x7e,    0x11, 0xff,   1,     5,       2,       0,        0,     0,    0,
3341                0x41,    0x03, 0xff,   1,     0,       2,       0,        0,     0,    0,
3342                0x40,    0x08, 0xff,   1,     0,       2,       0,        0,     0,    0
3343            ];
3344            disableSeqConfig = [
3345                0x7e,    0x10, 0xff,   1,     5,       2,       0,        0,     0,    0
3346            ];
3347        }
3348    }
3349}
3350```
3351
3352### 测试
3353
3354UT测试可以获取到sensor的三轴数据
3355
3356测试代码路径
3357
3358```c
3359drivers/peripheral/sensor/test/unittest/common/hdf_sensor_test.cpp
3360```
3361
3362编译UT代码命令:
3363
3364```c
3365./build.sh --product-name rk3568 --build-target hdf_test_sensor
3366```
3367
3368hdf_test_sensor.bin  push到system/bin目录,添加执行权限,执行
3369
3370有如下结果代表sensor 测试成功
3371
3372```c
3373SensorTestDataCallback enter
3374sensor id :[1], data[1]: 0.001877
3375sensor id :[1], data[2]: 0.160823
3376sensor id :[1], data[3]: 0.046122
3377```
3378
3379##    Vibrator
3380
3381### vibrator 模型
3382
3383Vibrator驱动模型主要包含Vibrator(传感器)相关的HDI接口与实现,提供Vibrator HDI(Hardware Driver Interface)能力接口,支持静态HCS配置的时间序列和动态配置持续时间两种振动效果。调用StartOnce接口动态配置持续振动时间;调用StartEffect接口启动静态配置的振动效果。
3384
3385**图 1** Vibrator驱动模型图
3386
3387![dayu200-vibrator-01.png](figures/dayu200/dayu200-vibrator-01.png)
3388
3389rk3568 支持线性马达,整体的驱动框架openharmony 主线已经具备,只需要实现具体的器件驱动即可。
3390
3391### HDF驱动实现
3392
3393代码路径:
3394
3395```c
3396drivers/framework/model/misc/vibrator/driver/chipset/vibrator_linear_driver.c
3397```
3398
3399linear Vibrator加速度计驱动入口函数实现
3400
3401```c
3402struct HdfDriverEntry g_linearVibratorDriverEntry = {
3403    .moduleVersion = 1,
3404    .moduleName = "HDF_LINEAR_VIBRATOR",
3405    .Bind = BindLinearVibratorDriver,
3406    .Init = InitLinearVibratorDriver,
3407    .Release = ReleaseLinearVibratorDriver,
3408};
3409
3410HDF_INIT(g_linearVibratorDriverEntry);
3411```
3412
3413### hcs配置
3414
3415驱动hcs配置
3416
3417```c
3418        vibrator :: host {
3419            hostName = "vibrator_host";
3420            device_vibrator :: device {
3421                device0 :: deviceNode {
3422                    policy = 2;
3423                    priority = 100;
3424                    preload = 0;
3425                    permission = 0664;
3426                    moduleName = "HDF_VIBRATOR";
3427                    serviceName = "hdf_misc_vibrator";
3428                    deviceMatchAttr = "hdf_vibrator_driver";
3429                }
3430            }
3431            device_linear_vibrator :: device {
3432                device0 :: deviceNode {
3433                    policy = 1;
3434                    priority = 105;
3435                    preload = 0;
3436                    permission = 0664;
3437                    moduleName = "HDF_LINEAR_VIBRATOR";
3438                    serviceName = "hdf_misc_linear_vibrator";
3439                    deviceMatchAttr = "hdf_linear_vibrator_driver";
3440                }
3441            }
3442        }
3443```
3444
3445线性马达器件hcs配置
3446
3447```c
3448root {
3449    linearVibratorConfig {
3450        boardConfig {
3451            match_attr = "hdf_linear_vibrator_driver";
3452            vibratorChipConfig {
3453                busType = 1; // 0:i2c 1:gpio
3454                gpioNum = 154;
3455                startReg = 0;
3456                stopReg = 0;
3457                startMask = 0;
3458            }
3459        }
3460    }
3461}
3462```
3463
3464### UT测试
3465
3466测试代码路径
3467
3468```c
3469drivers/peripheral/misc/vibrator/test/unittest/common/hdf_vibrator_test.cpp
3470```
3471
3472编译UT代码命令
3473
3474```c
3475./build.sh --product-name rk3568 --build-target hdf_test_vibrator
3476```
3477
3478hdf_test_vibrator.bin  push到system/bin目录,添加执行权限,执行
3479
3480```
3481[ RUN ] HdfVibratorTest.CheckVibratorInstanceIsEmpty
3482[ OK ] HdfVibratorTest.CheckVibratorInstanceIsEmpty (0 ms)
3483[ RUN ] HdfVibratorTest.PerformOneShotVibratorDuration001
3484[ OK ] HdfVibratorTest.PerformOneShotVibratorDuration001 (2001 ms)
3485[ RUN ] HdfVibratorTest.ExecuteVibratorEffect001
3486[ OK ] HdfVibratorTest.ExecuteVibratorEffect001 (5001 ms)
3487```
3488
3489