# PMU HAL 接口文档

## 概述

K230 提供了 PMU HAL 接口，用于在系统运行态处理中长按关机流程，以及配置 RTC 定时关机/定时开机。

- 用户态 HAL 头文件：`src/rtsmart/libs/rtsmart_hal/drivers/pmu/drv_pmu.h`
- 用户态 HAL 实现：`src/rtsmart/libs/rtsmart_hal/drivers/pmu/drv_pmu.c`
- 参考示例：`src/rtsmart/examples/peripheral/pmu/test_pmu.c`

PMU HAL 当前主要覆盖两类能力：

1. 长按电源键关机通知与用户态 ACK
1. RTC 定时关机、定时开机（power cycle）

> 注意：RTC 普通读写时间、普通 alarm/tick 中断配置属于 RTC 设备接口能力，不在 `drv_pmu.h` 这个 HAL 头文件内。

---

## 事件模型

长按关机流程由内核驱动和用户态 HAL 协同完成：

1. 用户态调用 `drv_pmu_register_notify()` 注册信号通知
1. 用户长按电源键达到阈值后，驱动上报 `DRV_PMU_EVENT_LONG_PRESS`
1. 用户态完成保存状态、卸载文件系统等清理动作
1. 用户松开电源键后，驱动上报 `DRV_PMU_EVENT_KEY_RELEASE`
1. 用户态调用 `drv_pmu_ack_shutdown()` 发送 ACK
1. 驱动收到 ACK 后执行真正关机

如果用户态不发送 ACK，则系统会保持当前运行状态，不会自动关机。

---

## 数据结构说明

### `drv_pmu_inst_t`

**描述**：PMU HAL 实例句柄，内部封装了 `/dev/pmu_pwrkey` 设备节点、信号等待集和通知注册状态。该类型对用户透明。

### `drv_pmu_event_t`

**描述**：PMU 事件位图类型。

### `DRV_PMU_EVENT_LONG_PRESS`

**描述**：检测到长按关机事件。

### `DRV_PMU_EVENT_KEY_RELEASE`

**描述**：长按之后检测到电源键松开事件。

---

## 函数接口说明

### `int drv_pmu_inst_create(drv_pmu_inst_t **inst);`

**功能**：创建 PMU HAL 实例并打开 `/dev/pmu_pwrkey`。

**参数**：

- `inst`：返回创建好的 PMU 实例

**返回值**：

- `0`：成功
- `-1`：失败

---

### `void drv_pmu_inst_destroy(drv_pmu_inst_t **inst);`

**功能**：销毁 PMU HAL 实例，自动注销通知并关闭设备。

**参数**：

- `inst`：PMU 实例指针的指针

---

### `int drv_pmu_register_notify(drv_pmu_inst_t *inst, int signo);`

**功能**：注册 PMU 事件通知。

注册成功后，驱动会向当前进程发送指定信号，用户态可通过 `drv_pmu_wait_event()` 等待并读取事件。

**参数**：

- `inst`：PMU 实例
- `signo`：通知信号编号；小于等于 `0` 时默认使用 `SIGUSR1`

**返回值**：

- `0`：成功
- `-1`：失败

**说明**：

- HAL 内部会自动阻塞该信号，并在销毁或注销时恢复
- 重复调用会先注销旧通知，再重新注册

---

### `int drv_pmu_unregister_notify(drv_pmu_inst_t *inst);`

**功能**：注销 PMU 事件通知。

**参数**：

- `inst`：PMU 实例

**返回值**：

- `0`：成功
- `-1`：失败

---

### `int drv_pmu_wait_event(drv_pmu_inst_t *inst, drv_pmu_event_t *event, int timeout_ms);`

**功能**：等待 PMU 事件并读取事件位图。

**参数**：

- `inst`：PMU 实例
- `event`：返回的事件位图
- `timeout_ms`：等待超时，单位 ms；小于 `0` 表示永久等待

**返回值**：

- `0`：成功收到事件
- `1`：超时，或等待被信号中断
- `-1`：失败

**说明**：

- 调用前必须已经执行 `drv_pmu_register_notify()`
- 成功时返回的事件可能同时包含多个 bit

---

### `int drv_pmu_ack_shutdown(drv_pmu_inst_t *inst);`

**功能**：向驱动发送关机 ACK。

通常应在收到 `DRV_PMU_EVENT_KEY_RELEASE` 后调用，表示用户态清理完成并允许系统关机。

**参数**：

- `inst`：PMU 实例

**返回值**：

- `0`：成功
- `-1`：失败

---

### `int drv_pmu_schedule_power_cycle(drv_pmu_inst_t *inst, uint32_t shutdown_after_s, uint32_t poweron_after_s);`

**功能**：配置一次 RTC 定时关机/开机流程。

系统会在 `shutdown_after_s` 秒后关机，并在关机后再等待 `poweron_after_s` 秒自动开机。

**参数**：

- `inst`：PMU 实例
- `shutdown_after_s`：距离关机的延迟时间，单位秒
- `poweron_after_s`：距离重新开机的延迟时间，单位秒

**返回值**：

- `0`：成功
- `-1`：失败

**说明**：

- 当前驱动要求两个参数通常都不小于 `2`
- `poweron_after_s` 是从关机时刻开始计时，不是从调用接口时刻开始计时

---

### `int drv_pmu_cancel_power_cycle(drv_pmu_inst_t *inst);`

**功能**：取消当前已配置的 RTC 定时关机/开机流程。

**参数**：

- `inst`：PMU 实例

**返回值**：

- `0`：成功
- `-1`：失败

---

### `int drv_pmu_event_has_long_press(drv_pmu_event_t event);`

**功能**：判断事件位图中是否包含长按事件。

**参数**：

- `event`：PMU 事件位图

**返回值**：

- 非 `0`：包含 `DRV_PMU_EVENT_LONG_PRESS`
- `0`：不包含

---

### `int drv_pmu_event_has_key_release(drv_pmu_event_t event);`

**功能**：判断事件位图中是否包含按键松开事件。

**参数**：

- `event`：PMU 事件位图

**返回值**：

- 非 `0`：包含 `DRV_PMU_EVENT_KEY_RELEASE`
- `0`：不包含

---

## 推荐使用流程

### 长按关机场景

1. 调用 `drv_pmu_inst_create()` 创建设备句柄
1. 调用 `drv_pmu_register_notify()` 注册 PMU 事件通知
1. 循环调用 `drv_pmu_wait_event()` 等待事件
1. 收到 `DRV_PMU_EVENT_LONG_PRESS` 后执行用户态清理
1. 收到 `DRV_PMU_EVENT_KEY_RELEASE` 后调用 `drv_pmu_ack_shutdown()`
1. 退出前调用 `drv_pmu_inst_destroy()` 释放资源

### RTC 定时开关机场景

1. 调用 `drv_pmu_inst_create()` 创建设备句柄
1. 调用 `drv_pmu_schedule_power_cycle()` 配置关机与开机延时
1. 如需取消，调用 `drv_pmu_cancel_power_cycle()`
1. 结束后调用 `drv_pmu_inst_destroy()` 释放资源

---

## 最小示例

### 长按关机监听示例

```c
#include <stdio.h>
#include "drv_pmu.h"

int pmu_wait_shutdown(void)
{
    drv_pmu_inst_t *pmu = NULL;
    drv_pmu_event_t event;

    if (drv_pmu_inst_create(&pmu) < 0)
        return -1;

    if (drv_pmu_register_notify(pmu, 0) < 0)
        goto err;

    for (;;) {
        int ret = drv_pmu_wait_event(pmu, &event, -1);

        if (ret < 0)
            goto err;
        if (ret > 0)
            continue;

        if (drv_pmu_event_has_long_press(event)) {
            /* 执行用户态清理 */
        }

        if (drv_pmu_event_has_key_release(event)) {
            if (drv_pmu_ack_shutdown(pmu) < 0)
                goto err;
            break;
        }
    }

    drv_pmu_inst_destroy(&pmu);
    return 0;

err:
    drv_pmu_inst_destroy(&pmu);
    return -1;
}
```

### RTC 定时开关机示例

```c
#include <stdint.h>
#include "drv_pmu.h"

int pmu_schedule_cycle(uint32_t shutdown_after_s, uint32_t poweron_after_s)
{
    drv_pmu_inst_t *pmu = NULL;
    int ret = -1;

    if (drv_pmu_inst_create(&pmu) < 0)
        return -1;

    if (drv_pmu_schedule_power_cycle(pmu,
                                     shutdown_after_s,
                                     poweron_after_s) < 0)
        goto out;

    ret = 0;

out:
    drv_pmu_inst_destroy(&pmu);
    return ret;
}
```

---

## 注意事项

1. 使用前需要确保系统已启用 `RT_USING_RTC_PMU`，并且存在设备节点 `/dev/pmu_pwrkey`。
1. `drv_pmu_wait_event()` 依赖信号通知机制，建议由专门线程统一等待和处理。
1. `drv_pmu_ack_shutdown()` 应在收到 `DRV_PMU_EVENT_KEY_RELEASE` 后调用。
1. 如果应用决定不关机，可以不发送 ACK，系统会继续运行。
1. `drv_pmu_schedule_power_cycle()` 依赖 RTC 当前时间正确，使用前建议先确认 RTC 时间已设置。
1. 当前 HAL 不配置“关机后长按开机”的硬件长按阈值，该阈值由底层 PMU 寄存器策略决定。

---

## 使用示例

请参考 `src/rtsmart/examples/peripheral/pmu/test_pmu.c`
