[Sipeed 內部培訓] V831/V833 的 SDK 的 kernel & package 的開發方法


由於本文找不到地方放,所以放我的博客上,如果你有想進一步了解的內容可以和我說,我會再針對這個內容做詳細的介紹。

本次培訓內容只涉及如下內容。

  1. 關於編譯、測試、打包鏡像的基礎用法,以及常見的注意點
  2. 關於 Tina Linux 的 kernel 和 package 的速成用法
  3. 關於適配和測試常見的基礎外設驅動,從上層用戶空間的軟件到底層硬件寄存器之間的聯系
  4. 拓展介紹給 SDK 加入沒有的外設驅動調試和開發流程

內容可能會有點多,但不一定要全部記下來,可以拿本文作為開發流程參考,用到的時候再查閱就行。

相關資料和源碼在公司的 coding github 倉庫里,主要看 sdkkernel 就行。

以下內容舉一反三,適用於 2020 年后的全志 Tina Linux 系列的 SDK 源碼,但不同芯片的驅動細節會有出入,不過框架改變很少,這里只介紹 V831/V833 的部分,關於 R329 的內容請移步到 github.com/sipeed/R329-Tina-jishu 倉庫,外部人員均可預覽。

獲取資料

一些常見的公開資料,可以在 https://open.allwinnertech.com/ 技術支持平台搜索獲取,如下圖,如果有找不到的可以直接和我要,如 MPP 和 ISP 方面的可能就沒有公開。

這里主要就是可以搜索,方便找一些相關的解答。

關於編譯、測試、打包鏡像的基礎用法,以及常見的注意點。

配置基礎編譯系統的方法看這里 Allwinner & Arm 中國 & Sipeed 開源硬件 R329 SDK 上手編譯與燒錄! ,可以用 ubuntu18 ~ 20 ,問題少一點,解決方案多一點。

以 Tina Linux 為例,它使用了 openwrt linux 系統為原型,所以編譯方法是相通的,只是會多一點自己的特色。

有如下基礎用法,我這里只是簡化和說明源頭:

source build/envsetup.sh


== before all ==
- lunch:        lunch <product_name>-<build_variant>

== build project ==
- m:            Make from the top of the tree.
- mm:           Build package in the current directory, but not their dependencies.
- mmb:          Clean and build package in the current directory, but not their dependencies.
- p:            Pack from the top of the tree.
- pd:           Pack card0 from the top of the tree.
- mp:           Make and pack from the top of the tree
- mpd:          Make and pack card0 from the top of the tree
- mboot:        Build boot0 and uboot, including uboot for nor.
- mboot0:       Just build boot0.
- muboot:       Build uboot, including uboot for nor.
- muboot_nor:   Just build uboot for nor.
- mkernel:      Build kernel.
- mlibc:        Build c library.

== jump directory ==
- croot:    Jump to the top of the tree.
- cboot:    Jump to uboot.
- cboot0:   Jump to boot0.
- cdts:     Jump to device tree.
- cbin:     Jump to uboot/boot0 bin directory.
- ckernel:  Jump to kernel.
- cdevice:  Jump to target.
- ccommon:  Jump to platform common.
- cconfigs: Jump to configs of target.
- cout:     Jump to out directory of target.
- ctarget:  Jump to target of compile directory.
- crootfs:  Jump to rootfs of compile directory.
- ctoolchain: Jump to toolchain directory.
- callwinnerpk: Jump to package allwinner directory.
- ctinatest:  Jump to tinateset directory.
- godir:    Go to the directory containing a file.

== grep file ==
- cgrep:    Greps on all local C/C++ files.

命令主要就用這幾個就好。

croot 到工程根目錄, cout 同理到 out 編譯目錄。

make 編譯工程全部內容,mkernel 同理單獨編譯 kernel (mboot 同理)。

make menuconfig 和 make kernel_menuconfig 分別是配置 package 和 kernel 的所選功能的。

pack 打包當前的編譯結果成一個 img 文件,供 livesuit 工具燒錄。

全志的燒錄工具網上多,不過在 linux 開發的話要看一下這個倉庫 https://github.com/jake5253/allwinner-livesuit 或這個 https://github.com/junhuanchen/sunxi-livesuite (我改了一點說明在 ubuntu 20 上使用)。

cgrep test 是快速的 grep -rn "test" 查代碼用的。

由於硬件的 uboot 配置已經被適配過,所以可以不用特別關注啟動方面的配置,極小概率會去修改的,之后有必要會代入說明。

現在我們具備了編譯,打包,燒錄的基礎,順便說一下一些常用的注意點。

make 只在 croot 的目錄下有效,編譯主要分 uboot kernel package 三大塊的編譯。

我們通常只需要關心 package 編譯即可,編譯系統和 openwrt 一致,這個可以從網上獲取相關用法。

單包編譯 make package/allwinner/eyesee-mpp/middleware/compile

清理單包 make package/allwinner/eyesee-mpp/middleware/clean

參考資料 在openwrt上編譯一個最簡單的ipk包

具體的包描述可以看目錄下的 Makefile 和 Config.in ,如果想要加一個包,就可以通過仿照其他包加入編譯系統,甚至是從 github 獲取別人移植好的包。

例如給系統加入 libuv ,我們可以github 搜索到獲得別人加入的。

這樣一些軟件包的編譯規則就可以不用自己寫了,這是一個軟件包常用的偷懶小技巧(不是),驅動代碼有時候也可以這樣操作,但自己最好熟悉整體的編譯系統是最好的。

#
# Copyright (C) 2015-2017 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=libuv
PKG_VERSION:=1.42.0
PKG_RELEASE:=1

PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=http://dist.libuv.org/dist/v$(PKG_VERSION)/
# PKG_HASH:=61a90db95bac00adec1cc5ddc767ebbcaabc70242bd1134a7a6b1fb1d498a194
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-v$(PKG_VERSION)

PKG_MAINTAINER:=Marko Ratkaj <marko.ratkaj@sartura.hr>
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_CPE_ID:=cpe:/a:libuv_project:libuv

CMAKE_INSTALL:=1
CMAKE_BINARY_SUBDIR:=out/cmake
PKG_BUILD_PARALLEL:=1

include $(BUILD_DIR)/package.mk
include $(BUILD_DIR)/cmake.mk

define Package/libuv
  SECTION:=libs
  CATEGORY:=Libraries
  TITLE:=Cross-platform asychronous I/O library
  URL:=https://libuv.org/
  DEPENDS:=+libpthread +librt
  ABI_VERSION:=1
endef

define Package/libuv/description
 libuv is a multi-platform support library with a focus on asynchronous I/O. It
 was primarily developed for use by Node.js, but it's also used by Luvit, Julia,
 pyuv, and others.
endef

CMAKE_OPTIONS += -DBUILD_TESTING=OFF

define Build/InstallDev
	$(call Build/InstallDev/cmake,$(1))
	$(SED) 's,/usr/include,$$$${prefix}/include,g' $(1)/usr/lib/pkgconfig/libuv.pc
	$(SED) 's,/usr/lib,$$$${prefix}/lib,g' $(1)/usr/lib/pkgconfig/libuv.pc
endef

define Package/libuv/install
	$(INSTALL_DIR) $(1)/usr/lib/
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/libuv.so* $(1)/usr/lib/
endef

$(eval $(call BuildPackage,libuv))

這里會用親自演示和講解一下上面這個包是如何參與編譯和調試的,注意目錄下 Config.in 主要影響 make menuconfig 搜索到這個包,如果勾選功能就會調起 Makefile 影響具體的編譯,有一些常見的宏需要聯系上下文獲取,如 交叉編譯的 gcc ,編譯輸出的目錄,拷貝哪些資源和程序到 rootfs 下,有一些包如何和 kernel 模塊聯系,主要有純粹軟件包也有底層驅動包,還有 openwrt 提供的 luci feed 軟件源,但這個對我們的使用影響很小,因為我們不是用作路由器的 web 服務。

如果到這里了還沒有編譯出系統鏡像的,就不用繼續往下看了,基本功都不過關。

關於 Tina Linux 的 kernel 和 package 的速成用法。

這章往下的前提條件是你已經將系統編譯出來並將其燒錄系統啟動進入終端了。

先從設備樹的結點開始說起,如果你從來沒有接觸過設備樹,這里我簡單介紹一下就行。

設備樹是一個存放驅動具體配置信息的文件,其中 dtb 是 dts 的編譯產物,內核 kernel 可以通過 dts 去配置要加載的驅動的一些配置信息。

舉個例子,如果你想要給系統添加一個 RTC 設備,那么可以在 device/config/chips/v833/configs/xxxx/board.dts 中依次定義樹結點。


&twi4 {
  pcf8563: rtc@51 {
    compatible = "nxp,pcf8563";
    reg = <0x51>;
  };
};

這樣的配置意味着 kernel 在啟動的時候要去負責調驅動程序,如果驅動成功則會注冊一個 rtc 設備到 /dev/rtcX 中,可能是 /dev/rtc1 等等。

接着這個驅動會需要 twi4 的結點,因此會定義下面這個結點。

    twi4: twi@0x07081400 {
      pinctrl-0 = <&twi0_pins_a>;
      pinctrl-1 = <&twi0_pins_b>;
      status = "okay";
    };

    pinctrl@0300b000 {

      twi0_pins_a: twi0@0 {
        allwinner,pins = "PE16", "PE17";
        allwinner,pname = "twi0_scl", "twi0_sda";
        allwinner,function = "twi0";
        allwinner,muxsel = <5>;
        allwinner,drive = <1>;
        allwinner,pull = <0>;
      };

      twi0_pins_b: twi0@1 {
        allwinner,pins = "PE16", "PE17";
        allwinner,function = "io_disabled";
        allwinner,muxsel = <7>;
        allwinner,drive = <1>;
        allwinner,pull = <0>;
      };
   }

其中 twi4 對應 /dev/i2c-4 的設備,其中 twi0_pins_a 和 twi0_pins_b 表示具體的 GPIO 引腳配置。

需要注意的是,驅動配置的字符串都是可以隨意設計的,因為具體結點項的使用會在驅動代碼中通過 of_property_read_xxxx 系列函數獲取對應的配置描述,所以不同 SOC 的定義都會有所不同。

但為什么要這樣配置呢?通常 SOC 廠家會提供底層的基礎結點配置(如 sun8iw19p1.dtsi 類似於 .h 和 .c 的關系),開發的時候只需要繼承其結點配置即可。

/linux-4.9/arch/arm/boot/dts/sun8iw19p1.dtsi
/linux-4.9/arch/arm/boot/dts/sun8iw19p1-pinctrl.dtsi
/linux-4.9/arch/arm/boot/dts/sun8iw19p1-clk.dtsi

但設備樹結點具體怎么配置是要結合驅動程序來看的,可以參考我以前的這篇文章,為 AW V831 配置 spidev 模塊,使用 py-spidev 進行用戶層的 SPI 通信。,這篇文章主要交代了如何在上層和底層以及外設輸出測量做了說明和示范。

這里我可以拿一些 屏幕 、 攝像頭 、外設設備之類的做一下演示。

關於適配和測試常見的基礎外設驅動,從上層用戶空間的軟件到底層硬件寄存器之間的聯系。

有了基本的修改驅動和設備樹的能力以后,我們來主動實現一些 SDK 沒有的功能,首先要熟悉一下 SDK 的編譯框架構成,以便於我們添加代碼和模塊進入。

先從 kernel 開始說起,kernel 里的驅動代碼,我們需要掌握掛載、修改、調試它的認知。

對於 linux 的 module 模塊的基礎開發方法可以看這個倉庫linux_python_ioctl_driver,基本用法就是 insmod 和 rmmod 、lsmod,在 X86 上稍微學習一下 IOCTL 的操作接口就行,,學會加載 .ko 釋放、查看等等(這里可以簡單演示一下)。

到這里要知道內核模塊 .ko 不同於鏈接庫 .so 模塊的代碼結構以及使用方法上的差異就行,大多數時候用不着寫原始驅動,拿過來改改就行。

然后知道了這個以后,還需要知道一些調試模塊的使用,例如 dmesg 、 debugfs 和 sysfs 的使用。

如何理解呢?拿屏幕操作來說吧。某個屏幕驅動出了問題,需要確認驅動的情況。

使用 dmesg 可以查看我們在 kernel 下寫的 printk 輸出的內容,只能看到輸出的結果,不能進行交互。

想要交互,我們可以通過 debugfs 或 sysfs 與內核模塊進行文件內容的交換,例如下述的這類用法作為參考。

訪問這些文件進行讀寫就等於調用驅動代碼的這些操作,驅動在加載的時候會去創建這些結點出來供用戶空間調用和傳遞數據。

/*
 * Allwinner SoCs display driver.
 *
 * Copyright (C) 2016 Allwinner.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#if defined(CONFIG_DISP2_SUNXI_DEBUG)

#include "dev_disp_debugfs.h"

static struct dentry *my_dispdbg_root;


struct dispdbg_data {
	char command[32];
	char name[32];
	char start[32];
	char param[64];
	char info[256];
	char tmpbuf[318];
};
static struct dispdbg_data dispdbg_priv;

static void dispdbg_process(void)
{
	unsigned int start;
	int err;

	err = kstrtou32(dispdbg_priv.start, 10, &start);
	if (err) {
		pr_warn("Invalid para\n");
		return;
	}

	if (start != 1)
		return;

	if (!strncmp(dispdbg_priv.name, "layer", 5)) {
		char *p = dispdbg_priv.name + 5;
		char *token = p;
		unsigned int disp, chan, id;
		struct disp_layer *lyr = NULL;

		pr_warn("%s,%s\n", dispdbg_priv.command, dispdbg_priv.name);

		token = strsep(&p, " ");
		if (!token) {
			pr_warn("Invalid para\n");
			return;
		}
		err = kstrtou32(token, 10, &disp);
		if (err) {
			pr_warn("Invalid para\n");
			return;
		}
		token = strsep(&p, " ");
		if (!token) {
			pr_warn("Invalid para\n");
			return;
		}
		err = kstrtou32(token, 10, &chan);
		if (err) {
			pr_warn("Invalid para\n");
			return;
		}
		token = strsep(&p, " ");
		if (!token) {
			pr_warn("Invalid para\n");
			return;
		}
		err = kstrtou32(token, 10, &id);
		if (err) {
			pr_warn("Invalid para\n");
			return;
		}
		lyr = disp_get_layer(disp, chan, id);
		if (lyr == NULL) {
			sprintf(dispdbg_priv.info, "get %s fail!",
				dispdbg_priv.name);
			return;
		}
		if (!strncmp(dispdbg_priv.command, "enable", 6)) {
			/* lyr->enable(lyr); */
		} else if (!strncmp(dispdbg_priv.command, "disable", 7)) {
			/* lyr->disable(lyr); */
		} else if (!strncmp(dispdbg_priv.command, "getinfo", 7)) {
			lyr->dump(lyr, dispdbg_priv.info);
		} else {
			sprintf(dispdbg_priv.info,
				"not support command for %s!",
				dispdbg_priv.name);
			return;
		}

/home/juwan/v831/lichee/linux-4.9/drivers/video/fbdev/sunxi/disp2/disp/dev_disp_debugfs.c

意味着你可以通過這個接口產生的文件和內核進行數據交換,這層是屬於直接顯示驅動的,還有顯示框架的功能可以使用,例如下圖。

這里只是對屏幕顯示驅動的調試做個一個示范,其他的也同理,細節可以參考 D1_Linux_Display_開發指南.pdf 一文。

如果這一切都十分正常的話,還是出現了錯誤,我們就需要檢查一下寄存器了,這時候我們可以這樣做。

這里我拿 定義 PWM 到綁定 GPIO 的寄存器配置做一個簡單的示范。

首先知道用戶空間提供的讀寫寄存器的接口,這個也和上述一致,只是底層開了這類接口給到 sysfs 了,可以參考 sunxi_dump_reg使用說明書.pdf 。

我拿一個調試 pwm 設備的時候案例來舉例,當把設備驅動、結點配置好了,驅動也寫好了,當輸出仍然沒有反應,最終發現是寄存器的引腳 function 沒有選到 pwm 而是 disable 的情況。

pwm 的控制方法如下:

// pwm debug
cat /sys/kernel/debug/pwm

// pwm設置
echo 2 > /sys/class/pwm/pwmchip0/export && echo 100000 > /sys/class/pwm/pwmchip0/pwm2/period && echo 20000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle && echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable

echo 3 > /sys/class/pwm/pwmchip0/export && echo 100000 > /sys/class/pwm/pwmchip0/pwm3/period && echo 20000 > /sys/class/pwm/pwmchip0/pwm3/duty_cycle && echo 1 > /sys/class/pwm/pwmchip0/pwm3/enable

現在我們來查閱 V833/V831_Datasheet_V1.1(For 索智).pdf 獲取對應寄存器的地址信息,把輸出的結果翻譯過來,有時候發現某個設備的控制操作存在問題,直接對寄存器寫入做最終配置,就可以檢查到為什么了。

首先定位 GPIO 的基地址:0x0300B000

接着定位到具有 pwm 功能的引腳 PD PH 組,如果是 PH 則地址偏移為 0x0300B000 + 0xfc (PH_CFG0) = 0x0300B0FC。

可以從該寄存器得知 PH 0:7 的功能選擇情況,從圖中可知其 四位 bit 為一組,現在我們可以用上述的方法對其進行修改和寫入,從而實現的開和關,甚至是重新配置。

cd /sys/class/sunxi_dump/
echo 0x0300B0FC > dump
cat dump

root@sipeed:/# cd /sys/class/sunxi_dump/
root@sipeed:/sys/class/sunxi_dump# echo 0x0300B0FC > dump
root@sipeed:/sys/class/sunxi_dump# cat dump
0x74471255
root@sipeed:/sys/class/sunxi_dump# 

# 0x74471255 > 
# 0b1110100010001110001001001010101 > 
# 0b 0111 0100 0100 0111 0001 0010 0101 0101 > 
# 0b 0111 0100 0100 0111 0001 0010 0111 0111 >
# 0b1110100010001110001001001110111 >
# 0x74471277
# 關閉最后兩個 io 引腳的 function 的使用

cd /sys/class/sunxi_dump
echo 0x0300B0FC 0x74471277 > write
cat write

root@sipeed:/# cd /sys/class/sunxi_dump
root@sipeed:/sys/class/sunxi_dump# echo 0x0300B0FC 0x74471277 > write
root@sipeed:/sys/class/sunxi_dump# cat write
reg         to_write    after_write
0x0300b0fc  0x74471255  0x74471277 
root@sipeed:/sys/class/sunxi_dump# 

以上上述操作就是打印寄存器和寫入方法,通過這種方式可以跳過驅動和用戶程序,直接將最終的寄存器配置結果設置上。

提供一種簡單的方法

cat /sys/kernel/debug/pinctrl/pio/pinmux-pins
root@sipeed:/# cat /sys/kernel/debug/pinctrl/pio/pinmux-pins 
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 64 (PC0): sdc2 (GPIO UNCLAIMED) function sdc2 group PC0
pin 65 (PC1): sdc2 (GPIO UNCLAIMED) function sdc2 group PC1
pin 66 (PC2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 67 (PC3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 68 (PC4): sdc2 (GPIO UNCLAIMED) function sdc2 group PC4
pin 69 (PC5): sdc2 (GPIO UNCLAIMED) function sdc2 group PC5
pin 70 (PC6): sdc2 (GPIO UNCLAIMED) function sdc2 group PC6
pin 71 (PC7): sdc2 (GPIO UNCLAIMED) function sdc2 group PC7
pin 72 (PC8): sdc2 (GPIO UNCLAIMED) function sdc2 group PC8
pin 73 (PC9): sdc2 (GPIO UNCLAIMED) function sdc2 group PC9
pin 74 (PC10): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 75 (PC11): sdc2 (GPIO UNCLAIMED) function sdc2 group PC11
pin 96 (PD0): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 97 (PD1): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD1
pin 98 (PD2): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD2
pin 99 (PD3): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD3
pin 100 (PD4): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD4
pin 101 (PD5): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD5
pin 102 (PD6): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD6
pin 103 (PD7): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD7
pin 104 (PD8): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD8
pin 105 (PD9): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 106 (PD10): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 107 (PD11): (MUX UNCLAIMED) pio:107
pin 108 (PD12): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 109 (PD13): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 110 (PD14): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 111 (PD15): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 112 (PD16): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 113 (PD17): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 114 (PD18): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD18
pin 115 (PD19): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD19
pin 116 (PD20): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 117 (PD21): soc@03000000:lcd0@01c0c000 (GPIO UNCLAIMED) function lcd0 group PD21
pin 118 (PD22): soc@03000000:pwm9@0300a000 (GPIO UNCLAIMED) function pwm9 group PD22
pin 128 (PE0): csi1 (GPIO UNCLAIMED) function csi1 group PE0
pin 129 (PE1): sensor1 (GPIO UNCLAIMED) function csi_mclk1 group PE1
pin 130 (PE2): csi1 (GPIO UNCLAIMED) function csi1 group PE2
pin 131 (PE3): csi1 (GPIO UNCLAIMED) function csi1 group PE3
pin 132 (PE4): csi1 (GPIO UNCLAIMED) function csi1 group PE4
pin 133 (PE5): csi1 (GPIO UNCLAIMED) function csi1 group PE5
pin 134 (PE6): csi1 (GPIO UNCLAIMED) function csi1 group PE6
pin 135 (PE7): csi1 (GPIO UNCLAIMED) function csi1 group PE7
pin 136 (PE8): csi1 (GPIO UNCLAIMED) function csi1 group PE8
pin 137 (PE9): csi1 (GPIO UNCLAIMED) function csi1 group PE9
pin 138 (PE10): csi1 (GPIO UNCLAIMED) function csi1 group PE10
pin 139 (PE11): csi1 (GPIO UNCLAIMED) function csi1 group PE11
pin 140 (PE12): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 141 (PE13): (MUX UNCLAIMED) pio:141
pin 142 (PE14): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 143 (PE15): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 144 (PE16): twi4 (GPIO UNCLAIMED) function twi0 group PE16
pin 145 (PE17): twi4 (GPIO UNCLAIMED) function twi1 group PE17
pin 146 (PE18): uart2 (GPIO UNCLAIMED) function uart2 group PE18
pin 147 (PE19): uart2 (GPIO UNCLAIMED) function uart2 group PE19
pin 148 (PE20): (MUX UNCLAIMED) pio:148
pin 149 (PE21): (MUX UNCLAIMED) pio:149
pin 160 (PF0): sdc0 (GPIO UNCLAIMED) function sdc0 group PF0
pin 161 (PF1): sdc0 (GPIO UNCLAIMED) function sdc0 group PF1
pin 162 (PF2): sdc0 (GPIO UNCLAIMED) function sdc0 group PF2
pin 163 (PF3): sdc0 (GPIO UNCLAIMED) function sdc0 group PF3
pin 164 (PF4): sdc0 (GPIO UNCLAIMED) function sdc0 group PF4
pin 165 (PF5): sdc0 (GPIO UNCLAIMED) function sdc0 group PF5
pin 166 (PF6): (MUX UNCLAIMED) pio:166
pin 192 (PG0): sdc1 (GPIO UNCLAIMED) function io_disabled group PG0
pin 193 (PG1): sdc1 (GPIO UNCLAIMED) function io_disabled group PG1
pin 194 (PG2): sdc1 (GPIO UNCLAIMED) function io_disabled group PG2
pin 195 (PG3): sdc1 (GPIO UNCLAIMED) function io_disabled group PG3
pin 196 (PG4): sdc1 (GPIO UNCLAIMED) function io_disabled group PG4
pin 197 (PG5): sdc1 (GPIO UNCLAIMED) function io_disabled group PG5
pin 198 (PG6): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 199 (PG7): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 224 (PH0): uart3 (GPIO UNCLAIMED) function uart3 group PH0
pin 225 (PH1): uart3 (GPIO UNCLAIMED) function uart3 group PH1
pin 226 (PH2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 227 (PH3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 228 (PH4): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 229 (PH5): twi2 (GPIO UNCLAIMED) function twi2 group PH5
pin 230 (PH6): twi2 (GPIO UNCLAIMED) function twi2 group PH6
pin 231 (PH7): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 232 (PH8): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 233 (PH9): uart0 (GPIO UNCLAIMED) function uart0 group PH9
pin 234 (PH10): uart0 (GPIO UNCLAIMED) function uart0 group PH10
pin 235 (PH11): spi1 (GPIO UNCLAIMED) function spi1 group PH11
pin 236 (PH12): spi1 (GPIO UNCLAIMED) function spi1 group PH12
pin 237 (PH13): spi1 (GPIO UNCLAIMED) function spi1 group PH13
pin 238 (PH14): spi1 (GPIO UNCLAIMED) function spi1 group PH14
pin 239 (PH15): spi1 (GPIO UNCLAIMED) function spi1 group PH15
pin 256 (PI0): sensor0 (GPIO UNCLAIMED) function csi_mclk0 group PI0
pin 257 (PI1): twi1 (GPIO UNCLAIMED) function twi1 group PI1
pin 258 (PI2): twi1 (GPIO UNCLAIMED) function twi1 group PI2
pin 259 (PI3): (MUX UNCLAIMED) pio:259
pin 260 (PI4): (MUX UNCLAIMED) pio:260
root@sipeed:/# cat /sys/kernel/debug/pinctrl/r_pio/pinmux-pins 
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 352 (PL0): twi0 (GPIO UNCLAIMED) function s_twi0 group PL0
pin 353 (PL1): twi0 (GPIO UNCLAIMED) function s_twi0 group PL1
pin 354 (PL2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 355 (PL3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 356 (PL4): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 357 (PL5): (MUX UNCLAIMED) (GPIO UNCLAIMED)
root@sipeed:/# 

其他寄存器同理,有時候設備樹配置了但沒有起作用的時候,不妨先用這個方法判斷一下最底層的配置情況。

拓展介紹給 SDK 加入沒有的外設驅動調試和開發流程

這些事情常發生在換料的時候,例如屏幕、觸摸、攝像頭、WIFI,這些在 2020 ~ 2022 年之間價格波動較大的物料,所以也要掌握具體的適配和更新的能力。

拿到供應商的原料代碼,大多數可能是用 MCU 、FPGA 等其他平台寫的,也可能沒有代碼,就一些寄存器手冊,這時候怎么寫?

例如平時拿到的資料可能是下面這樣的:

某屏幕的配置資料

	.hfp = 30;  
	.hbp = 30;
	.hsync = 10;
	.horizontal_active_pixel = 480;
	.vfp = 20;
	.vbp = 20;
	.vsync = 10;
  .vertical_active_line = 854;

PCLK=30MHZ

WriteComm (0xE8);
WriteData (0x00);
WriteData (0x0E);

WriteComm (0xFF);
WriteData (0x77);
WriteData (0x01);
WriteData (0x00);
WriteData (0x00);
WriteData (0x00);

WriteComm (0x11);
Delayms (120);

WriteComm (0xFF);
WriteData (0x77);
WriteData (0x01);
WriteData (0x00);
WriteData (0x00);
WriteData (0x13);

WriteComm (0xE8);
WriteData (0x00);
WriteData (0x0C);
Delayms (10);

某攝像頭的配置資料

static struct regval_list sensor_1080p20_regs[] = {

//window_size=1920*1080 mipi@2lane
//mclk=24mhz,mipi_clk=594Mbps
//pixel_line_total=2640,line_frame_total=1125
//row_time=35.55us,frame_rate=25fps
    ///////////////////////////////////////////////////////////////
{0xfe, 0xf0},
{0xfe, 0xf0},
{0xfe, 0xf0},
{0xfe, 0x00},
{0xf2, 0x00},//[1]I2C_open_ena [0]pwd_dn
{0xf3, 0x00},//0f//00[3]Sdata_pad_io [2:0]Ssync_pad_io 
{0xf4, 0x36},//[6:4]pll_ldo_set
{0xf5, 0xc0},//[7]soc_mclk_enable [6]pll_ldo_en [5:4]cp_clk_sel [3:0]cp_clk_div

基本上什么也做不了,我們要從這里提取我們需要的訊息,驅動 IC 型號和 datasheet ,模組的規格書等訊息。

如果是屏幕要確定驅動接口 im 的選擇,確認通信的方式,並行 I8080 還是 mipi dsi 還是 spi 等其他的串行,進一步再確認輸出圖像的格式,RGB565 、 RGB888 等,如何調試屏幕可以看我的這篇 理解 LCD 屏幕的驅動原理與調試過程,示例的驅動 IC 為 GC9308 ,展示整個屏幕的驅動過程。 和 D1_Tina_Linux_Display_開發指南.pdf ,主要是軟件部分,硬件電路部分就讓硬件去檢查就行,自己優先確認 RESET 和 CLK 時序沒問題就行。

可以參見 ./lichee/linux-4.9/drivers/video/fbdev/sunxi/disp2/disp/lcd/st7701s.c 為例。

如果是攝像頭則要先確認 RESET 、 PWDN 、 I2C addr 、 MCLK 時鍾的基礎信息,再來確認配置流程和輸出圖像的格式,RAW BGGR 、YUV422 等。

可以參見 ./lichee/linux-4.9/drivers/video/fbdev/sunxi/disp2/disp/lcd/st7701s.c 為例。

上述兩者只是簡單介紹了通常調試時的基本觀念,接下來就結合 Tina Linux 的底層接口來說明問題。

可以參見 ./lichee/linux-4.9/drivers/media/platform/sunxi-vin/modules/sensor/gc0328.c 為例。

在不同平台上寫任何驅動的時候,我們都需要對驅動框架有一個整體的認識,以屏幕和攝像頭兩個典型為例。

202x 年后 sunxi 的顯示驅動框架以下述為概括

通常來說,一些簡單的場合我們可以直接使用 framebuffer 驅動產生的 /dev/fb0 進行屏幕的繪圖,但經過了 display engine 框架后就具備了圖層的概念,也比 fb 多出更多圖像處理功能。

知道這個以后,我們主要關注的是也僅僅只是驅動層部分,只需要將其對接進去框架就行,不管它是走 fb 還是走 de 都只是上層驅動接管的,用戶再去調用就行。

所以移植測試的時候可以直接使用 fb 或 debugfs 測試,而不用擔心用戶層通過 mpp 的 vo 訪問了 hwdisplay 后調用的 de 合成后顯示是否會存在不一致的問題,lcd 驅動只需要完成驅動屏幕的配置即可。

所以根據實際情況進行寄存器的配置就行,具體要看 tcon 硬件資源的配置了,以及如何支持 lcd(hv/lvds/cpu/dsi) 的顯示,主要看 lcd_if 的定義,這個可以從 LCD調試使用說明書.pdf 中得知。

至於為什么支持和支持多少,則要從 datasheet 中得知。

知道了底層的基本情況(不需要細看或背下來),我們可以在參考驅動中取得相應的設備樹配置。

/*
 * drivers/video/fbdev/sunxi/disp2/disp/lcd/s2003t46/s2003t46g.c
 *
 * Copyright (c) 2007-2018 Allwinnertech Co., Ltd.
 * Author: zhengxiaobin <zhengxiaobin@allwinnertech.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *
lcd_used            = <1>;

lcd_driver_name     = "st7789v_cpu";

lcd_backlight       = <150>;

lcd_if              = <1>;
lcd_x               = <240>;
lcd_y               = <320>;
lcd_width           = <108>;
lcd_height          = <64>;
lcd_dclk_freq       = <18>;

lcd_pwm_used        = <1>;
lcd_pwm_ch          = <8>;
lcd_pwm_freq        = <50000>;
lcd_pwm_pol         = <1>;
lcd_pwm_max_limit   = <255>;

lcd_hbp             = <60>;
lcd_ht              = <298>;
lcd_hspw            = <30>;
lcd_vbp             = <340>;
lcd_vt              = <336>;
lcd_vspw            = <2>;

lcd_frm             = <1>;
lcd_io_phase        = <0x0000>;
lcd_hv_clk_phase    = <0>;
lcd_hv_sync_polarity= <0>;
lcd_hv_data_polarity = <1>;
lcd_gamma_en        = <0>;
lcd_bright_curve_en = <0>;
lcd_cmap_en         = <0>;

lcdgamma4iep        = <22>;
lcd_cpu_mode        = <1>;
lcd_cpu_te          = <1>;
lcd_cpu_if	    = <12>;
lcd_hv_if	    	= <0>;
lcd_hv_srgb_seq	    = <0>;
lcd_rb_swap			= <0>;

lcd_gpio_0 = <&pio PE 16 1 0 3 0>;
lcd_gpio_1 = <&pio PE 17 1 0 3 0>;

pinctrl-0 = <&rgb8_pins_a>;
pinctrl-1 = <&rgb8_pins_b>;

 */

#include "st7789v_cpu.h"

這時候檢查 menuconfig 配置並加入到設備樹里就行了,如果發現沒有反應,可以看 dmesg 或 直接打印 tcon 的寄存器來確認配置情況,屏幕驅動基本就到這里了,怎么測試就用 fbdisplay 就行,或者類似的 fb 顯示。

關於攝像頭這里我簡單介紹一下基礎的框架,由於 V831 / V833 是提供了 AW MPP IPC 媒體處理軟件封裝,可以大幅度的使用這些外設,如 vi vo ai ao isp ise 等,但並不是說脫離了 mpp 就不能用。

例如 sunxi 攝像頭的 vin 驅動框架如下圖:(上一代為 vfe 里面還可以看到代碼) ,細節可以參見 SUNXI-VIN模塊使用文檔.pdf 。

主要框架為 V4L2 框架,效果就是可以得到 /dev/videoX 設備進行攝像頭數據的讀取,可以通過 camerademo 或 sample_virvi 來調試攝像頭輸入,前者直接訪問設備,后者經過 mpp 框架。

介紹一下攝像頭的設備樹

sensor0 和 sensor1 表示多個 sensor 通常為 前 和 后 攝像頭

其中 sensorX_reset sensor0_pwdn 由 sensor_power.ko 統一管理,啟動時經過 /etc/initd.d/S0mpp 統一加載注冊。

sensor0_twi_cci_id 為綁定的 twi(i2c) 或 cci 用於配置攝像頭的通路,如 sensor0_twi_cci_id = <1>; 表示 使用 twi1 這個 /dev/i2c-1 的設備去訪問攝像頭,所以你可以用 i2cdetect -y 1 來掃攝像頭設備。

      sensor0:sensor@0 {
        device_type = "sensor0";
        sensor0_twi_cci_id = <1>;
        // sensor0_mname = "sp2305_mipi";
        // sensor0_twi_addr = <0x78>;

        sensor0_mname = "gc2093_mipi";
        sensor0_twi_addr = <0x6e>; // 0x6e 0xfd
        sensor0_phase = <1>; // pwdn
        
        sensor0_mclk_id = <0>;
        sensor0_pos = "rear";
        sensor0_isp_used = <1>;
        sensor0_fmt = <1>;
        sensor0_stby_mode = <0>;
        sensor0_vflip = <1>;
        sensor0_hflip = <1>;
        sensor0_iovdd-supply = <>; // sensor0_iovdd-supply = <&reg_aldo2>;
        sensor0_iovdd_vol = <1800000>;
        sensor0_avdd-supply = <>;
        sensor0_avdd_vol = <2800000>;
        sensor0_dvdd-supply = <>; // sensor0_dvdd-supply = <&reg_dldo2>;
        sensor0_dvdd_vol = <1200000>;
        sensor0_power_en = <>;
        sensor0_reset = <&pio PI 3 1 0 1 0>;
        sensor0_pwdn = <&pio PI 4 1 0 1 0>;
        status	= "okay";
      };

經過了攝像頭的基礎配置后,我們需要注意一下,攝像頭的 ko 模塊是外部注入到系統目錄的(可見於 ./target/allwinner/xxxx/modules.mk ),而非包含在 kernel 內部的,好處就是可以放很多個攝像頭模塊,而不用重燒系統,更換設備樹即可,如何更新設備樹看 關於 V831 / V833 Tina Linux 更新設備樹( dts > dtb )的用法


define KernelPackage/vin-v4l2
  SUBMENU:=$(VIDEO_MENU)
  TITLE:=Video input support (staging)
  DEPENDS:=
  FILES:=$(LINUX_DIR)/drivers/media/v4l2-core/videobuf2-core.ko
  FILES+=$(LINUX_DIR)/drivers/media/v4l2-core/videobuf2-dma-contig.ko
  FILES+=$(LINUX_DIR)/drivers/media/v4l2-core/videobuf2-memops.ko
  FILES+=$(LINUX_DIR)/drivers/media/v4l2-core/videobuf2-v4l2.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/vin_io.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/vin_v4l2.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor/gc2053_mipi.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor/gc2093_mipi.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor/sp2305_mipi.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor/gc0328.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor/gc2145.ko
  FILES+=$(LINUX_DIR)/drivers/media/platform/sunxi-vin/modules/sensor_power/sensor_power.ko
#  FILES+=$(LINUX_DIR)/drivers/video/fbdev/sunxi/disp2/hdmi2/hdmi20.ko
  AUTOLOAD:=$(call AutoProbe,videobuf2-core videobuf2-dma-contig videobuf2-memops videobuf2-v4l2 vin_io vin_v4l2 sp2305_mipi gc2145 sensor_power)
endef

define KernelPackage/vin_v4l2/description
 Kernel modules for video input support
endef

$(eval $(call KernelPackage,vin-v4l2))

經過這個配置以后,就會在 build 的時候將其打包到 lib/modules/4.9/ 目錄下,然后在用戶空間注冊就行,但要記得在 menuconfig 選中這類 kmod_xxx 模塊,不同於 kernel_menuconfig ,它的用途是從 kernel 中提取需要的模塊(modules)。

接下來介紹一下攝像頭的基礎配置結構代碼參考:

/*
 * A V4L2 driver for Raw cameras.
 *
 * Copyright (c) 2017 by Allwinnertech Co., Ltd.  http://www.allwinnertech.com
 *
 * Authors:  Chen weihong <chenweihong@allwinnertech.com>
 *
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/clk.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mediabus.h>
#include <linux/io.h>

#include "camera.h"
#include "sensor_helper.h"

MODULE_AUTHOR("zhj");
MODULE_DESCRIPTION("A low-level driver for GC2093sensors");
MODULE_LICENSE("GPL");

#define MCLK              (24*1000*1000)
#define V4L2_IDENT_SENSOR  0x2093

//define the registers
#define EXP_HIGH		0xff
#define EXP_MID			0x03
#define EXP_LOW			0x04

#define GAIN_HIGH		0xff
#define GAIN_LOW		0x24
/*
 * Our nominal (default) frame rate.
 */
#define ID_REG_HIGH		0x03f0
#define ID_REG_LOW		0x03f1
#define ID_VAL_HIGH		((V4L2_IDENT_SENSOR) >> 8)
#define ID_VAL_LOW		((V4L2_IDENT_SENSOR) & 0xff)
#define SENSOR_FRAME_RATE 30

#define HDR_RATIO 16
#define short_exp_max 124
/*
 * The GC2093i2c address
 */
#define I2C_ADDR 0x6e

#define SENSOR_NUM 0x2
#define SENSOR_NAME "gc2093_mipi"
#define SENSOR_NAME_2 "gc2093_mipi_2"

有很多同類參考,主要看 sensor 廠家(gc ov imx sc)和 輸出圖像類型 (RAW YUV RGB),可以不拘泥於實現的平台,關鍵看寄存器配置(static struct regval_list sensor_default_regs)和增益(gain)曝光(exp)的映射函數的實現,大多數 sensor 的實現都是亂寫的,但目的就是要函數輸入的值對應 sensor 的曝光呈現小到大的線性關系。

這里我演示一下幾處關鍵注意的地方:


static int sensor_init(struct v4l2_subdev *sd, u32 val)
{
	int ret;
	struct sensor_info *info = to_state(sd);

	sensor_dbg("sensor_init\n");

	/*Make sure it is a target sensor */
	ret = sensor_detect(sd);
	if (ret) {
		sensor_err("chip found is not an target chip.\n");
		return ret;
	}

	info->focus_status = 0;
	info->low_speed    = 0;
	info->width        = 1920;
	info->height       = 1080;
	info->hflip        = 0;
	info->vflip        = 0;
	info->gain         = 0;
	info->exp          = 0;

	info->tpf.numerator      = 1;
	info->tpf.denominator    = 25;	/* 30fps */
	info->preview_first_flag = 1;
	return 0;
}

/*
 * Store information about the video data format.
 */
static struct sensor_format_struct sensor_formats[] = {
	{
		.desc      = "Raw RGB Bayer",
		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, /*.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, */
		.regs      = sensor_fmt_raw,
		.regs_size = ARRAY_SIZE(sensor_fmt_raw),
		.bpp       = 1
	},
};
#define N_FMTS ARRAY_SIZE(sensor_formats)

static struct sensor_win_size sensor_win_sizes[] = {
	#if 0
	{
		.width      = 1920,
		.height     = 1080,
		.hoffset    = 0,
		.voffset    = 0,
		.hts        = 2900,
		.vts        = 1350,
		.pclk       = 97.9 * 1000 * 1000,
		.mipi_bps   = 391.5 * 1000 * 1000,
		.fps_fixed  = 25,
		.bin_factor = 1,
		.intg_min   = 1 << 4,
		.intg_max   = 1125 << 4,
		.gain_min   = 1 << 4,
		.gain_max   = 110 << 4,
		.regs       = sensor_1920x1080p30_regs,
		.regs_size  = ARRAY_SIZE(sensor_1920x1080p30_regs),
		.set_size   = NULL,
		.top_clk    = 300*1000*1000,
		.isp_clk    = 297*1000*1000,
	},
	#endif
	{
		.width      = 1920,
		.height     = 1080,
		.hoffset    = 0,
		.voffset    = 0,
		.hts        = 2816,
		.vts        = 1250,
		.pclk       = 176 * 1000 * 1000,
		.mipi_bps   = 704 * 1000 * 1000,
		.fps_fixed  = 25,
		.bin_factor = 1,
		.if_mode    = MIPI_VC_WDR_MODE,
		.wdr_mode   = ISP_DOL_WDR_MODE,
		.intg_min   = 1 << 4,
		.intg_max   = 1250 << 4,
		.gain_min   = 1 << 4,
		.gain_max   = 48 << 4,
		.regs       = sensor_1920x1080p30_wdr_regs,
		.regs_size  = ARRAY_SIZE(sensor_1920x1080p30_wdr_regs),
		.set_size   = NULL,
		.top_clk    = 300*1000*1000,
		.isp_clk    = 351*1000*1000,
	},
};

/*
 * Code for dealing with controls.
 * fill with different sensor module
 * different sensor module has different settings here
 * if not support the follow function , retrun -EINVAL
 */

static int sensor_g_exp(struct v4l2_subdev *sd, __s32 *value)
{
	struct sensor_info *info = to_state(sd);
	*value = info->exp;
	sensor_dbg("sensor_get_exposure = %d\n", info->exp);
	return 0;
}

static int sensor_s_exp(struct v4l2_subdev *sd, unsigned int exp_val)
{
	struct sensor_info *info = to_state(sd);
	
	int tmp_exp_val = exp_val / 16;
	int exp_short = 0;
    unsigned int intt_long_h, intt_long_l,intt_short_h,intt_short_l;
	
	if (info->isp_wdr_mode == ISP_DOL_WDR_MODE) 
	{
       //sensor_dbg("Sensor in WDR mode, HDR_RATIO = %d\n", HDR_RATIO);
       if(tmp_exp_val<1*HDR_RATIO)
	   {
	       tmp_exp_val=1*HDR_RATIO ;
	   }
	   
       if(tmp_exp_val>1100)
	   {
		   tmp_exp_val=1100;
	   }
       
		exp_short = tmp_exp_val / HDR_RATIO;
		
	    intt_long_l = tmp_exp_val & 0xff;
	    intt_long_h = (tmp_exp_val >> 8) & 0x3f;
	
	    intt_short_l = exp_short & 0xff;
	    intt_short_h = (exp_short >> 8) & 0x3f;

		sensor_write(sd,0x0003, intt_long_h);
		sensor_write(sd,0x0004, intt_long_l);
		sensor_print("sensor_set_long_exp 20210302 = %x line Done!\n", tmp_exp_val);
		sensor_write(sd,0x0001, intt_short_h);
		sensor_write(sd,0x0002, intt_short_l);
		sensor_print("sensor_set_short_exp 20210302 = %x line Done!\n", exp_short);
	} 
	else 
	{
		sensor_dbg("exp_val:%d\n", exp_val);
		sensor_write(sd, 0x0003, (tmp_exp_val >> 8) & 0xFF);
		sensor_write(sd, 0x0004, (tmp_exp_val & 0xFF));
	}

	info->exp = exp_val;
	return 0;
}

static int sensor_g_gain(struct v4l2_subdev *sd, __s32 *value)
{
	struct sensor_info *info = to_state(sd);
	*value = info->gain;
	sensor_print("sensor_get_gain 20210302 = %x\n", info->gain);
	return 0;
}

vin 的 sensor_power 有兩處地方,mpp 方面使用 ./linux-4.9/drivers/media/platform/sunxi-vin/modules/sensor_power/sensor_power.c 自行管理,也可以直接的 V4L2 驅動內部的 static int sensor_power(struct v4l2_subdev *sd, int on) 函數進行管理,所以要注意 PWDN 和 RESET 的方向和執行的主體。

static int sensor_power_on(struct sensor_power_dev *sensor_power)
{
	if (sensor_power->id == 0 || sensor_power->id == 1) { /* sensor0 or sensor1 power on */
		sensor_set_pmu_channel(sensor_power, IOVDD, ON);
		sensor_set_pmu_channel(sensor_power, DVDD, ON);
		sensor_set_pmu_channel(sensor_power, AVDD, ON);
		usleep_range(1000, 1200);

		sensor_gpio_set_status(sensor_power, RESET, 1);
		sensor_gpio_set_status(sensor_power, PWDN, 1);
		sensor_gpio_write(sensor_power, RESET, SENSOR_GPIO_HIGH);
    
		sensor_gpio_write(sensor_power, PWDN, (sensor_power->phase) ? SENSOR_GPIO_HIGH : SENSOR_GPIO_LOW);
		
    usleep_range(1000, 1200);

		sensor_set_mclk(sensor_power, ON);
		vin_set_mclk_freq(sensor_power, sensor_power->mclk.frequency);
		usleep_range(1000, 1200);
	}
	return 0;
};

static void sensor_power_off(struct sensor_power_dev *sensor_power)
{
	if (sensor_power->id == 0 || sensor_power->id == 1) { /* sensor0 or sensor1 power off */
		sensor_set_pmu_channel(sensor_power, IOVDD, OFF);
		sensor_set_pmu_channel(sensor_power, DVDD, OFF);
		sensor_set_pmu_channel(sensor_power, AVDD, OFF);
		sensor_gpio_set_status(sensor_power, RESET, 1);
		sensor_gpio_set_status(sensor_power, PWDN, 1);
		sensor_gpio_write(sensor_power, RESET, SENSOR_GPIO_LOW);

		sensor_gpio_write(sensor_power, PWDN, (sensor_power->phase) ? SENSOR_GPIO_LOW : SENSOR_GPIO_HIGH);
		
		sensor_set_mclk(sensor_power, OFF);
		usleep_range(1000, 1200);
	}
}

最后在進行 ISP 調試前先確認輸出的是 RAW 圖【小技巧】如何從 datasheet 中得知自己的 sensor 是什么 RAW 輸出 ,還有 增益 曝光函數要檢查一下邊界,否則會出現 ISP 控制時不匹配的情況,至於 ISP 如何調試,可以參考 攝像頭 ISP 調試的經驗之談(以全志 AW hawkview 為例),該文只是介紹了基礎,但具體如何調試我需要手把手演示和說明。

大部分情況下驅動外設都是修改適配一下,而不需要重造輪子,但要結合上層來進行調試,如觸摸屏、攝像頭、按鍵方面的驅動要通過上層程序接收輸入。

當然很新的設計可能要重新設計框架,這種就比較少見了,本文只是速成培訓教程。

MPP 框架的使用和調用,我會另外再出一篇文檔。

總結一下

到這里為止,我們應該能夠學會和掌握修改 SDK 的能力了,但源碼的細節還有框架的使用,以及一些芯片的資源的細節就需要自己琢磨琢磨了,如果有必要可以直接和我說需要什么資料或者什么部分看不懂,我會在資料里面補充的。

注意,本次培訓材料不是教你如何寫代碼,只是向你介紹了全志現在的 Tina Linux 的整體情況,如果要加功能或修 bug 應該從哪些地方下手,其中代碼上很細節的部分,可能親自演示一下要比這樣整理寫出來更好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM