[TOC]
驱动模块
Interrupt
框架支持
U-Boot 原生代码没有中断框架,RK 自己实现了一套用于支持 GICv2/v3,默认使能。
目前用到中断的场景:
- Pwrkey:U-Boot 充电时 CPU 会进入低功耗休眠,需要通过Pwrkey 中断唤醒 CPU;
- Timer:U-Boot 充电和测试用例中会用到 Timer 中断;
- Debug:使能 CONFIG_ROCKCHIP_DEBUGGER 调试功能;
配置:
CONFIG_IRQ
CONFIG_GICV2
CONFIG_GICV3
框架代码:
./drivers/irq/irq-gpio-switch.c
./drivers/irq/irq-gpio.c
./drivers/irq/irq-generic.c
./drivers/irq/irq-gic.c
./drivers/irq/virq.c
./include/irq-generic.h
相关接口
// CPU本地中断开关
void enable_interrupts(void);
int disable_interrupts(void);
// GPIO转换成中断号
int gpio_to_irq(struct gpio_desc *gpio);
int phandle_gpio_to_irq(u32 gpio_phandle, u32 pin);
int hard_gpio_to_irq(unsigned gpio);
// 注册/释放中断回调
void irq_install_handler(int irq, interrupt_handler_t *handler, void *data);
void irq_free_handler(int irq);
// 使能/关闭中断
int irq_handler_enable(int irq);
int irq_handler_disable(int irq);
// 中断触发类型
int irq_set_irq_type(int irq, unsigned int type);
申请 IRQ
- 拥有独立硬件中断号的外设不需要额外转换,例如:pwm, timer等。
- GPIO 的 pin 脚没有独立的硬件中断号,需要额外转换申请。
一共有 3 种方式申请 GPIO 的 pin 脚中断号:
(1)传入 struct gpio_desc 结构体
// 此方法可以动态解析dts配置,比较灵活、常用。
int gpio_to_irq(struct gpio_desc *gpio);
范例:
battery {
compatible = "battery,rk817";
......
dc_det_gpio = <&gpio2 7 GPIO_ACTIVE_LOW>;
......
};
struct gpio_desc dc_det;
int ret, irq;
ret = gpio_request_by_name_nodev(dev_ofnode(dev), "dc_det_gpio", 0,
&dc_det, GPIOD_IS_IN);
// 为了示例简单,省去返回值判断
if (!ret) {
irq = gpio_to_irq(&dc_det);
irq_install_handler(irq, ...);
irq_set_irq_type(irq, IRQ_TYPE_EDGE_FALLING);
irq_handler_enable(irq);
}
(2)传入 gpio 的 phandle 和 pin
// 此方法可以动态解析dts配置,比较灵活、常用。
int phandle_gpio_to_irq(u32 gpio_phandle, u32 pin);
范例(rk817 的中断引脚为 GPIO0_A7):
rk817: pmic@20 {
compatible = "rockchip,rk817";
reg = <0x20>;
......
interrupt-parent = <&gpio0>; // "&gpio0": 指向gpio0节点的phandle;
interrupts = <7 IRQ_TYPE_LEVEL_LOW>; // "7": pin脚;
......
};
u32 interrupt[2], phandle;
int irq, ret;
phandle = dev_read_u32_default(dev->parent, "interrupt-parent", -1);
if (phandle < 0) {
printf("failed get 'interrupt-parent', ret=%d\n", phandle);
return phandle;
}
ret = dev_read_u32_array(dev->parent, "interrupts", interrupt, 2);
if (ret) {
printf("failed get 'interrupt', ret=%d\n", ret);
return ret;
}
// 为了示例简单,省去返回值判断
irq = phandle_gpio_to_irq(phandle, interrupt[0]);
irq_install_handler(irq, pwrkey_irq_handler, dev);
irq_set_irq_type(irq, IRQ_TYPE_EDGE_FALLING);
irq_handler_enable(irq);
(3)强制指定 gpio
// 此方法直接强制指定 gpio,传入的 gpio 必须通过特殊的宏来声明才行,不够灵活,不建议使用。
int hard_gpio_to_irq(unsigned gpio);
范例(GPIO0_A0 申请中断):
int gpio0_a0, irq;
// 为了示例简单,省去返回值判断
gpio0_a0 = RK_IRQ_GPIO(RK_GPIO0, RK_PA0);
irq = hard_gpio_to_irq(gpio0_a0);
irq_install_handler(irq, ...);
irq_handler_enable(irq);
Clock
框架支持
clock 驱动使用 clk-uclass 框架和标准接口。
配置:
CONFIG_CLK
框架代码:
./drivers/clk/clk-uclass.c
平台驱动代码:
./drivers/clk/rockchip/...
相关接口
// 申请时钟
int clk_get_by_index(struct udevice *dev, int index, struct clk *clk);
int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk);
// 使能/关闭时钟
int clk_enable(struct clk *clk);
int clk_disable(struct clk *clk);
// 配置/获取频率
ulong (*get_rate)(struct clk *clk);
ulong (*set_rate)(struct clk *clk, ulong rate);
// 配置/获取相位
int (*get_phase)(struct clk *clk);
int (*set_phase)(struct clk *clk, int degrees);
时钟初始化
一共有三类接口涉及时钟初始化,如下先列出cru节点的信息,方便后续介绍。
cru: clock-controller@ff2b0000 {
compatible = "rockchip,px30-cru";
......
assigned-clocks =
<&pmucru PLL_GPLL>, <&pmucru PCLK_PMU_PRE>,
<&pmucru SCLK_WIFI_PMU>, <&cru ARMCLK>,
<&cru ACLK_BUS_PRE>, <&cru ACLK_PERI_PRE>,
<&cru HCLK_BUS_PRE>, <&cru HCLK_PERI_PRE>,
<&cru PCLK_BUS_PRE>, <&cru SCLK_GPU>;
assigned-clock-rates =
<1200000000>, <100000000>,
<26000000>, <600000000>,
<200000000>, <200000000>,
<150000000>, <150000000>,
<100000000>, <200000000>;
......
}
第一类,平台基础时钟默认初始化:rkclk_init()
各平台cru驱动probe会调用rkclk_init()完成 pll/cpu/bus 频率初始化,频率定义在 cru_rkxxx.h 。例如 RK3399:
#define APLL_HZ (600 * MHz)
#define GPLL_HZ (800 * MHz)
#define CPLL_HZ (384 * MHz)
#define NPLL_HZ (600 * MHz)
#define PPLL_HZ (676 * MHz)
#define PMU_PCLK_HZ ( 48 * MHz)
#define ACLKM_CORE_HZ (300 * MHz)
#define ATCLK_CORE_HZ (300 * MHz)
#define PCLK_DBG_HZ (100 * MHz)
#define PERIHP_ACLK_HZ (150 * MHz)
#define PERIHP_HCLK_HZ ( 75 * MHz)
#define PERIHP_PCLK_HZ (37500 * KHz)
#define PERILP0_ACLK_HZ (300 * MHz)
#define PERILP0_HCLK_HZ (100 * MHz)
#define PERILP0_PCLK_HZ ( 50 * MHz)
#define PERILP1_HCLK_HZ (100 * MHz)
#define PERILP1_PCLK_HZ ( 50 * MHz)
......
第二类,平台基础时钟二次初始化:clk_set_defaults()
各平台cru驱动probe可能会调用clk_set_defaults()解析且配置cru节点内assigned-clocks/assigned-clock-parents/assigned-clock-rates指定的频率(即重新配置频率),但是不包括arm频率。仅当实现set_armclk_rate() 时才会配置。
除了cru之外,有需求的外设也可以在自己的probe里主动调用 clk_set_defaults(),例如 vop、gmac。
第三类,各模块时钟初始化:clk_set_rate()
大部分外设模块都是调用clk_set_rate() 配置自己的频率。
CPU提频
cpu开机提频的实现流程:
- 步骤1:cru 节点的
assigned-clocks里指定 arm 目标频率; - 步骤2:cru 驱动probe时调用
clk_set_defaults()获取(但不会配置)步骤1的arm目标频率; - 步骤3:实现
set_armclk_rate(),这是一个 __weak函数,它会配置从步骤2获取的 arm 目标频率; - 步骤4:arm 的 regulator 节点增加
regulator-init-microvolt = <...>指定init电压避免电压不足;
时钟树
U-Boot 框架没有提供时钟树管理,各平台增加了soc_clk_dump() 用于简单打印时钟信息。例如:
CLK: (sync kernel. arm: enter 1200000 KHz, init 1200000 KHz, kernel 800000 KHz)
apll 800000 KHz
dpll 392000 KHz
cpll 1000000 KHz
gpll 1188000 KHz
npll 24000 KHz
ppll 100000 KHz
hsclk_bus 297000 KHz
msclk_bus 198000 KHz
lsclk_bus 99000 KHz
msclk_peri 198000 KHz
lsclk_peri 99000 KHz
第一行打印的含义:
sync kernel:cru 驱动通过clk_set_defaults()配置了 kernel cru 节点内指定的各总线频率(arm 频率除外);否则显示为sync uboot;enter 1200000 KHz:前级 Loader 进入 U-Boot 时的 arm 频率;init 1200000 KHz:U-Boot 的 arm 初始化频率,即 APLL_HZ 定义的频率;kernel 800000 KHz:实现了 set_armclk_rate() 并设置了 kernel cru 节点里assigned-clocks指定的 arm 频率;否则显示:"kernel 0N/A";
GPIO
框架支持
GPIO 驱动使用 gpio-uclass 框架和标准接口,用户必须通过 struct gpio_desc才能访问到gpio。
配置:
CONFIG_DM_GPIO
CONFIG_ROCKCHIP_GPIO
框架代码:
./drivers/gpio/gpio-uclass.c
驱动代码:
./drivers/gpio/rk_gpio.c
相关接口
// 申请/释放GPIO
int gpio_request_by_name(struct udevice *dev, const char *list_name,
int index, struct gpio_desc *desc, int flags);
int gpio_request_by_name_nodev(ofnode node, const char *list_name, int index,
struct gpio_desc *desc, int flags);
int gpio_request_list_by_name(struct udevice *dev, const char *list_name,
struct gpio_desc *desc_list, int max_count, int flags);
int gpio_request_list_by_name_nodev(ofnode node, const char *list_name,
struct gpio_desc *desc_list, int max_count,
int flags);
int dm_gpio_free(struct udevice *dev, struct gpio_desc *desc)
// 配置GPIO方向。@flags:GPIOD_IS_OUT(输出)和 GPIOD_IS_IN(输入)
int dm_gpio_set_dir_flags(struct gpio_desc *desc, ulong flags);
// 设置/获取GPIO电平
int dm_gpio_get_value(const struct gpio_desc *desc)
int dm_gpio_set_value(const struct gpio_desc *desc, int value)
注意事项:
dm_gpio_get_value() 的返回值表示active状态,而非电平的高或低。例如:
-
gpios = <&gpio2 RK_PD0 GPIO_ACTIVE_LOW>,低电平时返回值为 1,高电平为 0。 -
gpios = <&gpio2 RK_PD0 GPIO_ACTIVE_HIGH>,低电平时返回值为 0,高电平为 1。
dm_gpio_set_value() 的参数value也是同理,1:atcive,0:inactive。
Pinctrl
框架支持
pinctrl 驱动使用 pinctrl-uclass 框架和标准接口。
配置:
CONFIG_PINCTRL_GENERIC
CONFIG_PINCTRL_ROCKCHIP
框架代码:
./drivers/pinctrl/pinctrl-uclass.c
驱动代码:
./drivers/pinctrl/pinctrl-rockchip.c
相关接口
int pinctrl_select_state(struct udevice *dev, const char *statename) // 设置状态
int pinctrl_get_gpio_mux(struct udevice *dev, int banknum, int index) // 获取状态
pinctrl 框架会在各个driver probe 时又框架自动为其设置"default"状态,用户一般不需要调用pinctrl接口。
I2C
框架支持
i2c 驱动使用 i2c-uclass 框架和标准接口。
配置:
CONFIG_DM_I2C
CONFIG_SYS_I2C_ROCKCHIP
框架代码:
./drivers/i2c/i2c-uclass.c
驱动代码:
./drivers/i2c/rk_i2c.c
./drivers/i2c/i2c-gpio.c // gpio模拟i2c通讯,目前用不到
相关接口
// i2c 读写
int dm_i2c_read(struct udevice *dev, uint offset, uint8_t *buffer, int len)
int dm_i2c_write(struct udevice *dev, uint offset, const uint8_t *buffer, int len)
// 对上面接口的封装
int dm_i2c_reg_read(struct udevice *dev, uint offset)
int dm_i2c_reg_write(struct udevice *dev, uint offset, unsigned int val);
Display
框架支持
RK U-Boot 目前支持的显示接口包括:RGB、LVDS、EDP、MIPI 和 HDMI,未来还会加入 CVBS、DP 等。U-Boot 显示的 logo 图片来自 kernel 根目录下的 logo.bmp 和 logo_kernel.bmp,它们被打包在 resource.img 里。
对图片的要求:
-
8bit 或者 24bit BMP 格式;
-
logo.bmp 和 logo_kernel.bmp 的图片分辨率大小一致;
-
对于 rk312x/px30/rk3308 这种基于 vop lite 结构的芯片,由于 VOP 不支持镜像,而 24bit 的 BMP 图片是按镜像存储,所以如果发现显示的图片做了 y 方向的镜像,请在 PC 端提前将图片做好 y 方向的镜像。
配置:
CONFIG_DM_VIDEO
CONFIG_DISPLAY
CONFIG_DRM_ROCKCHIP
CONFIG_DRM_ROCKCHIP_PANEL
CONFIG_DRM_ROCKCHIP_DW_MIPI_DSI
CONFIG_DRM_ROCKCHIP_DW_HDMI
CONFIG_DRM_ROCKCHIP_LVDS
CONFIG_DRM_ROCKCHIP_RGB
CONFIG_DRM_ROCKCHIP_RK618
CONFIG_ROCKCHIP_DRM_TVE
CONFIG_DRM_ROCKCHIP_ANALOGIX_DP
CONFIG_DRM_ROCKCHIP_INNO_VIDEO_COMBO_PHY
CONFIG_DRM_ROCKCHIP_INNO_VIDEO_PHY
框架代码:
drivers/video/drm/rockchip_display.c
drivers/video/drm/rockchip_display.h
驱动文件:
vop:
drivers/video/drm/rockchip_crtc.c
drivers/video/drm/rockchip_crtc.h
drivers/video/drm/rockchip_vop.c
drivers/video/drm/rockchip_vop.h
drivers/video/drm/rockchip_vop_reg.c
drivers/video/drm/rockchip_vop_reg.h
rgb:
drivers/video/drm/rockchip_rgb.c
drivers/video/drm/rockchip_rgb.h
lvds:
drivers/video/drm/rockchip_lvds.c
drivers/video/drm/rockchip_lvds.h
mipi:
drivers/video/drm/rockchip_mipi_dsi.c
drivers/video/drm/rockchip_mipi_dsi.h
drivers/video/drm/rockchip-inno-mipi-dphy.c
edp:
drivers/video/drm/rockchip_analogix_dp.c
drivers/video/drm/rockchip_analogix_dp.h
drivers/video/drm/rockchip_analogix_dp_reg.c
drivers/video/drm/rockchip_analogix_dp_reg.h
hdmi:
drivers/video/drm/dw_hdmi.c
drivers/video/drm/dw_hdmi.h
drivers/video/drm/rockchip_dw_hdmi.c
drivers/video/drm/rockchip_dw_hdmi.h
panel:
drivers/video/drm/rockchip_panel.c
drivers/video/drm/rockchip_panel.h
相关接口
// 显示 U-Boot logo 和 kernel logo:
void rockchip_show_logo(void);
// 显示 bmp 图片,目前主要用于充电图片显示:
void rockchip_show_bmp(const char *bmp);
// 将 U-Boot 中的一些变量通过 dtb 传给内核。
// 包括 kernel logo 的大小、地址、格式, crtc 输出扫描时序以及过扫描的配置。
// 未来还会加入 BCSH 等相关变量配置。
void rockchip_display_fixup(void *blob);
DTS 配置
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
drm_logo: drm-logo@00000000 {
compatible = "rockchip,drm-logo";
// 预留buffer用于kernel logo的存放,具体地址和大小在U-Boot中会修改
reg = <0x0 0x0 0x0 0x0>;
};
};
&route-edp {
status = "okay"; // 使能U-Boot logo显示功能
logo,uboot = "logo.bmp"; // 指定U-Boot logo显示的图片
logo,kernel = "logo_kernel.bmp"; // 指定kernel logo显示的图片
logo,mode = "center"; // center:居中显示,fullscreen:全屏显示
charge_logo,mode = "center"; // center:居中显示,fullscreen:全屏显示
connect = <&vopb_out_edp>; // 确定显示通路,vopb->edp->panel
};
&edp {
status = "okay"; // 使能edp
};
&vopb {
status = "okay"; // 使能vopb
};
&panel {
"simple-panel";
...
status = "okay";
disp_timings: display-timings {
native-mode = <&timing0>;
timing0: timing0 {
...
};
};
};
defconfig
目前除了 RK3308 之外的其他平台,U-Boot 的 defconfig 已经默认支持显示,只要在 dts 中将显示相关的信息配置好即可。RK3308 考虑到启动速度等一些原因默认不支持显示,需要在 defconfig 中加入如下修改:
--- a/configs/evb-rk3308_defconfig
+++ b/configs/evb-rk3308_defconfig
@@ -4,7 +4,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x2000
CONFIG_ROCKCHIP_RK3308=y
CONFIG_ROCKCHIP_SPL_RESERVE_IRAM=0x0
CONFIG_RKIMG_BOOTLOADER=y
-# CONFIG_USING_KERNEL_DTB is not set
CONFIG_TARGET_EVB_RK3308=y
CONFIG_DEFAULT_DEVICE_TREE="rk3308-evb"
CONFIG_DEBUG_UART=y
@@ -55,6 +54,11 @@ CONFIG_USB_GADGET_DOWNLOAD=y
CONFIG_G_DNL_MANUFACTURER="Rockchip"
CONFIG_G_DNL_VENDOR_NUM=0x2207
CONFIG_G_DNL_PRODUCT_NUM=0x330d
+CONFIG_DM_VIDEO=y
+CONFIG_DISPLAY=y
+CONFIG_DRM_ROCKCHIP=y
+CONFIG_DRM_ROCKCHIP_RGB=y
+CONFIG_LCD=y
CONFIG_USE_TINY_PRINTF=y
CONFIG_SPL_TINY_MEMSET=y
CONFIG_ERRNO_STR=y
关于 upstream defconfig 配置的说明:
upstream 维护了一套 Rockchip U-Boot 显示驱动,目前主要支持 RK3288 和 RK3399 两个平台:
./drivers/video/rockchip/
如果要使用这套驱动,可以打开 CONFIG_VIDEO_ROCKCHIP,同时关闭 CONFIG_DRM_ROCKCHIP。跟我们目前 SDK 使用的显示驱动对比,后者的优势有:
-
支持的平台和显示接口更全面;
-
HDMI、DP 等显示接口可以根据用户的设定输出指定分辨率,过扫描效果,显示效果调节效果等。
-
U-Boot logo 可以平滑过渡到 kernel logo 直到系统起来;
LOGO分区
用户如果有动态更新开机LOGO的需求(一般在应用层发起更新),可以通过独立的LOGO分区实现。
操作步骤:
-
分区表中增加独立的LOGO分区
-
用户根据需要以某种方式动态更新LOGO分区中的图片。更新时,用户直接把原始图片更新到LOGO分区中即可,不需要任何打包。当LOGO分区的图片无效时,则仍旧使用resource文件中默认的图片。
LOGO分区支持情况:
如果代码只包含如下提交,则LOGO分区仅支持1张图片,只能替换默认的 logo.bmp:
1d30bcc rockchip: resource: support parse "logo" partition picture
如果代码包含如下提交,LOGO分区支持2张图片:图片1用于替换logo.bmp,图片用于替换logo_kernel.bmp。两张图片紧挨着,图片之间保持512字节对齐,顺序不可更换。
commit 07f987d8d495380787203e2bc2accd44100e6051
Author: Joseph Chen <chenjh@rock-chips.com>
Date: Sun Dec 8 18:00:37 2019 +0800
rockchip: resource: support parse logo_kernel.bmp from logo partition
"logo" partition layout, not change order:
|----------------------| 0x00
| raw logo.bmp |
|----------------------| N*512-byte aligned
| raw logo_kernel.bmp |
|----------------------|
N: the sector count of logo.bmp
Signed-off-by: Joseph Chen <chenjh@rock-chips.com>
Change-Id: I2deba013d3963c99664c5bfd69693835a46ba48f
假设图片分别为logo.bmp和logo_kernel.bmp。logo.img 打包命令:
cat logo.bmp > logo.img && truncate -s %512 logo.img && cat logo_kernel.bmp >> logo.img
把生成的logo.img烧写到logo分区即可,开机后看到"LOGO: "打印:
U-Boot 2017.09-g042c01531e-210512-dirty #cjh (May 14 2021 - 11:25:03 +0800)
Model: Rockchip RK3568 Evaluation Board
PreSerial: 2, raw, 0xfe660000
DRAM: 2 GiB
Sysmem: init
Relocation Offset: 7d34f000, fdt: 7b9f8758
Using default environment
dwmmc@fe2b0000: 1, dwmmc@fe2c0000: 2, sdhci@fe310000: 0
Bootdev(atags): mmc 0
MMC0: HS200, 200Mhz
PartType: EFI
boot mode: normal
FIT: No fdt blob
Android 11.0, Build 2021.4, v2
Found DTB in boot part
// 如下打印说明logo.img分区的图片被正确识别到。
LOGO: logo.bmp
LOGO: logo_kernel.bmp
DTB: rk-kernel.dtb
HASH(c): OK
......
Pmic/Regulator
框架支持
PMIC/Regulator 驱动使用 pmic-uclass、regulator-uclass 框架和标准接口。
PMIC支持:
rk805/rk808/rk809/rk816/rk817/rk818
Regulator支持:
rk805/rk808/rk809/rk816/rk817/rk818/syr82x/tcs452x/fan53555/pwm/gpio/fixed
配置:
CONFIG_DM_PMIC
CONFIG_PMIC_CHILDREN
CONFIG_PMIC_RK8XX // 适用于目前所有RK8XX系列芯片
CONFIG_DM_REGULATOR
CONFIG_REGULATOR_PWM
CONFIG_REGULATOR_RK8XX // 适用于目前所有RK8XX系列芯片
CONFIG_REGULATOR_FAN53555
框架代码:
./drivers/power/pmic/pmic-uclass.c
./drivers/power/regulator/regulator-uclass.c
驱动文件:
./drivers/power/pmic/rk8xx.c
./drivers/power/regulator/rk8xx.c
./drivers/power/regulator/fixed.c
./drivers/power/regulator/gpio-regulator.c
./drivers/power/regulator/pwm_regulator.c
./drivers/power/regulator/fan53555_regulator.c
相关接口
// 获取regulator。 @platname:“regulator-name”指定的名字,如:vdd_arm、vdd_logic;
int regulator_get_by_platname(const char *platname, struct udevice **devp);
// 使能/关闭
int regulator_get_enable(struct udevice *dev);
int regulator_set_enable(struct udevice *dev, bool enable);
int regulator_set_suspend_enable(struct udevice *dev, bool enable);
int regulator_get_suspend_enable(struct udevice *dev);
// 配置/获取电压
int regulator_get_value(struct udevice *dev);
int regulator_set_value(struct udevice *dev, int uV);
int regulator_set_suspend_value(struct udevice *dev, int uV);
int regulator_get_suspend_value(struct udevice *dev);
init 电压
目前有两种方式为某路regulator设置初始化电压输出,前提是必须配置regulator-boot-on:
-
配置
regulator-min-microvolt和regulator-min-microvolt为相同值; -
配置
regulator-init-microvolt = <...>
vdd_arm: DCDC_REG1 {
regulator-name = "vdd_arm";
regulator-boot-on; // 必须配置
regulator-min-microvolt = <712500>;
regulator-max-microvolt = <1450000>;
regulator-init-microvolt = <1100000>; // 设置初始化电压为1.1v
......
};
跳过初始化
如果想跳过某路regulator的初始化,可增加regulator-loader-ignore。
vdd_arm: DCDC_REG1 {
regulator-name = "vdd_arm";
regulator-loader-ignore;// 仅对U-Boot阶段的regulator初始化有效,kernel无效
......
};
Charge
框架支持
U-Boot 原生代码不支持充电功能,RK自己实现了一套。
充电涉及的模块较多:Display、PMIC、电量计、充电动画、pwrkey、led、CPU低功耗休眠、Timer等。
电量计支持:
RK809/RK816/RK817/RK818/cw201x。
配置:
// 框架
CONFIG_DM_CHARGE_DISPLAY
CONFIG_CHARGE_ANIMATION
CONFIG_DM_FUEL_GAUGE
// 驱动
CONFIG_POWER_FG_CW201X
CONFIG_POWER_FG_RK818
CONFIG_POWER_FG_RK817
CONFIG_POWER_FG_RK816
充电框架:
./drivers/power/charge-display-uclass.c
充电动画驱动:
// 负责管理整个充电过程,它会获取电量、充电类型、按键事件,发起低功耗休眠等。
./drivers/power/charge_animation.c
电量计框架:
./drivers/power/fuel_gauge/fuel_gauge_uclass.c
电量计驱动:
./drivers/power/fuel_gauge/fg_rk818.c
./drivers/power/fuel_gauge/fg_rk817.c // rk809复用
./drivers/power/fuel_gauge/fg_rk816.c
......
逻辑流程:
charge-display-uclass.c
=> charge_animation.c
=> fuel_gauge_uclass.c
=> fg_rkxx.c
打包图片
充电图片位于./tools/images/ 目录,需要打包进 resource.img 才能被充电框架显示。
内核编译得到的resource.img 默认没打包充电图片,需要在U-Boot 里另外单独打包。
$ ls tools/images/
battery_0.bmp battery_1.bmp battery_2.bmp battery_3.bmp battery_4.bmp battery_bmp battery_fail.bmp
打包命令:
./pack_resource.sh <input resource.img> 或
./scripts/pack_resource.sh <input resource.img>
打包信息:
./pack_resource.sh /home/cjh/3399/kernel/resource.img
Pack ./tools/images/ & /home/guest/3399/kernel/resource.img to resource.img ...
Unpacking old image(/home/guest/3399/kernel/resource.img):
rk-kernel.dtb logo.bmp logo_kernel.bmp
Pack to resource.img successed!
Packed resources:
rk-kernel.dtb battery_1.bmp battery_2.bmp battery_3.bmp battery_4.bmp battery_bmp battery_fail.bmp logo.bmp logo_kernel.bmp battery_0.bmp
resource.img is packed ready
成功后会在 U-Boot 根目录下生成包含图片的 resource.img,通过 hd 命令确认内容:
hd resource.img | less
00000000 52 53 43 45 00 00 00 00 01 01 01 00 0a 00 00 00 |RSCE............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
......
*
00000400 45 4e 54 52 62 61 74 74 65 72 79 5f 31 2e 62 6d |ENTRbattery_1.bm| // 图片1
00000410 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |p...............|
00000420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000500 00 00 00 00 4d 00 00 00 9c 18 00 00 00 00 00 00 |....M...........|
00000510 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000600 45 4e 54 52 62 61 74 74 65 72 79 5f 32 2e 62 6d |ENTRbattery_2.bm| // 图片2
00000610 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |p...............|
00000620 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
......
DTS 配置
DTS充电节点:
charge-animation {
compatible = "rockchip,uboot-charge";
status = "okay";
rockchip,uboot-charge-on = <0>; // 是否开启U-Boot充电
rockchip,android-charge-on = <1>; // 是否开启Android充电
rockchip,uboot-exit-charge-level = <5>; // U-Boot充电时,允许开机的最低电量
rockchip,uboot-exit-charge-voltage = <3650>;// U-Boot充电时,允许开机的最低电压
rockchip,screen-on-voltage = <3400>; // U-Boot充电时,允许点亮屏幕的最低电压
rockchip,uboot-low-power-voltage = <3350>; // U-Boot无条件强制进入充电模式的最低电压
rockchip,system-suspend = <1>; // 是否灭屏时进入trust低功耗待机(要ATF支持)
rockchip,auto-off-screen-interval = <20>; // 自动灭屏超时,单位秒,默认15s
rockchip,auto-wakeup-interval = <10>; // 休眠自动唤醒时间,单位秒。如果值为0或没
// 有这个属性,则禁止休眠自动唤醒,一般用于
// 压力测试使用
rockchip,auto-wakeup-screen-invert = <1>; // 休眠自动唤醒时是否需要亮/灭屏
};
系统休眠
Pwrkey按键:
-
短按 pwrkey 可以亮/灭屏,灭屏时系统会进入低功耗模式;
-
长按 pwrkey 可开机进入系统
低功模式有2种,通过 rockchip,system-suspend = <VAL> 选择:
- VAL为0:cpu wfi 模式。此时不处理外设,仅仅cpu 进入低功耗模式;
- VAL为1:system suspend 模式,需要ATF 支持才有效。类同kernel的系统深度待机,整个SoC进入待机。
ATF支持低功耗的版本要求:
| 芯片 | 最低版本 |
|---|---|
| RK3399 | rk3399_bl31_v1.32.elf |
| RK3368 | rk3368h_bl31_v2.22.elf |
| PX30 | px30_bl31_v1.05.elf |
| RK3326 | rk3326_bl31_v1.05.elf |
| RK3308 | rk3308_bl31_v2.00.elf、rk3308_bl31_aarch32_v2.20.elf |
| RK312X | rk3126_tee_ta_v1.39.bin |
| RK3288 | rk3288_tee_ta_v1.43.bin |
更换图片
- 更换
./tools/images/目录下的图片(采用 8bit 或 24bit bmp),使用命令ls | sort确认图片排列顺序是低电量到高电量,使用 pack_resource.sh 脚本把图片打包进 resource.img; - 修改
./drivers/power/charge_animation.c里的图片和电量关系;
/*
* IF you want to use your own charge images, please:
*
* 1. Update the following 'image[]' to point to your own images;
* 2. You must set the failed image as last one and soc = -1 !!!
*/
static const struct charge_image image[] = {
{ .name = "battery_0.bmp", .soc = 5, .period = 600 },
{ .name = "battery_1.bmp", .soc = 20, .period = 600 },
{ .name = "battery_2.bmp", .soc = 40, .period = 600 },
{ .name = "battery_3.bmp", .soc = 60, .period = 600 },
{ .name = "battery_4.bmp", .soc = 80, .period = 600 },
{ .name = "battery_bmp", .soc = 100, .period = 600 },
{ .name = "battery_fail.bmp", .soc = -1, .period = 1000 },
};
// @name:图片的名字;
// @soc:图片对应的电量;
// @period:图片刷新时间(单位:ms);
// 注意:最后一张图片必须是 fail 图片,且“soc=-1”不可改变 !!
充电灯
实际产品中用户对 led 的控制需求各不相同,因此充电框架仅支持 2 个灯。充电时刻 led、充满时刻 led:
- 充满时刻 led:充电时候,电量有变化的时候,才会翻转 led 显示;
- 充满时刻 led:电量 100%充满时,才会点亮 led 灯;
上述2个Led 仅是一个demo,用户请根据自己的需求修改代码。
配置选项:
CONFIG_LED_CHARGING_NAME
CONFIG_LED_CHARGING_FULL_NAME
这两个配置选项用于指定 led 的 label 属性,请参考Led章节。
Storage
存储驱动使用标准的存储框架,访问接口对接到 BLK 层用于支持文件系统。目前支持的存储设备:eMMC、Nand flash、SPI Nand flash、SPI Nor flash,其中 flash 相关的框架如下:
| 简称 | 主要支持的颗粒类型 | 主控驱动 | flash 框架 | 注册设备类型 | 主要支持文件系统 |
|---|---|---|---|---|---|
| rknand 方案 | MLC TLC Nand | drivers/rkand | drivers/rkand | block 设备 | FAT、EXT、SquashFS |
| rkflash 方案 | SLC Nand、SPI Nand | drivers/rkflash | drivers/rkflash | block 设备 | FAT、EXT、SquashFS |
| rkflash 方案(SPI Nor 支持) | SPI Nor | drivers/rkflash | drivers/rkflash | block 或 mtd 设备 | SquashFS、JFFS2 |
| SLC Nand 开源方案 | SLC Nand | drivers/mtd/nand/raw | drivers/mtd/nand/raw | mtd 设备 | UBI |
| SPI Nand 开源方案 | SPI Nand | drivers/spi/rockchip_sfc.c | drivers/mtd/nand/raw | mtd 设备 | UBI |
| SPI Nor 开源方案 | SPI Nor | drivers/spi/rockchip_sfc.c | drivers/mtd/spi | mtd 或 mtd block 设备 | SquashFS、JFFS2 |
说明:
- rkflash 与 开源方案中关于 Nand flash 的支持主要区别在于:rkflash 集成 rk ftl(Flash Transfer Layer)在存储驱动中,而开源方案 ftl 部分则依赖于文件系统自身的 flash 的管理,例如 UBI 文件系统支持坏块管理、磨损均衡等适合 Nand flash 的文件系统特性。
框架支持
rknand
rknand 是针对大容量 Nand flash 设备所设计的存储驱动,通过 Nandc host 与 Nand flash device 通信,具体适用颗粒选型参考《RKNandFlashSupportList》,适用以下颗粒:
- SLC、MLC、TLC Nand flash
配置:
CONFIG_RKNAND
驱动文件:
./drivers/rknand/
rkflash
rkflash 是针对选用小容量存储的设备所设计的存储驱动,其中 Nand flash 的支持是通过 Nandc host 与 Nand flash device 通信完成,SPI flash 的支持是通过 SFC host 与 SPI flash devices 通信完成,具体适用颗粒选型参考《RK SpiNor and SLC Nand SupportList》,适用以下颗粒:
- 128MB、256MB 和 512MB 的 SLC Nand flash
- 部分 SPI Nand flash
- 部分 SPI Nor flash 颗粒
配置:
CONFIG_RKFLASH
CONFIG_RKNANDC_NAND /* 小容量并口Nand flash */
CONFIG_RKSFC_NOR /* SPI Nor flash */
CONFIG_RKSFC_NAND /* SPI Nand flash */
驱动文件:
./drivers/rkflash/
注意:
- SFC(serial flash controller)是 Rockchip 为简便支持 spi flash 所设计的专用模块;
- 由于 rknand 驱动与 rkflash 驱动 Nand 代码中 ftl 部分不兼容,所以
- CONFIG_RKNAND 与 CONFIG_RKNANDC_NAND 不能同时配置
- CONFIG_RKNAND 与 CONFIG_RKSFC_NAND 不能同时配置
MMC & SD
MMC为多媒体卡,比如 eMMC;SD为是一种基于半导体快闪记忆器的新一代记忆设备。在rockchip平台,它们共用一个 dw_mmc 控制器(除了rk3399,rk3399pro)。
配置:
CONFIG_MMC_DW=y
CONFIG_MMC_DW_ROCKCHIP=y
CONFIG_CMD_MMC=y
驱动文件:
./drivers/mmc/
SLC Nand & SPI Nand & SPI Nor 开源方案
由于开源社区的不断完善及 UBI 文件系统的可行性,RK 也完善 flash 结合较多开源代码的方案,且开源方案默认选用 pre loader 为 SPL 的启动方案,所以大部分配置都是结合 SPL 相关配置来完成。
配置:
-
SPL 、MTD、开源存储驱动及 MTD block 配置:参考 CH10-SPL 4.2 章节
-
去除 rkflash 宏配置:
CONFIG_RKFLASH=n
驱动文件:
./drivers/mtd/nand/raw //SLC Nand 主控驱动及协议层
./drivers/mtd/nand/spi //SPI Nand 协议层
./drivers/spi/rockchip_sfc.c //SPI Flash 主控驱动
./drivers/mtd/spi //SPI Nor 协议层
相关接口
存储驱动的访问接口都对挂到BLK层,所以无论何种存储都通过如下接口访问:
// 获取存储句柄
struct blk_desc *rockchip_get_bootdev(void)
// 访问接口
unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start,
lbaint_t blkcnt, void *buffer)
unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
lbaint_t blkcnt, const void *buffer)
unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,
lbaint_t blkcnt)
DTS 配置
eMMC配置:
// rkxxxx.dtsi配置
emmc: dwmmc@ff390000 {
compatible = "rockchip,px30-dw-mshc", "rockchip,rk3288-dw-mshc";
reg = <0x0 0xff390000 0x0 0x4000>; // 控制器寄存器base address及长度
max-frequency = <150000000>; // eMMC普通模式时钟为50MHz,当配置为eMMC
// HS200模式,该max-frequency生效
clocks = <&cru HCLK_EMMC>, <&cru SCLK_EMMC>,
<&cru SCLK_EMMC_DRV>, <&cru SCLK_EMMC_SAMPLE>; // 控制器对应时钟编号
clock-names = "biu", "ciu", "ciu-drv", "ciu-sample"; // 控制器时钟名
fifo-depth = <0x100>; // fifo深度,默认配置
interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>; // 中断配置
status = "disabled";
};
// rkxxxx-u-boot.dtsi
&emmc {
u-boot,dm-pre-reloc;
status = "okay";
}
// rkxxxx.dts
&emmc {
bus-width = <8>; // 设备总线位宽
cap-mmc-highspeed; // 标识此卡槽支持highspeed mmc
mmc-hs200-1_8v; // 支持HS200
supports-emmc; // 标识此插槽为eMMC功能,必须添加,否则无法初始化外设
disable-wp; // 对于无物理WP管脚,需要配置
non-removable; // 此项表示该插槽为不可移动设备。 此项为必须添加项
num-slots = <1>; // 标识为第几插槽
status = "okay";
};
Nandc 配置:
&nandc {
u-boot,dm-pre-reloc;
status = "okay";
};
SFC 配置:
&sfc {
u-boot,dm-pre-reloc;
status = "okay";
spi_nand: flash@0 {
u-boot,dm-spl;
compatible = "spi-nand";
reg = <0>;
spi-tx-bus-width = <1>;
spi-rx-bus-width = <4>;
spi-max-frequency = <96000000>;
};
spi_nor: flash@1 {
u-boot,dm-spl;
compatible = "jedec,spi-nor";
reg = <0>;
spi-tx-bus-width = <1>;
spi-rx-bus-width = <4>;
spi-max-frequency = <96000000>;
};
};
注意:
- 考虑到软件的兼容性,u-boot 下仅支持
spi-tx-bus-width = <1>的一线 SPI flash 传输;
Uart
serial 使用 serial-uclass.c 框架和标准接口,目前主要是UART debug在使用。
配置:
// 使能配置
CONFIG_DEBUG_UART
CONFIG_SYS_NS16550
// 参数配置
CONFIG_DEBUG_UART_BASE
CONFIG_DEBUG_UART_CLOCK
CONFIG_BAUDRATE
框架代码:
./drivers/serial/serial-uclass.c
驱动代码:
./drivers/serial/ns16550.c
单独更换
单独更换 U-Boot 阶段的UART debug 流程如下(uart2 为例):
-
CONFIG_ROCKCHIP_PRELOADER_SERIAL禁用; -
board_debug_uart_init()里配置 uart iomux(注意:某些平台有m0、m1...模式要配置 ); -
board_debug_uart_init()里配置 uart clock ,保证时钟源是 24Mhz; -
defconfig 更新
CONFIG_BAUDRATE; -
defconfig 更新
CONFIG_DEBUG_UART_BASE; -
U-Boot uart 节点中增加 2 个必要属性并且使能:
&uart2 {
u-boot,dm-pre-reloc;
clock-frequency = <24000000>;
status = "okay";
};
- U-Boot chosen 节点中指定 stdout-path:
chosen {
stdout-path = &uart2;
};
全局更换
Pre-loader serial 是实现前级固件共享UART debug 配置的机制,这些固件包括:ddr、miniloader、bl31、op-tee、U-Boot。原理:由最早阶段的 ddr bin 配置好UART debug 并且通过 ATAGS 传参机制逐级传递下去,各级固件获取 UART debug 配置进行使用(不包括 kernel)。
用户可以通过修改 ddr bin里的串口配置实现UART debug的全局替换,步骤:
DDR bin 配置
rkbin 仓库里提供了工具给用户配置不同的参数,包括串口更换:
tools/ddrbin_tool
tools/ddrbin_param.txt
tools/ddrbin_tool_user_guide.txt
U-Boot 配置
1 使能配置:
CONFIG_ROCKCHIP_PRELOADER_SERIAL // 已经默认使能
2 rkxx-u-boot.dtsi 中把使用到的 uart 节点加上属性“u-boot,dm-pre-reloc;”;
3 aliases 建立 serial 别名,因为U-Boot是通过aliaes找到目标节点并初始化它的。
例如:./arch/arm/dts/rk1808-u-boot.dtsi 里为了方便,为所有 uart 都建立别名;
aliases {
mmc0 = &emmc;
mmc1 = &sdmmc;
// 必须创建别名
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
serial4 = &uart4;
serial5 = &uart5;
serial6 = &uart6;
serial7 = &uart7;
};
.....
// 必须增加u-boot,dm-pre-reloc属性
&uart0 {
u-boot,dm-pre-reloc;
};
&uart1 {
u-boot,dm-pre-reloc;
};
&uart2 {
u-boot,dm-pre-reloc;
clock-frequency = <24000000>;
status = "okay";
};
&uart3 {
u-boot,dm-pre-reloc;
};
&uart4 {
u-boot,dm-pre-reloc;
};
关闭打印
CONFIG_DISABLE_CONSOLE=y
相关接口
// UART debug接口
void putc(const char c);
void puts(const char *s);
int printf(const char *fmt, ...);
void flushc(void);
// 跟外设通信功能的普通UART接口
int serial_dev_getc(struct udevice *dev);
int serial_dev_tstc(struct udevice *dev);
void serial_dev_putc(struct udevice *dev, char ch);
void serial_dev_puts(struct udevice *dev, const char *str);
void serial_dev_setbrg(struct udevice *dev, int baudrate);
void serial_dev_clear(struct udevice *dev);
Key
框架支持
U-Boot 框架默认没有支持按键功能,RK 自己实现了一套按键框架。
实现规则:
- 所有按键都通过 kernel 和 U-Boot 的 DTS 指定,U-Boot 不使用 hard code 的方式定义任何按键;
- U-Boot 优先查找 kernel dts 中的按键,找不到再查找 U-Boot dts 中的按键。
- U-Boot dts里仅定义了烧写按键。
- 如果用户要更新烧写按键定义,请同时更新kernel和U-Boot的dts。
配置:
CONFIG_DM_KEY
CONFIG_RK8XX_PWRKEY
CONFIG_ADC_KEY
CONFIG_GPIO_KEY
CONFIG_RK_KEY
框架代码:
./include/dt-bindings/input/linux-event-codes.h
./drivers/input/key-uclass.c
./include/key.h
驱动代码:
./drivers/input/rk8xx_pwrkey.c // 支持PMIC的pwrkey(RK805/RK809/RK816/RK817)
./drivers/input/rk_key.c // 支持compatible = "rockchip,key"
./drivers/input/gpio_key.c // 支持compatible = "gpio-keys"
./drivers/input/adc_key.c // 支持compatible = "adc-keys"
pwrkey 仅以中断方式被识别,其余 gpio 按键以轮询方式被识别。
相关接口
接口:
int key_read(int code)
code 定义:
/include/dt-bindings/input/linux-event-codes.h
返回值:
enum key_state {
KEY_PRESS_NONE, // 非完整的短按(没有释放按键)或非完整长按(按下时间不够长);
KEY_PRESS_DOWN, // 一次完整的短按(按下=>释放);
KEY_PRESS_LONG_DOWN, // 一次完整的长按(可以不释放);
KEY_NOT_EXIST, // 按键不存在
};
KEY_PRESS_LONG_DOWN 默认时长 2000ms,目前只用于 U-Boot 充电的 pwrkey 长按事件。
#define KEY_LONG_DOWN_MS 2000
范例:
int ret;
ret = key_read(KEY_VOLUMEUP);
...
Vendor Storage
Vendor Storage 用于存放 SN、MAC 等不需要加密的小数据。数据存放在 NVM(eMMC、NAND 等)的保留分区中,有多个备份,更新数据时数据不丢失,可靠性高。
详细的资料参考文档《appnote rk vendor storage》。
原理概述
一共把 vendor 的存储块分成 4 个分区,vendor0、vendor1、vendor2、vendor3。每个 vendorX(X=0、1、2、3)的 hdr 里都有一个单调递增的 version 字段用于表明 vendorX 被更新的时刻点。每次读操作只读取最新的 vendorX(即 version 最大),写操作的时候会更新 version 并且把整个原有信息和新增信息搬移到 vendorX+1 分区里。例如当前从 vendor2 读取到信息,经过修改后再回写,此时写入的是 vendor3。这样做只是为了起到一个简单的安全防护作用。
框架支持
U-Boot 框架没有支持 Vendor Storage 功能,Rockchip 自己实现了一套 Vendor Storage 驱动。
配置:
CONFIG_ROCKCHIP_VENDOR_PARTITION
驱动文件:
./arch/arm/mach-rockchip/vendor.c
./arch/arm/include/asm/arch-rockchip/vendor.h
相关接口
int vendor_storage_read(u16 id, void *pbuf, u16 size)
int vendor_storage_write(u16 id, void *pbuf, u16 size)
关于 id 的定义和使用,请参考《appnote rk vendor storage》。
功能自测
U-Boot 串口命令行下使用"rktest vendor"命令可以进行 Vendor Storage 功能自测。
OPTEE Client
U-Boot在ARM TrustZone里属于Non-Secure World,需要借助OPTEE Client才能访问安全资源。
框架支持
U-Boot 框架默认没有支持OPTEE Client功能,RK 自己实现了一套。
配置:
// 总使能
CONFIG_OPTEE_CLIENT
// 旧平台使用,如 RK312x、RK322x、RK3288、RK3228H、RK3368、RK3399
CONFIG_OPTEE_V1
// 新平台使用,如 RK3326、RK3308
CONFIG_OPTEE_V2
// 当 eMMC 的 RPMB 不能用时必须开启此配置,即启用security分区!
CONFIG_OPTEE_ALWAYS_USE_SECURITY_PARTITION
框架和驱动:
lib/optee_clientApi/
固件说明
使用的 trust.img 必须启用 TA 功能,否则无法跟OPTEE Client交互。
接口文档
Optee client 驱动在 lib/optee_client 目录下,Optee Client Api 请参考《TEE_Client_API_Specification-V1.0_c.pdf》。 下载地址为:https://globalplatform.org/specs-library/tee-client-api-specification/
共享内存
U-Boot 与 Optee 通信时的数据需放在共享内存中。用户可通过 TEEC_AllocateSharedMemory() 申请共享内存,但建议不超过 1M。若超过则建议分割数据进行多次传递,使用完需调用 TEEC_ReleaseSharedMemory() 释放共享内存。
测试命令
作用:测试安全存储功能。U-Boot 命令行:
=> mmc testsecurestorage
该测试用例将循环测试安全存储读写功能,当硬件使用 emmc 时将测试 rpmb 与 security 分区两种安全存储方式;当硬件使用 nand 时只测试 security 分区安全存储。
常见错误打印
-
没有找到 emmc 或者 nand 设备。此时请检查 U-Boot 是否缺少配置,或者硬件是否损坏。
"TEEC: Could not find device" -
没有找到 security 分区。当没有RPMB可用时,需要在 parameter.txt 中定义 security 分区。
"TEEC: Could not find security partition" -
第一次使用 security 分区进行安全存储或 security 分区数据被非法篡改时会出现该打印。
"TEEC: verify [%d] fail, cleanning ...." -
安全存储的空间不足。请检查存储的数据是否过大,或者之前存储过大量的数据但没有删除。
"TEEC: Not enough space available in secure storage !"
DVFS
本章节的DVFS不同于kernel,是专门针对宽温芯片的动态调频调压机制。
宽温策略
U-Boot 框架没有支持 DVFS,为了支持某些芯片的宽温功能,RK 实现了一套 DVFS 宽温驱动根据芯片温度调整 cpu/dmc 频率-电压。但有别于内核 DVFS 驱动,这套宽温驱动仅仅在触发最高/低温度阈值时进行控制。
宽温策略:
- 宽温驱动用于调整 cpu/dmc 的频率-电压,控制策略可同时对 cpu 和 dmc 生效,也可只对其中一个生效,由 dts 配置决定;cpu 和 dmc 的控制策略是一样的;
- 宽温驱动会解析 cpu/dmc 节点的 opp table、regulator、clock、thermal zone 的"trip-point-0",获取频率-电压档位、最高/低温度阈值、允许的最高电压等信息;
- 若 cpu/dmc 的 opp table 里指定了
rockchip,low-temp = <...>或rockchip,high-temp = <...>,又或者 cpu/dmc 引用了 thermal zone 的 trip 节点,那么 cpu/dmc 宽温控制策略就会生效; - 关键属性:
- rockchip,low-temp:最低温度阈值,下述用 TEMP_min 表示;
- rockchip,high-temp 和 thermal zone:最高温度阈值,下述用 TEMP_max 表示(如果二者都有效,策略上都会拿当前温度进与之比较);
- rockchip,max-volt:允许设置的最高电压值,下述用 V_max 表示;
- 阈值触发的处理:
- 如果温度高于 TEMP_max,把频率和电压都降到最低档位;
- 如果温度低于 TEMP_min,默认抬压 50mv。若抬压 50mv 会导致电压超过 V_max,则电压设定为 V_max,同时把频率降低 2 档;
- 目前宽温策略应用在 2 个时刻点:
- regulator 和 clk 框架初始化完成后,宽温驱动进行初始化并且执行一次宽温策略,具体位置在 board.c 文件的 board_init()中调用;
- preboot 阶段(即加载固件之前)再执行一次宽温策略:如果 dts 节点中指定了"repeat"等相关属性(见下文),当执行完本次宽温策略后芯片温度依然不在温度阈值范围内,那就停止系统启动并且不断执行宽温策略,直到芯片温度回归到阈值范围内才继续启动系统。如果没有"repeat"等相关属性,则执行完本次宽温策略后就直接启动系统,目前一般不需要 repeat 属性。
框架支持
框架代码:
./drivers/power/dvfs/dvfs-uclass.c
./include/dvfs.h
./cmd/dvfs.c
驱动代码:
./drivers/power/dvfs/rockchip_wtemp_dvfs.c
相关接口
// 执行一次dvfs策略
int dvfs_apply(struct udevice *dev);
// 如果存在repeat属性,当温度不在阈值范围内时循环执行dvfs策略
int dvfs_repeat_apply(struct udevice *dev);
启用宽温
- 配置使能:
CONFIG_DM_DVFS=y
CONFIG_ROCKCHIP_WTEMP_DVFS=y
CONFIG_DM_THERMAL=y
CONFIG_ROCKCHIP_THERMAL=y
CONFIG_USING_KERNEL_DTB=y
- 指定 CONFIG_PREBOOT:
#ifdef CONFIG_DM_DVFS
#define CONFIG_PREBOOT "dvfs repeat"
#else
#define CONFIG_PREBOOT
#endif
- kernel dts 配置宽温节点
uboot-wide-temperature {
compatible = "rockchip,uboot-wide-temperature";
// 可选项。表示是否在U-Boot阶段触发cpu的最高/低温度阈值时让宽温驱动停止启动系统,
// 且不断执行宽温处理策略,直到芯片温度回归到阈值范围内才继续启动系统。
cpu,low-temp-repeat;
cpu,high-temp-repeat;
// 可选项。表示是否在U-Boot阶段触发dmc的最高/低温度阈值时让宽温驱动停止启动系统,
// 且不断执行宽温处理策略,直到芯片温度回归到阈值范围内才继续启动系统。
dmc,low-temp-repeat;
dmc,high-temp-repeat;
status = "okay";
};
一般情况下不需要配置上述的 repeat 相关属性。
宽温结果
当 cpu 温控启用时有如下打印:
// <NULL>表明没有指定低温阈值
DVFS: cpu: low=<NULL>'c, high=95'c, Vmax=1350000uV, tz_temp=88.0'c, h_repeat=0, l_repeat=0
当 cpu 温控触发高温阈值时会有调整信息:
DVFS: 90.352'c
DVFS: cpu(high): 600000000->408000000 Hz, 1050000->950000 uV
当 cpu 温控触发低温阈值时会有调整信息:
DVFS: 10.352'c
DVFS: cpu(low): 600000000->600000000 Hz, 1050000->1100000 uV
同理,当 dmc 触发高低温阈值时,也会有上述信息打印,信息前缀为"dmc":
DVFS: dmc: ......
DVFS: dmc(high): ......
DVFS: dmc(low): ......
AMP
框架支持
U-Boot 框架默认没有 AMP(Asymmetric Multi-Processing) 支持,RK 自己实现了一套 AMP 机制:不同的CPU运行不同的固件。
AMP功能需要trust配合,目前仅部分平台完整支持。AMP固件采用FIT格式,默认支持sha256固件完整性校验。
配置:
CONFIG_AMP
CONFIG_ROCKCHIP_AMP
框架代码:
./drivers/cpu/amp-uclass.c
./drivers/cpu/rockchip_amp.c
its 模版:
./drivers/cpu/amp.its
打包工具:
./tools/mkimage // 完整编译一次U-Boot后会自动生成
代码提交点至少:
commit 31f8f6ebae796097f7f10c67dff58fd907371c63
Author: Joseph Chen <chenjh@rock-chips.com>
Date: Wed Feb 24 14:34:25 2021 +0800
cpu: amp: support brought up rockchip fit image
./tools/mkimage -f amp.its -E -p 0xe00 amp.img
Signed-off-by: Joseph Chen <chenjh@rock-chips.com>
Change-Id: Icdaf370900472622cf31df402aff84ecc6821fe4
功能启用
-
制作amp.img需要一份its文件,请基于
drivers/cpu/amp.its修改:/dts-v1/;
/ {
description = "FIT source file for rockchip AMP";
#address-cells = <1>;
images {
amp0 {
description = "bare-mental-core0"; // 必选项:描述信息
data = /incbin/("../../hal0.bin"); // 必选项:amp0固件
type = "firmware";
compression = "none";
arch = "arm"; // 必选项:"arm64":64位, "arm":32位
cpu = <0x000>; // 必选项:cpu硬件id(mpidr)
thumb = <0>; // 必选项:0: arm or thumb2; 1: 纯thumb
hyp = <0>; // 必选项:0: el1/svc; 1: el2/hyp
load = <0xa00000>; // 必选项:固件加载和运行地址
udelay = <500000>; // 可选项:启动完当前CPU后做延时后再启动下一个CPU
linux-os; // 特殊可选项!
hash {
algo = "sha256";
};
};
amp1 {
......
};
amp2 {
......
};
amp3 {
......
};
};
configurations {
default = "conf";
conf {
description = "Rockchip AMP images";
rollback-index = <0x0>;
// 指定需要被加载的固件和顺序。
loadables = "amp0", "amp1", "amp2", "amp3";
signature {
algo = "sha256,rsa2048";
padding = "pss";
key-name-hint = "dev";
sign-images = "loadables";
};
};
};
};
说明:
-
主CPU:我们称当前跑U-Boot、负责启动其它核的CPU为“主CPU”,主CPU的状态是最后切换的。
-
data:固件路径。该路径是基于amp.its的相对路径。
-
arch:CPU 32/64模式。ARMv7只能是"arm";ARMv8可指定"arm64"或"arm"分别表示AArch64位或AArch32。
-
cpu:cpu硬件ID,即mpidr(Multiprocessor Affinity Register),取(低)32位即可。例如:
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x0>; // mpidr
......
};
cpu1: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x100>; // mpidr
......
};
......
}; -
thumb:CPU指令模式。如果是纯thumb则指定为1,否则为0。
-
hyp:CPU虚拟机模式。
-
load:固件加载和运行地址。
-
udelay:加载完成后的延时,单位us。启动完当前CPU后做相应延时后再启动下一个CPU。
-
loadables:AMP固件加载顺序。主CPU是最后被加载的,不受此处指定的顺序影响。
-
linux-os:主CPU跑Linux。可选,只能在主CPU的节点下添加。若该属性存在:
-
主CPU启动完其它核之后继续跑Linux(即按照原本的流程、固件格式被U-Boot加载启动)。
-
主CPU节点依然要指定在"loadables"里。
-
主CPU节点内的"data", "load"字段没有实际作用,但依然要保留,否则mkimage打包时会报错。
建议创建一个空文件指定data字段,例如:touch hal0.bin。
-
-
特别说明:AMP OS需要各自处理好内存管理,避免内存冲突。
-
固件打包:
// 0xe00为固件头大小,不建议改变
./tools/mkimage -f ./drivers/cpu/amp.its -E -p 0xe00 amp.img需要完整编译一次U-Boot才会自动生成mkimage工具。
-
分区表增加amp分区
在 parameter.txt 分区表文件中增加 "amp" 分区 ,然后烧写amp.img。
U-Boot是直接加载整个amp分区的内容到内存,建议amp分区大小按实际需要配置。
-
Bring up
U-Boot 框架会在合适的时机自动发起所有 AMP 的 bring up。例如:
.......
## Loading loadables from FIT Image at 3b3f7c80 ...
Trying 'amp0' loadables subimage
Description: bare-mental-core0
Type: Firmware
Compression: uncompressed
Data Start: 0x3b3f8a80
Data Size: 1016690 Bytes = 992.9 KiB
Architecture: ARM
Load Address: 0x00a00000
Hash algo: sha256
Hash value: 3376b6ee20dd52a06652b2e15c688206fbec021140d37c15f1020f9ddb20b76d
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x3b3f8a80 to 0x00a00000
## Loading loadables from FIT Image at 3b3f7c80 ...
Trying 'amp1' loadables subimage
Description: bare-mental-core1
Type: Firmware
Compression: uncompressed
Data Start: 0x3b4f0e80
Data Size: 1016690 Bytes = 992.9 KiB
Architecture: ARM
Load Address: 0x00b00000
Hash algo: sha256
Hash value: f3a08027d58113e9611434ac4f12dd68a40c80df2a6603c10138a2519ccc5f5d
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x3b4f0e80 to 0x00b00000
## Loading loadables from FIT Image at 3b3f7c80 ...
Trying 'amp2' loadables subimage
Description: bare-mental-core2
Type: Firmware
Compression: uncompressed
Data Start: 0x3b5e9280
Data Size: 1016690 Bytes = 992.9 KiB
Architecture: ARM
Load Address: 0x00c00000
Hash algo: sha256
Hash value: 602eda361c86fb8ad19cd7db813c67bb962d12d7d7355cac015bfff018fef788
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x3b5e9280 to 0x00c00000
## Loading loadables from FIT Image at 3b3f7c80 ...
Trying 'amp3' loadables subimage
Description: bare-mental-core3
Type: Firmware
Compression: uncompressed
Data Start: 0x3b6e1680
Data Size: 1016690 Bytes = 992.9 KiB
Architecture: AArch64
Load Address: 0x00d00000
Hash algo: sha256
Hash value: 1491d53be18e58165f750813291f2ce26e736bda0c3a9edb257be4614b1e00f2
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x3b6e1680 to 0x00d00000
// 成功打印(如果失败,会有相应错误打印)
AMP: Brought up cpu[100] with state 0x10, entry 0x00b00000 ...OK
AMP: Brought up cpu[200] with state 0x10, entry 0x00c00000 ...OK
AMP: Brought up cpu[300] with state 0x10, entry 0x00d00000 ...OK
AMP: Brought up cpu[0, self] with state 0x10, entry 0x00a00000 ...OK
......
IO-Domain
框架支持
U-Boot 框架默认没有对 io-domain 的支持,RK 自己实现了一套。
配置:
CONFIG_IO_DOMAIN
CONFIG_ROCKCHIP_IO_DOMAIN
框架代码:
./drivers/power/io-domain/io-domain-uclass.c
驱动代码:
./drivers/power/io-domain/rockchip-io-domain.c
相关接口
void io_domain_init(void)
用户不需要主动调用io_domain_init(),只需要开启上述配置即可,U-Boot框架会自动初始化。
Watchdog
框架支持
watchdog 驱动使用 wdt-uclass.c 框架和标准接口。
配置:
CONFIG_WDT
CONFIG_ROCKCHIP_WATCHDOG
框架代码:
./drivers/watchdog/wdt-uclass.c
驱动代码:
./drivers/watchdog/rockchip_wdt.c
相关接口
// 设置喂狗超时时间且启动wdt(@flags默认填0)
int wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags);
// 关闭wdt
int wdt_stop(struct udevice *dev);
// 喂狗
int wdt_reset(struct udevice *dev);
// 忽略,目前未做底层驱动实现
int wdt_expire_now(struct udevice *dev, ulong flags)
目前 U-Boot 的默认流程里不启用、也不使用 wdt 功能,用户可根据自己的产品需求进行启用。
Crypto
Crypto 模块主要用于实现硬件级别的加密和哈希算法,目前有两个IP版本:
- Crypto V1:rk3399/rk3368/rk3328/rk3229/rk3288/rk3128;
- Crypto V2:rk3326/px30/rk3308/rk1808;
框架支持
U-Boot 默认没有crypto框架支持,RK 自己实现了一套。
配置:
CONFIG_DM_CRYPTO
CONFIG_ROCKCHIP_CRYPTO_V1
CONFIG_ROCKCHIP_CRYPTO_V2
框架代码:
./drivers/crypto/crypto-uclass.c
./cmd/crypto.c
驱动代码:
// crypto v1:
./drivers/crypto/rockchip/crypto_v1.c
// crytpo v2:
./drivers/crypto/rockchip/crypto_v2.c
./drivers/crypto/rockchip/crypto_v2_pka.c
./drivers/crypto/rockchip/crypto_v2_util.c
相关接口
// 获取crypto:
struct udevice *crypto_get_device(u32 capability);
// SHA接口:
int crypto_sha_init(struct udevice *dev, sha_context *ctx);
int crypto_sha_update(struct udevice *dev, u32 *input, u32 len);
int crypto_sha_final(struct udevice *dev, sha_context *ctx, u8 *output);
int crypto_sha_csum(struct udevice *dev, sha_context *ctx,
char *input, u32 input_len, u8 *output);
// RSA接口:
int crypto_rsa_verify(struct udevice *dev, rsa_key *ctx, u8 *sign, u8 *output);
- 接口使用请参考:
./cmd/crypto.c; - v1 和 v2 的 SHA 使用不同:v1 要求 crypto_sha_init() 时必须先把要计算的数据总长度赋给
ctx->length,v2 不需要;
DTS 配置
crypto 节点必须定义在 U-Boot dts ,主要原因:
- 各平台旧 SDK 的内核 dts 没有 crypto 节点,因此需要考虑对旧 SDK 的兼容;
- U-Boot 的 secure boot 会用到 crypto,因此由 U-Boot 自己控制 crypto 更为安全合理;
- crypto v1 配置(RK3399 为例):
crypto: crypto@ff8b0000 {
u-boot,dm-pre-reloc;
compatible = "rockchip,rk3399-crypto";
reg = <0x0 0xff8b0000 0x0 0x10000>;
clock-names = "sclk_crypto0", "sclk_crypto1";
clocks = <&cru SCLK_CRYPTO0>, <&cru SCLK_CRYPTO1>; // 不需要指定频率,默认100M
status = "disabled";
};
- crypto v2 配置(px30 为例):
crypto: crypto@ff0b0000 {
u-boot,dm-pre-reloc;
compatible = "rockchip,px30-crypto";
reg = <0x0 0xff0b0000 0x0 0x4000>;
clock-names = "sclk_crypto", "apkclk_crypto";
clocks = <&cru SCLK_CRYPTO>, <&cru SCLK_CRYPTO_APK>;
clock-frequency = <200000000>, <300000000>; // 一般需要指定频率
status = "disabled";
};
- crypto v1 和 v2 的 dts 配置差异在于 clk 频率指定。
Reset
框架支持
reset 驱动使用 reset-uclass.c 框架和标准接口。RK 平台上reset 的实质是进行 CRU 软复位。
配置:
CONFIG_DM_RESET
CONFIG_RESET_ROCKCHIP
框架代码:
./drivers/reset/reset-uclass.c
驱动代码:
./drivers/reset/reset-rockchip.c
相关接口
// 获取reset句柄
int reset_get_by_index(struct udevice *dev, int index, struct reset_ctl *reset_ctl);
int reset_get_by_name(struct udevice *dev, const char *name,
struct reset_ctl *reset_ctl);
// 释放reset
int reset_free(struct reset_ctl *reset_ctl);
// 请求reset
int reset_request(struct reset_ctl *reset_ctl);
// 触发reset、释放reset
int reset_assert(struct reset_ctl *reset_ctl);
int reset_deassert(struct reset_ctl *reset_ctl);
范例:
struct reset_ctl reset_ctl;
ret = reset_get_by_name(dev, "mac-phy", &reset_ctl);
if (ret) {
debug("reset_get_by_name() failed: %d\n", ret);
return ret;
}
ret = reset_request(&reset_ctl);
if (ret)
return ret;
ret = reset_assert(&reset_ctl);
if (ret)
return ret;
......
ret = reset_deassert(&reset_ctl);
if (ret)
return ret;
......
ret = reset_free(&reset_ctl);
if (ret)
return ret;
DTS 配置
U-Boot 默认启用reset功能,用户只需在外设节点里指定要操作的 reset 对象即可:
// 格式:
reset-names = <name-string-list>
resets = <cru-phandle-list>
例如 gmac2phy:
gmac2phy: ethernet@ff550000 {
compatible = "rockchip,rk3328-gmac";
......
// 指定reset属性
reset-names = "stmmaceth", "mac-phy";
resets = <&cru SRST_GMAC2PHY_A>, <&cru SRST_MACPHY>;
};
Led
框架支持
Led 驱动使用 led-uclass.c 框架和标准接口。
配置:
CONFIG_LED_GPIO
框架代码:
drivers/led/led-uclass // 默认编译
驱动代码:
drivers/led/led_gpio.c // 支持 compatible = "gpio-leds"
相关接口
// 获取led device
int led_get_by_label(const char *label, struct udevice **devp);
// 设置/获取led状态
int led_set_state(struct udevice *dev, enum led_state_t state);
enum led_state_t led_get_state(struct udevice *dev);
// 忽略,目前未做底层驱动实现
int led_set_period(struct udevice *dev, int period_ms);
DTS 节点
U-Boot 的 led_gpio.c 功能相对简单,只解析 led 节点下的 3 个属性:
- gpios:led 控制引脚和有效状态;
- label:led 名字;
- default-state:默认状态,驱动 probe 时会被设置;
leds {
compatible = "gpio-leds";
status = "okay";
blue-led {
gpios = <&gpio2 RK_PA1 GPIO_ACTIVE_LOW>;
label = "battery_full";
default-state = "off";
};
green-led {
gpios = <&gpio2 RK_PA0 GPIO_ACTIVE_LOW>;
label = "greenled";
default-state = "off";
};
......
};
Efuse/Otp
框架支持
efuse/otp 驱动使用 misc-uclass.c 框架和标准接口。通常情况,efuse/otp 一般会有 secure 和 non-secure 之分。U-Boot 提供 non-secure 的访问,U-Boot SPL 提供 secure otp 某些区域的访问。
non-secure 配置:
CONFIG_MISC
CONFIG_ROCKCHIP_EFUSE
CONFIG_ROCKCHIP_OTP
secure 配置:
CONFIG_SPL_MISC=y
CONFIG_SPL_ROCKCHIP_SECURE_OTP=y
框架代码:
./drivers/misc/misc-uclass.c
驱动代码:
// non-secure:
./drivers/misc/rockchip-efuse.c
./drivers/misc/rockchip-otp.c
// secure:
./drivers/misc/rockchip-secure-otp.S
相关接口
// non-secure:
int misc_read(struct udevice *dev, int offset, void *buf, int size)
// secure:
int misc_read(struct udevice *dev, int offset, void *buf, int size)
int misc_write(struct udevice *dev, int offset, void *buf, int size)
DTS 配置
以 rk3308 为例:
non-secure:
otp: otp@ff210000 {
compatible = "rockchip,rk3308-otp";
reg = <0x0 0xff210000 0x0 0x4000>;
};
secure:
secure_otp: secure_otp@0xff2a8000 {
compatible = "rockchip,rk3308-secure-otp";
reg = <0x0 0xff2a8000 0x0 0x4000>;
secure_conf = <0xff2b0004>;
mask_addr = <0xff540000>;
};
调用示例
non-secure 示例:
char data[10] = {0};
struct udevice *dev;
/* retrieve the device */
ret = uclass_get_device_by_driver(UCLASS_MISC,
DM_GET_DRIVER(rockchip_otp), &dev);
if (ret) {
printf("no misc-device found\n");
return 0;
}
misc_read(dev, 0x10, &data, 10);
secure 示例:
char data[10] = {0};
struct udevice *dev;
int i;
/* retrieve the device */
ret = uclass_get_device_by_driver(UCLASS_MISC,
DM_GET_DRIVER(rockchip_secure_otp), &dev);
if (ret) {
printf("no misc-device found\n");
return 0;
}
for (i = 0; i < 10; i++)
data[i] = i;
misc_write(dev, 0x10, &data, 10);
memset(data, 0, 10);
misc_read(dev, 0x10, &data, 10);
Secure-Otp
RK 对 secure otp 只开放部分区域读写,区域如下:
0x0; // Rockchip 定义为 Secure boot enable flag
0x10-0x2f; // Rockchip 定义为 RSA Public key hash
0x80-0x187; // Rockchip 定义为 reserved for OEM
MTD
MTD (Memory Technology Device) 即内存技术设备,支持 nand、spi nand、spi nor。RK 设计了 MTD block 层用于支持 MTD 设备的读写。
框架支持
U-Boot配置:
// MTD驱动
CONFIG_MTD=y
CONFIG_CMD_MTDPARTS=y
CONFIG_MTD_DEVICE=y
// MTD block设备驱动
CONFIG_CMD_MTD_BLK=y
CONFIG_MTD_BLK=y
// 其他nand设备驱动config
......
SPL配置:
CONFIG_MTD=y
CONFIG_CMD_MTDPARTS=y
CONFIG_MTD_DEVICE=y
CONFIG_SPL_MTD_SUPPORT=y
// 其他nand设备驱动config
......
框架代码:
drivers/mtd/mtd-uclass.c
drivers/mtd/mtdcore.c
drivers/mtd/mtd_uboot.c
drivers/mtd/mtd_blk.c
驱动为各个控制器驱动,把读写等接口挂接到 MTD 层。
相关接口
unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start,
lbaint_t blkcnt, void *buffer)
以太网
框架支持
框架代码:
./net/*
./drivers/net/*
./drivers/net/phy/*
驱动代码:
./drivers/net/designware.c
./drivers/net/dwc_eth_qos.c
./drivers/net/gmac_rockchip.c
menuconfig 配置:
- 驱动配置 Rockchip 以太网驱动有两套驱动,如果对驱动的选择有疑问,请参考我们对应的 sdk config 配置。
// designware:
CONFIG_DM_ETH=y
CONFIG_ETH_DESIGNWARE=y
CONFIG_GMAC_ROCKCHIP=y
// dwc_eth_qos:
CONFIG_DM_ETH=y
CONFIG_DM_ETH_PHY=y
CONFIG_DWC_ETH_QOS=y
CONFIG_GMAC_ROCKCHIP=y
另外 dwc_eth_qos 驱动需要配置 nocache memory,参考 RV1126:
diff --git a/include/configs/rv1126_common.h b/include/configs/rv1126_common.h
index 933917f3f0..9d70795fb8 100644
--- a/include/configs/rv1126_common.h
+++ b/include/configs/rv1126_common.h
@@ -50,6 +50,7 @@
#define CONFIG_SYS_SDRAM_BASE 0
#define SDRAM_MAX_SIZE 0xfd000000
+#define CONFIG_SYS_NONCACHED_MEMORY (1 << 20) /* 1 MiB */
#ifndef CONFIG_SPL_BUILD
- cmd 配置 需要的功能手动配置上。
Command line interface ---> Network commands --->
[*] bootp, tftpboot
[ ] tftp put
[ ] tftp download and bootm
[ ] tftp download and flash
[ ] tftpsrv
[ ] rarpboot
-*- dhcp
-*- pxe
[ ] nfs
-*- mii
-*- ping
[ ] cdp
[ ] sntp
[ ] dns
[ ] linklocal
[ ] ethsw
相关接口
- 数据结构初始化接口
void net_init(void);
int eth_register(struct eth_device *dev);
int phy_init(void);
- 设备注册接口
int eth_register(struct eth_device *dev);
int phy_register(struct phy_driver *drv);
- 网络数据读写 和 phy 读写 U-Boot的数据收发需要主动调用,没有采用中断或轮询方式,具体实现可参照 NetLoop().
int eth_send(void *packet, int length);
int eth_rx(void);
int phy_read(struct phy_device *phydev, int devad, int regnum);
int phy_write(struct phy_device *phydev, int devad, int regnum, u16 val);
DTS 配置
DTS 节点与 kernel 一样,需要关注的是以下板级相关的属性配置:
-
phy 接口配置(phy-mode)
-
phy 复位脚与复位时间(snps,reset-gpio) (snps,reset-delays-us)
-
针对主控的时钟输出方向(clock_in_out)
-
时钟源选择与频率设定(assigned-clock-parents) (assigned-clock-rates)
-
RGMII Delayline, RGMII 接口需要(tx_delay) (rx_delay)
&gmac {
phy-mode = "rgmii";
clock_in_out = "input";
snps,reset-gpio = <&gpio3 RK_PA0 GPIO_ACTIVE_LOW>;
snps,reset-active-low;
/* Reset time is 20ms, 100ms for rtl8211f */
snps,reset-delays-us = <0 20000 100000>;
assigned-clocks = <&cru CLK_GMAC_SRC>, <&cru CLK_GMAC_TX_RX>, <&cru CLK_GMAC_ETHERNET_OUT>;
assigned-clock-parents = <&cru CLK_GMAC_SRC_M1>, <&cru RGMII_MODE_CLK>;
assigned-clock-rates = <125000000>, <0>, <25000000>;
pinctrl-names = "default";
pinctrl-0 = <&rgmiim1_pins &clk_out_ethernetm1_pins>;
tx_delay = <0x2a>;
rx_delay = <0x1a>;
phy-handle = <&phy>;
status = "okay";
};
使用示例
常用的网络命令:
- DHCP
Usage:
dhcp [loadAddress] [[hostIPaddr:]bootfilename]
使用这条命令,就不需要设置 serverip,ipaddr,以及 gateway 了。 当 dhcp 成功从 dhcp 服务器上面拿到 ip 地址后,其就会从 hostIPaddr 地址,以 tftp 的方式获取文件。
100M 环境:
=> dhcp 0x20000000 192.168.0.100:kernel.img
ethernet@ffc40000 Waiting for PHY auto negotiation to complete. done
BOOTP broadcast 1
DHCP client bound to address 192.168.0.106 (2 ms)
Using ethernet@ffc40000 device
TFTP from server 192.168.0.100; our IP address is 192.168.0.106
Filename 'kernel.img'.
Load address: 0x20000000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
###############################################################
1.5 MiB/s
done
Bytes transferred = 19054084 (122be04 hex)
- PING
=> ping 192.168.0.1
ethernet@ffc40000 Waiting for PHY auto negotiation to complete. done
Using ethernet@ffc40000 device
host 192.168.0.1 is alive
- TFTP
1000M 环境:
Usage:
tftp [loadAddress] [[hostIPaddr:]bootfilename]
也可以自己设置 IP 地址:
=> setenv ipaddr 192.168.1.101
=> setenv serverip 192.168.1.100
=> tftp kernel.img 0x20000000
ethernet@ffc40000 Waiting for PHY auto negotiation to complete. done
Using ethernet@ffc40000 device
TFTP from server 192.168.1.100; our IP address is 192.168.1.101
Filename 'kernel.img'.
Load address: 0x20000000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################
12.2 MiB/s
done
Bytes transferred = 20275220 (1356014 hex)
网络故障排查
1. 网络环境,常见的有下面几个方向
-
电脑端防火墙是否没关;
-
如果是跨网段的,确认网关是否设置;
-
TFTP 服务器配置是否正确;
-
某些路由器的 TFTP 功能是否被关闭。
2. 代码问题,一般来说,主要确认以下3个地方:
-
pinctrl 配置是否正确。检查相关 pin 的 iomux 和 驱动强度是否正确,也可以 dump 相关寄存器与内核比较是否一致,大部分情况下,我们是先调通了内核的网络再调 U-Boot 的。
-
clock 配置是否正确。时钟配置的检查相对麻烦一些,主要检查分频比,时钟源,以及时钟方向,大部分寄存器在 CRU 里面,也存在有的芯片部分寄存器在 GRF,同样可以 dump 相关寄存器 与内核的比较是否一致。
-
PHY 复位脚。主要检测复位脚配置是否正确,以及复位波形是否符合 PHY 的要求。
PCIe
框架支持
框架代码:
./drivers/pci/*
./drivers/phy/*
驱动代码:
drivers/pci/pcie_dw_rockchip.c
drivers/phy/phy-rockchip-snps-pcie3.c
menuconfig 配置:
- 驱动配置
Rockchip PCIe驱动目前支持的平台请查看pcie_dw_rockchip.c文件中的compatible属性,如果对驱动的选择有疑问,请参考我们对应的 sdk config 配置。
CONFIG_DM_REGULATOR_GPIO=y
CONFIG_PCI=y
CONFIG_DM_PCI=y
CONFIG_DM_PCI_COMPAT=y
CONFIG_PCI_PNP=y
CONFIG_PCIE_DW_ROCKCHIP=y
CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=y
CONFIG_PHY=y
CONFIG_CMD_PCI=y
//添加NVMe支持
CONFIG_NVME=y
CONFIG_CMD_NVME=y
//添加PCIe转USB支持
CONFIG_USB_XHCI_PCI=y
DTS 配置
直接复用内核DTB节点,相关文档请参考内核PCIe配置说明。
使用示例
常用的命令:
- pci
// 启动PCIe扫描, 总线枚举识别到一个Gen3的2个lane的设备
=> pci enum
PCIe Linking... LTSSM is 0x1
PCIe Link up, LTSSM is 0x230011
PCIE-0: Link up (Gen3-x2, Bus0)
// 扫描bus0, 默认是bridge设备
=> pci scan
Scanning PCI devices on bus 0
BusDevFun VendorId DeviceId Device Class Sub-Class
_____________________________________________________________
00.00.00 0x1d87 0x3566 Bridge device 0x04
// 扫描bus1, 目前看到接的是一个NVMe
=> pci 1
Scanning PCI devices on bus 1
BusDevFun VendorId DeviceId Device Class Sub-Class
_____________________________________________________________
01.00.00 0x144d 0xa808 Mass storage controller 0x08
// 发起nvme 扫描
=> nvme scan
// 罗列nvme设备详细信息
=> nvme details
Blk device 0: Optional Admin Command Support:
Namespace Management/Attachment: no
Firmware Commit/Image download: yes
Format NVM: yes
Security Send/Receive: no
Blk device 0: Optional NVM Command Support:
Reservation: yes
Save/Select field in the Set/Get features: yes
Write Zeroes: yes
Dataset Management: yes
Write Uncorrectable: yes
Blk device 0: Format NVM Attributes:
Support Cryptographic Erase: No
Support erase a particular namespace: Yes
Support format a particular namespace: Yes
Blk device 0: LBA Format Support:
Blk device 0: End-to-End DataProtect Capabilities:
As last eight bytes: No
As first eight bytes: No
Support Type3: No
Support Type2: No
Support Type1: No
Blk device 0: Metadata capabilities:
As part of a separate buffer: No
As part of an extended data LBA: No
// 看到一个256GB的NVMe, 如果看不到容量,需要拔出设备确保完全掉电,重来。
=> nvme info
Device 0: Vendor: 0x144d Rev: EXD7201Q Prod: S444NA0M384608
Type: Hard Disk
Capacity: 244198.3 MB = 238.4 GB (500118192 x 512)
// 选择ID为0的nvme设备
=> nvme device 0
Device 0: Vendor: 0x144d Rev: EXD7201Q Prod: S444NA0M384608
Type: Hard Disk
Capacity: 244198.3 MB = 238.4 GB (500118192 x 512)
... is now current device
// 将0x40000000内存设置位0x55aa55aa
=> md.l 0x40000000 1
40000000: d08ec033 3...
=> mw.l 0x40000000 0x55aa55aa
=> md.l 0x40000000 1
40000000: 55aa55aa .U.U
// 从0x40000000内存开始取1个block数据,写入NVME的LBA 0地址
=> nvme write 0x40000000 0x0 0x1
nvme write: device 0 block # 0, count 1 ... 1 blocks written: OK
// 检查下0x44000000内存,确认原始数据
=> md.l 0x44000000 1
44000000: ffffffff ....
// 从NVMe的LBA 0地址,读取1个block数据,写入内存0x44000000
=> nvme read 0x44000000 0x0 0x1
nvme read: device 0 block # 0, count 1 ... 1 blocks read: OK
// 确认0x44000000内存数据是从NVMe读回来的
=> md.l 0x44000000 1
44000000: 55aa55aa .U.U