RK806S-5是RK3576配备的PMIC电源管理芯片,SoC的上电启动逻辑由其控制。
上电启动过程中,RK806S-5一般在uboot阶段接管电源控制,所以其对应的驱动在u-boot/drivers/power/pmic/rk8xx.c
关于下电启动,当进入Linux系统以后,则由一个脚本来控制关机逻辑/usr/bin/power-key.sh
在进入系统以后,默认的点按PWRON按钮是进入系统休眠,我们的需求为关机,所以在short_press()里直接加入poweroff关机指令即可
#!/bin/sh
EVENT=${1:-short-press}
LONG_PRESS_TIMEOUT=3 # s
DEBOUNCE=2 # s
PID_FILE=/var/run/power_key.pid
LOCK_FILE=/var/run/.power_key.lock
log()
{
logger -t $(basename $0) "[$$]: $@"
}
# HACK: Monitoring power state changes and update the modified time
while ! pgrep -x inotifywait >/dev/null; do
inotifywait -e modify /sys/power/state >/dev/null 2>&1
# Avoid race with freezing processes
sleep .2
touch /sys/power/state
done&
parse_wakeup_time()
{
LAST_MODIFY="$(stat -c "%Y" /sys/power/state)"
NOW="$(date "+%s")"
WAKE_TIME="$(expr "$NOW" - "$LAST_MODIFY")"
log "Last state changed: $(date -d "@$LAST_MODIFY" "+%D %T")..."
}
short_press()
{
log "Power key short press..."
poweroff
if which systemctl >/dev/null; then
SUSPEND_CMD="systemctl suspend"
elif which pm-suspend >/dev/null; then
SUSPEND_CMD="pm-suspend"
else
SUSPEND_CMD="echo -n mem > /sys/power/state"
fi
# Debounce
if [ -f $LOCK_FILE ]; then
log "Too close to the latest request..."
return 0
fi
if parse_wakeup_time; then
if [ "$WAKE_TIME" -le $DEBOUNCE ]; then
log "We might just resumed!"
return 0
fi
fi
log "Prepare to suspend..."
touch $LOCK_FILE
sh -c "$SUSPEND_CMD"
{ sleep $DEBOUNCE && rm $LOCK_FILE; }&
}
long_press()
{
log "Power key long press (${LONG_PRESS_TIMEOUT}s)..."
log "Prepare to power off..."
poweroff
}
log "Received power key event: $@..."
case "$EVENT" in
press)
# Lock it
exec 3<$0
flock -x 3
start-stop-daemon -K -q -p $PID_FILE || true
start-stop-daemon -S -q -b -m -p $PID_FILE -x /bin/sh -- \
-c "sleep $LONG_PRESS_TIMEOUT; $0 long-press"
# Unlock
flock -u 3
;;
release)
# Avoid race with press event
sleep .5
start-stop-daemon -K -q -p $PID_FILE && short_press
;;
short-press)
short_press
;;
long-press)
long_press
;;
esac
当RK806S-5使用i2c通讯时,对应使用rk8xx.c,当使用spi通讯时,对应同文件目录的rk8xx_spi.c
uboot同样符合device--driver的驱动框架
下面我们以rk8xx.c的驱动为例,测试实现一个开机长按PWRON按键启动
+ #include <boot_rkimg.h>
+ int mode;
/* read Chip variant */
if (device_is_compatible(dev, "rockchip,rk817") ||
device_is_compatible(dev, "rockchip,rk809")) {
id_msb = RK817_ID_MSB;
id_lsb = RK817_ID_LSB;
} else if (device_is_compatible(dev, "rockchip,rk806")) {
id_msb = RK806_CHIP_NAME;
id_lsb = RK806_CHIP_VER;
} else {
id_msb = ID_MSB;
id_lsb = ID_LSB;
}
ret = rk8xx_read(dev, id_msb, &msb, 1);
if (ret)
return ret;
ret = rk8xx_read(dev, id_lsb, &lsb, 1);
if (ret)
return ret;
priv->variant = ((msb << 8) | lsb) & RK8XX_ID_MSK;
show_variant = priv->variant;
+ /* 10*100ms */
+ mode = rockchip_get_boot_mode();
+ printf("rk806s: mode: %d\n", mode);
+ printf("pmic 0x5d = %x\n", pmic_reg_read(dev, 0x5d)); //SYS_STS
+ printf("pmic 0x74 = %x\n", pmic_reg_read(dev, 0x74)); //ON_SOURCE
+
+ if ((pmic_reg_read(dev, 0x5d) & 0x80) && ((pmic_reg_read(dev, 0x74) & 0x80) | (pmic_reg_read(dev, 0x74) & 0x40)) &&
+ mode == 11) {
+ i = 0;
+ while (i < 10) {
+ value = pmic_reg_read(dev, 0x5d);
+ if (value & 0x80) {
+ printf("rk806s: power off\n");
+ rk8xx_shutdown(dev);
+ }
+ mdelay(100);
+ i++;
+ }
+ printf("rk806s: power on\n");
+ }
需要了解几个开机启动相关的寄存器
SYS_STS(0x5d)
系统状态寄存器,反映 RK806S PMIC 的关键硬件状态,所有位为只读(RO)
0x5d的BIT(7)反映的是PWRON按键的状态
ON_SOURCE(0x74)
系统状态寄存器,反映 RK806S PMIC 的关键硬件状态,所有位为只读(RO)
ON_VDC为电源适配器12V插入检测,即当SoC从断电状态接入12V电源适配器时,0x74的值则为0x40
当SoC在12V电源已经接入,且处于关机状态时,使用PWRON按键开机,0x74的值则为0x80
查看PWRON按键开机说明,默认情况下只需要20ms或500ms的点按就能实现开机,然而为了排除误触的情况,我们通过驱动实现长按开机
解析此处的核心代码
当点按PWRON按键时,rk806上电开始启动bootloader和uboot,在执行到此处时检测到是由于按键启动的,所以0x5d的值是0x80,0x74的值是0x80,在一般情况下,mode都默认是none的,所以mode值为11,因此就进入了我们的if判断,若是由于按键启动的,则直接调用rk8xx_shutdown函数关掉,进入这个函数会看到实际是通过i2c给0x72寄存器的BIT(0)写1,使其关机。
所以此操作就实现了点按PWRON按键无法开机,避免了误触情况。
在判断里加上0x74寄存器为0x40的情况,则避免了12V电源插入自动启动
而长按开机时,0x5d实际读到的值是0x00,所以不会进入此处的判断
关于此处按键寄存器检测的值,从逻辑上来看更像是按下为0x00,不按为0x80,这里后续还要再想办法测试一下,即rk806芯片手册与实际描述是相反的。或者与PWRON按键接vcc或GND有关,因为rk806手册按下按键时电平状态是拉低的
总之实现的逻辑是这样的,具体的场景需要微调寄存器判断的值
if ((pmic_reg_read(dev, 0x5d) & 0x80) && ((pmic_reg_read(dev, 0x74) & 0x80) | (pmic_reg_read(dev, 0x74) & 0x40)) &&
mode == 11) {
i = 0;
while (i < 10) {
value = pmic_reg_read(dev, 0x5d);
if (value & 0x80) {
printf("rk806s: power off\n");
rk8xx_shutdown(dev);
}
mdelay(100);
i++;
}
printf("rk806s: power on\n");
}
static int rk8xx_shutdown(struct udevice *dev)
{
struct rk8xx_priv *priv = dev_get_priv(dev);
u8 val, dev_off, devctrl_reg;
int ret = 0;
switch (priv->variant) {
case RK806_ID:
devctrl_reg = RK806_SYS_CFG3;
dev_off = RK806_DEV_OFF;
break;
case RK808_ID:
devctrl_reg = REG_DEVCTRL;
dev_off = BIT(3);
break;
case RK805_ID:
case RK816_ID:
case RK818_ID:
devctrl_reg = REG_DEVCTRL;
dev_off = BIT(0);
break;
case RK809_ID:
case RK817_ID:
devctrl_reg = RK817_REG_SYS_CFG3;
dev_off = BIT(0);
break;
default:
printf("Unknown PMIC: RK%x\n", priv->variant);
return -EINVAL;
}
ret = rk8xx_read(dev, devctrl_reg, &val, 1);
if (ret)
return ret;
val |= dev_off;
ret = rk8xx_write(dev, devctrl_reg, &val, 1);
if (ret)
return ret;
return 0;
}