feat: 添加Atguigu键盘板支持和IP5306电源管理驱动

添加了两个新的键盘板支持:
- atguigu_keyboard_dongle (基于nrf52833)
- atguigu_mini_keyboard (基于nrf52840)

同时添加了完整的IP5306电源管理芯片驱动,包括:
- 支持充电状态检测
- 提供软硬件保活脉冲功能
- 硬件后端利用nRF的RTC+GPIOTE+GPPI实现低功耗保活
- 软件后端作为备用方案
This commit is contained in:
2026-04-10 08:18:46 +08:00
parent 2356cb4fe8
commit 908e7a0a4d
27 changed files with 993 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
config BOARD_ATGUIGU_KEYBOARD_DONGLE
select SOC_NRF52833_QDAA

View File

@@ -0,0 +1,2 @@
&pinctrl {
};

View File

@@ -0,0 +1,42 @@
/dts-v1/;
#include <nordic/nrf52833_qdaa.dtsi>
#include "atguigu_keyboard_dongle-pinctrl.dtsi"
/ {
model = "Keyboard Dongle";
compatible = "atguigu,atguigu-keyboard-dongle";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 DT_SIZE_K(48)>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000c000 DT_SIZE_K(220)>;
};
slot1_partition: partition@43000 {
label = "image-1";
reg = <0x00043000 DT_SIZE_K(220)>;
};
storage_partition: partition@7a000 {
label = "storage";
reg = <0x0007a000 DT_SIZE_K(24)>;
};
};
};

View File

@@ -0,0 +1,10 @@
identifier: atguigu_keyboard_dongle/nrf52833
name: Keyboard Dongle
vendor: atguigu
type: mcu
arch: arm
ram: 128
flash: 512
toolchain:
- zephyr
supported: []

View File

@@ -0,0 +1,2 @@
CONFIG_ARM_MPU=y
CONFIG_HW_STACK_PROTECTION=y

View File

@@ -0,0 +1,9 @@
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
board_runner_args(jlink "--device=nRF52833_xxAA" "--speed=4000")
board_runner_args(pyocd "--target=nrf52833" "--frequency=4000000")
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)

View File

@@ -0,0 +1,5 @@
board:
name: atguigu_keyboard_dongle
vendor: atguigu
socs:
- name: nrf52833

View File

@@ -0,0 +1,2 @@
# Suppress "unique_unit_address_if_enabled" to handle some overlaps
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View File

@@ -0,0 +1,2 @@
config BOARD_ATGUIGU_MINI_KEYBOARD
select SOC_NRF52840_QFAA

View File

@@ -0,0 +1,73 @@
&pinctrl {
qdec_default: qdec_default {
group1 {
psels = <NRF_PSEL(QDEC_A, 0, 10)>,
<NRF_PSEL(QDEC_B, 1, 6)>;
bias-pull-up;
};
};
qdec_sleep: qdec_sleep {
group1 {
psels = <NRF_PSEL(QDEC_A, 0, 10)>,
<NRF_PSEL(QDEC_B, 1, 6)>;
low-power-enable;
};
};
led_spi_default: led_spi_default {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
};
};
led_spi_sleep: led_spi_sleep {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
low-power-enable;
};
};
lcd_spi_default: lcd_spi_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
};
};
lcd_spi_sleep: lcd_spi_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
low-power-enable;
};
};
i2c1_default: i2c1_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
<NRF_PSEL(TWIM_SCL, 0, 24)>;
bias-pull-up;
};
};
i2c1_sleep: i2c1_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
<NRF_PSEL(TWIM_SCL, 0, 24)>;
low-power-enable;
};
};
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
low-power-enable;
};
};
};

View File

@@ -0,0 +1,240 @@
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "atguigu_mini_keyboard-pinctrl.dtsi"
#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>
#include <zephyr/dt-bindings/led/led.h>
/ {
model = "Mini Keyboard, 17 keys";
compatible = "atguigu,atguigu-mini-keyboard";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,display = &st7789v3;
};
aliases {
backlight = &backlight;
};
mode_sense: mode-sense {
compatible = "voltage-divider";
io-channels = <&adc 5>;
output-ohms = <1>;
full-ohms = <1>;
};
battery_sense: battery-sense {
compatible = "voltage-divider";
io-channels = <&adc 7>;
output-ohms = <100000>;
full-ohms = <200000>;
power-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
power-on-sample-delay-us = <100>;
};
pwm_leds: pwm_leds {
compatible = "pwm-leds";
status = "okay";
backlight: pwm_led_0 {
pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_INVERTED>;
};
};
led_0: led_0 {
compatible = "gpio-leds";
status = "okay";
chan0 {
gpios = <&gpio0 17 GPIO_ACTIVE_LOW>;
};
};
led_1: led_1 {
compatible = "gpio-leds";
status = "okay";
chan0 {
gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
};
};
mipi_dbi: mipi_dbi {
compatible = "zephyr,mipi-dbi-spi";
status = "okay";
spi-dev = <&spi3>;
dc-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
write-only;
#address-cells = <1>;
#size-cells = <0>;
st7789v3: st7789v@0 {
compatible = "sitronix,st7789v";
status = "okay";
reg = <0>;
mipi-max-frequency = <32000000>;
width = <320>;
height = <172>;
x-offset = <0>;
y-offset = <34>;
vcom = <0x2b>;
gctrl = <0x35>;
vrhs = <0x11>;
vdvs = <0x20>;
mdac = <0xA0>;
lcm = <0x2c>;
colmod = <0x55>;
gamma = <0x01>;
porch-param = [ 0c 0c 00 33 33 ];
cmd2en-param = [ 5a 69 02 01 ];
pwctrl1-param = [ a4 a1 ];
pvgam-param = [ d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17 ];
nvgam-param = [ d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e ];
ram-param = [ 00 f0 ];
rgb-param = [ c0 02 14 ];
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
};
};
};
&spi2 {
status = "okay";
pinctrl-0 = <&led_spi_default>;
pinctrl-1 = <&led_spi_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>;
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
supply-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
reg = <0>;
spi-max-frequency = <8000000>;
chain-length = <17>;
color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;
spi-one-frame = <0xFC>;
spi-zero-frame = <0xC0>;
};
};
&i2c1 {
status = "okay";
pinctrl-0 = <&i2c1_default>;
pinctrl-1 = <&i2c1_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <400000>;
ip5306: pmic@75 {
status = "okay";
compatible = "injoinic,ip5306";
reg = <0x75>;
keepalive-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
keepalive-interval-ms = <10000>;
keepalive-offload;
};
};
/* 编码器 */
&qdec {
status = "okay";
/* 引用上面定义的标签 */
pinctrl-0 = <&qdec_default>;
pinctrl-1 = <&qdec_sleep>;
/* 指定 pinctrl-0 为默认pinctrl-1 为睡眠 */
pinctrl-names = "default", "sleep";
/* 别忘了 QDEC 必须的两个属性 */
steps = <40>;
led-pre = <0>;
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 DT_SIZE_K(48)>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000c000 DT_SIZE_K(472)>;
};
slot1_partition: partition@82000 {
label = "image-1";
reg = <0x00082000 DT_SIZE_K(472)>;
};
storage_partition: partition@f8000 {
label = "storage";
reg = <0x000f8000 DT_SIZE_K(32)>;
};
};
};
&spi3 {
status = "okay";
pinctrl-0 = <&lcd_spi_default>;
pinctrl-1 = <&lcd_spi_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
};
&adc {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@5 {
reg = <5>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
zephyr,input-positive = <NRF_SAADC_AIN5>;
zephyr,resolution = <12>;
};
channel@7 {
reg = <7>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
zephyr,input-positive = <NRF_SAADC_AIN7>;
zephyr,resolution = <12>;
};
};
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&gpiote {
status = "okay";
};
&usbd {
status = "okay";
};
&uicr {
nfct-pins-as-gpios;
gpio-as-nreset;
};

View File

@@ -0,0 +1,10 @@
identifier: atguigu_mini_keyboard/nrf52840
name: Mini Keyboard, 17 keys
vendor: atguigu
type: mcu
arch: arm
ram: 256
flash: 1024
toolchain:
- zephyr
supported: []

View File

@@ -0,0 +1,2 @@
CONFIG_ARM_MPU=y
CONFIG_HW_STACK_PROTECTION=y

View File

@@ -0,0 +1,9 @@
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)

View File

@@ -0,0 +1,5 @@
board:
name: atguigu_mini_keyboard
vendor: atguigu
socs:
- name: nrf52840

View File

@@ -0,0 +1,2 @@
# Suppress "unique_unit_address_if_enabled" to handle some overlaps
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View File

@@ -0,0 +1,5 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306.c)
zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306_keepalive_sw.c)
zephyr_library_sources_ifdef(CONFIG_IP5306_KEEPALIVE_HW_NRF drivers/power/ip5306_keepalive_nrf.c)
zephyr_include_directories(include)

5
modules/ip5306/Kconfig Normal file
View File

@@ -0,0 +1,5 @@
menu "IP5306 Module"
rsource "drivers/power/Kconfig"
endmenu

View File

@@ -0,0 +1,17 @@
config IP5306
bool "Injoinic IP5306 PMIC driver"
depends on I2C && GPIO
default n
help
Enable IP5306 PMIC driver over I2C.
config IP5306_KEEPALIVE_HW_NRF
bool "Enable nRF keepalive HW offload backend"
depends on IP5306 && SOC_FAMILY_NORDIC_NRF
select NRFX_GPIOTE
select NRFX_GPPI
select NRFX_RTC2
default y
help
Enable low-power keepalive offload backend using RTC2 + GPIOTE + GPPI.
Runtime selection is controlled by devicetree property keepalive-offload.

View File

@@ -0,0 +1,152 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/power/ip5306.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include "ip5306_priv.h"
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
#include <soc_nrf_common.h>
#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \
.keepalive_psel = NRF_DT_GPIOS_TO_PSEL_OR(DT_DRV_INST(inst), keepalive_gpios, 0),
#else
#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst)
#endif
LOG_MODULE_REGISTER(ip5306, LOG_LEVEL_INF);
#define DT_DRV_COMPAT injoinic_ip5306
#define IP5306_REG_READ0 0x70
#define IP5306_REG_READ1 0x71
#define IP5306_STATUS_BIT BIT(3)
static int ip5306_read_reg(const struct device *dev, uint8_t reg, uint8_t *val)
{
const struct ip5306_config *cfg = dev->config;
return i2c_reg_read_byte_dt(&cfg->i2c, reg, val);
}
static int ip5306_get_status_bit(const struct device *dev, uint8_t reg, bool *flag)
{
uint8_t value = 0U;
int ret;
if (flag == NULL) {
return -EINVAL;
}
ret = ip5306_read_reg(dev, reg, &value);
if (ret != 0) {
return ret;
}
*flag = ((value & IP5306_STATUS_BIT) != 0U);
return 0;
}
static int ip5306_api_is_charging(const struct device *dev, bool *charging)
{
return ip5306_get_status_bit(dev, IP5306_REG_READ0, charging);
}
static int ip5306_api_is_charge_full(const struct device *dev, bool *full)
{
return ip5306_get_status_bit(dev, IP5306_REG_READ1, full);
}
static void ip5306_keepalive_start(const struct device *dev)
{
struct ip5306_data *data = dev->data;
const struct ip5306_config *cfg = dev->config;
if (!cfg->has_keepalive_gpio || (data->keepalive_interval_ms == 0U)) {
data->backend = IP5306_KEEPALIVE_BACKEND_NONE;
return;
}
/*
* 选择策略:
* 1) DTS 显式请求硬件 offload 时,先尝试硬件后端;
* 2) 若硬件依赖不可用或资源被占用,则告警后回退软件后端。
*/
if (cfg->keepalive_offload) {
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
int ret = ip5306_keepalive_hw_nrf_start(dev);
if (ret == 0) {
LOG_INF("Keepalive backend=HW(nRF), pulse=%ums interval=%ums",
data->keepalive_pulse_ms, data->keepalive_interval_ms);
return;
}
LOG_WRN("HW keepalive unavailable (%d), fallback to SW backend", ret);
#else
LOG_WRN("HW keepalive requested but HW backend is not built, fallback to SW backend");
#endif
}
ip5306_keepalive_sw_start(dev);
LOG_INF("Keepalive backend=SW, pulse=%ums interval=%ums",
data->keepalive_pulse_ms, data->keepalive_interval_ms);
}
static int ip5306_init(const struct device *dev)
{
const struct ip5306_config *cfg = dev->config;
struct ip5306_data *data = dev->data;
if (!i2c_is_ready_dt(&cfg->i2c)) {
return -ENODEV;
}
if (cfg->has_keepalive_gpio) {
if (!gpio_is_ready_dt(&cfg->keepalive_gpio)) {
return -ENODEV;
}
if (gpio_pin_configure_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE) != 0) {
return -EIO;
}
}
data->keepalive_high = false;
data->dev = dev;
data->backend = IP5306_KEEPALIVE_BACKEND_NONE;
data->keepalive_pulse_ms = (cfg->keepalive_pulse_ms != 0U) ?
cfg->keepalive_pulse_ms : IP5306_KEEPALIVE_DEFAULT_PULSE_MS;
data->keepalive_interval_ms = (cfg->keepalive_interval_ms != 0U) ?
cfg->keepalive_interval_ms : IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS;
ip5306_keepalive_start(dev);
return 0;
}
static const struct ip5306_driver_api ip5306_api = {
.is_charging = ip5306_api_is_charging,
.is_charge_full = ip5306_api_is_charge_full,
};
#define IP5306_DEFINE(inst) \
static struct ip5306_data ip5306_data_##inst; \
static const struct ip5306_config ip5306_cfg_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.keepalive_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, keepalive_gpios, {0}), \
IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \
.keepalive_pulse_ms = DT_INST_PROP(inst, keepalive_pulse_ms), \
.keepalive_interval_ms = DT_INST_PROP(inst, keepalive_interval_ms), \
.has_keepalive_gpio = DT_INST_NODE_HAS_PROP(inst, keepalive_gpios), \
.keepalive_offload = DT_INST_PROP_OR(inst, keepalive_offload, false), \
}; \
DEVICE_DT_INST_DEFINE(inst, ip5306_init, NULL, &ip5306_data_##inst, \
&ip5306_cfg_##inst, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&ip5306_api);
DT_INST_FOREACH_STATUS_OKAY(IP5306_DEFINE)

View File

@@ -0,0 +1,198 @@
#include <errno.h>
#include <hal/nrf_rtc.h>
#include <nrfx_gpiote.h>
#include <nrfx_rtc.h>
#include <helpers/nrfx_gppi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include "ip5306_priv.h"
LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF);
#define IP5306_KEEPALIVE_RTC_FREQUENCY_HZ 32768U
#define IP5306_KEEPALIVE_RTC_CC_HIGH 0U
#define IP5306_KEEPALIVE_RTC_CC_LOW 1U
#define IP5306_KEEPALIVE_RTC_CC_PERIOD 2U
#define IP5306_KEEPALIVE_MIN_PERIOD_TICKS 3U
/* nRF52 单核场景默认使用 GPIOTE0 + RTC2。 */
static nrfx_gpiote_t ip5306_keepalive_gpiote = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(0));
static const nrfx_rtc_t ip5306_keepalive_rtc = NRFX_RTC_INSTANCE(2);
static bool ip5306_keepalive_hw_claimed;
static void ip5306_keepalive_rtc_handler(nrfx_rtc_int_type_t int_type)
{
/*
* 这里故意不处理任何中断逻辑:
* 本模块的保活脉冲由 RTC event -> GPPI/PPI -> GPIOTE task 的硬件链路完成,
* 不依赖线程或 ISR 参与,从而减少 CPU 唤醒次数、降低功耗。
*/
ARG_UNUSED(int_type);
}
static uint32_t ip5306_keepalive_ms_to_rtc_ticks(uint32_t time_ms)
{
/* 四舍五入到最近 tick降低长时间运行时累计漂移。 */
uint64_t ticks = ((uint64_t)time_ms * IP5306_KEEPALIVE_RTC_FREQUENCY_HZ + 500U) / 1000U;
return (uint32_t)MAX(ticks, 1U);
}
int ip5306_keepalive_hw_nrf_start(const struct device *dev)
{
struct ip5306_data *data = dev->data;
const struct ip5306_config *cfg = dev->config;
const bool active_high = ((cfg->keepalive_gpio.dt_flags & GPIO_ACTIVE_LOW) == 0U);
const uint32_t period_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_interval_ms);
uint32_t high_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_pulse_ms);
int ret;
if (ip5306_keepalive_hw_claimed) {
return -EBUSY;
}
if (period_ticks < IP5306_KEEPALIVE_MIN_PERIOD_TICKS) {
return -EINVAL;
}
if (high_ticks >= (period_ticks - 1U)) {
high_ticks = period_ticks - 1U;
}
if (!nrfx_gpiote_init_check(&ip5306_keepalive_gpiote)) {
ret = nrfx_gpiote_init(&ip5306_keepalive_gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
if ((ret != 0) && (ret != -EALREADY)) {
return ret;
}
}
ret = nrfx_gpiote_channel_alloc(&ip5306_keepalive_gpiote, &data->keepalive_gpiote_channel);
if (ret != 0) {
/* GPIOTE 通道不足通常意味着被其他驱动占用。 */
LOG_ERR("Failed to allocate GPIOTE channel");
return ret;
}
const nrfx_gpiote_output_config_t output_config = {
.drive = NRF_GPIO_PIN_S0S1,
.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
.pull = NRF_GPIO_PIN_NOPULL,
};
const nrfx_gpiote_task_config_t task_config = {
.task_ch = data->keepalive_gpiote_channel,
.polarity = NRF_GPIOTE_POLARITY_TOGGLE,
.init_val = active_high ? NRF_GPIOTE_INITIAL_VALUE_LOW : NRF_GPIOTE_INITIAL_VALUE_HIGH,
};
ret = nrfx_gpiote_output_configure(&ip5306_keepalive_gpiote, cfg->keepalive_psel,
&output_config, &task_config);
if (ret != 0) {
goto err_free_channel;
}
nrfx_gpiote_out_task_enable(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
nrfx_gpiote_out_task_force(&ip5306_keepalive_gpiote, cfg->keepalive_psel, active_high ? 0U : 1U);
/*
* RTC2 初始化前先检查是否已被外部占用:
* 若占用则直接返回 -EBUSY让上层走软件保活回退。
*/
if (nrfx_rtc_init_check(&ip5306_keepalive_rtc)) {
ret = -EBUSY;
goto err_disable_task;
}
const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
ret = nrfx_rtc_init(&ip5306_keepalive_rtc, &rtc_cfg, ip5306_keepalive_rtc_handler);
if ((ret != 0) && (ret != -EALREADY)) {
goto err_disable_task;
}
nrfx_rtc_disable(&ip5306_keepalive_rtc);
nrfx_rtc_counter_clear(&ip5306_keepalive_rtc);
/*
* RTC 角色:时间基准,按 compare 点产生事件。
* - CC0 到点 -> 产生 COMPARE0 event脉冲上升沿触发点
* - CC1 到点 -> 产生 COMPARE1 event脉冲下降沿触发点
* - CC2 到点 -> 触发周期边界,配合 SHORT 实现自动循环
*/
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_HIGH, period_ticks - high_ticks - 10U, false);
if (ret != 0) {
goto err_disable_task;
}
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_LOW, period_ticks - 10U, false);
if (ret != 0) {
goto err_disable_task;
}
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_PERIOD, period_ticks, false);
if (ret != 0) {
goto err_disable_task;
}
const uint32_t set_task = active_high ?
nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) :
nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
const uint32_t clr_task = active_high ?
nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) :
nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
const uint32_t high_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_0);
const uint32_t low_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_1);
const uint32_t period_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_2);
const uint32_t clear_task = nrfx_rtc_task_address_get(&ip5306_keepalive_rtc, NRF_RTC_TASK_CLEAR);
/*
* GPPI/PPI 角色:事件路由器。
* 将 RTC compare event 直接连接到 GPIOTE task不经过线程/ISR。
*/
ret = nrfx_gppi_conn_alloc(high_event, set_task, &data->keepalive_set_handle);
if (ret != 0) {
goto err_disable_task;
}
ret = nrfx_gppi_conn_alloc(low_event, clr_task, &data->keepalive_clr_handle);
if (ret != 0) {
nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle);
goto err_disable_task;
}
/*
* 某些 SoC/SDK 组合下 RTC SHORT 宏不可用,因此这里用第三条 GPPI 链路实现“周期自动清零”。
* 这样仍保持纯硬件闭环COMPARE2 event -> RTC CLEAR task不需要 CPU/ISR 参与。
*/
ret = nrfx_gppi_conn_alloc(period_event, clear_task, &data->keepalive_period_handle);
if (ret != 0) {
nrfx_gppi_conn_free(low_event, clr_task, data->keepalive_clr_handle);
nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle);
goto err_disable_task;
}
/*
* GPIOTE 角色GPIO 执行器。
* 收到 task 后立即对目标 pin 执行 SET/CLR输出保活脉冲波形。
*/
nrfx_gppi_conn_enable(data->keepalive_set_handle);
nrfx_gppi_conn_enable(data->keepalive_clr_handle);
nrfx_gppi_conn_enable(data->keepalive_period_handle);
nrfx_rtc_enable(&ip5306_keepalive_rtc);
ip5306_keepalive_hw_claimed = true;
data->backend = IP5306_KEEPALIVE_BACKEND_HW_NRF;
data->dev = dev;
return 0;
err_disable_task:
nrfx_gpiote_out_task_disable(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
err_free_channel:
(void)nrfx_gpiote_channel_free(&ip5306_keepalive_gpiote, data->keepalive_gpiote_channel);
return ret;
}

View File

@@ -0,0 +1,54 @@
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include "ip5306_priv.h"
LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF);
void ip5306_keepalive_sw_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct ip5306_data *data = CONTAINER_OF(dwork, struct ip5306_data, keepalive_work);
const struct ip5306_config *cfg = data->dev->config;
int ret;
if ((data->backend != IP5306_KEEPALIVE_BACKEND_SW) || (data->keepalive_interval_ms == 0U))
{
return;
}
if (!data->keepalive_high)
{
ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_ACTIVE);
if (ret != 0)
{
LOG_ERR("SW keepalive set high failed: %d", ret);
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
return;
}
data->keepalive_high = true;
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_pulse_ms));
}
else
{
ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE);
if (ret != 0)
{
LOG_ERR("SW keepalive set low failed: %d", ret);
}
data->keepalive_high = false;
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
}
}
int ip5306_keepalive_sw_start(const struct device *dev)
{
struct ip5306_data *data = dev->data;
data->backend = IP5306_KEEPALIVE_BACKEND_SW;
data->dev = dev;
k_work_init_delayable(&data->keepalive_work, ip5306_keepalive_sw_work_handler);
return (int)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
}

View File

@@ -0,0 +1,58 @@
#ifndef IP5306_KEEPALIVE_PRIV_H_
#define IP5306_KEEPALIVE_PRIV_H_
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
#include <helpers/nrfx_gppi.h>
#endif
#define IP5306_KEEPALIVE_DEFAULT_PULSE_MS 100U
#define IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS 20000U
enum ip5306_keepalive_backend {
IP5306_KEEPALIVE_BACKEND_NONE,
IP5306_KEEPALIVE_BACKEND_SW,
IP5306_KEEPALIVE_BACKEND_HW_NRF,
};
struct ip5306_config {
struct i2c_dt_spec i2c;
struct gpio_dt_spec keepalive_gpio;
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
uint32_t keepalive_psel;
#endif
uint32_t keepalive_pulse_ms;
uint32_t keepalive_interval_ms;
bool has_keepalive_gpio;
bool keepalive_offload;
};
struct ip5306_data {
struct k_work_delayable keepalive_work;
bool keepalive_high;
uint32_t keepalive_pulse_ms;
uint32_t keepalive_interval_ms;
enum ip5306_keepalive_backend backend;
const struct device *dev;
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
uint8_t keepalive_gpiote_channel;
nrfx_gppi_handle_t keepalive_set_handle;
nrfx_gppi_handle_t keepalive_clr_handle;
nrfx_gppi_handle_t keepalive_period_handle;
#endif
};
int ip5306_keepalive_sw_start(const struct device *dev);
int ip5306_keepalive_hw_nrf_start(const struct device *dev);
#endif /* IP5306_KEEPALIVE_PRIV_H_ */

View File

@@ -0,0 +1,33 @@
title: Injoinic IP5306 PMIC
description: |
Injoinic IP5306 power management IC accessed over I2C.
This binding exposes two status bits and an optional KEY pin keepalive
pulse output.
compatible: "injoinic,ip5306"
include: i2c-device.yaml
properties:
keepalive-gpios:
type: phandle-array
description: |
GPIO connected to KEY pin. The driver sends periodic pulses on this pin
to keep boost output alive.
keepalive-pulse-ms:
type: int
default: 100
description: Pulse width in milliseconds for each keepalive pulse.
keepalive-interval-ms:
type: int
default: 20000
description: Interval in milliseconds between keepalive pulses.
keepalive-offload:
type: boolean
description: |
Prefer hardware keepalive offload backend when available on this SoC.
If not set, driver always uses software keepalive backend.

View File

@@ -0,0 +1 @@
injoinic Injoinic

View File

@@ -0,0 +1,48 @@
#ifndef ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_
#define ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_
#include <stdbool.h>
#include <errno.h>
#include <zephyr/device.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef int (*ip5306_get_flag_t)(const struct device *dev, bool *flag);
struct ip5306_driver_api {
ip5306_get_flag_t is_charging;
ip5306_get_flag_t is_charge_full;
};
static inline int ip5306_is_charging(const struct device *dev, bool *charging)
{
const struct ip5306_driver_api *api =
(const struct ip5306_driver_api *)dev->api;
if (api == NULL || api->is_charging == NULL) {
return -ENOSYS;
}
return api->is_charging(dev, charging);
}
static inline int ip5306_is_charge_full(const struct device *dev, bool *full)
{
const struct ip5306_driver_api *api =
(const struct ip5306_driver_api *)dev->api;
if (api == NULL || api->is_charge_full == NULL) {
return -ENOSYS;
}
return api->is_charge_full(dev, full);
}
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_ */

View File

@@ -0,0 +1,5 @@
build:
cmake: .
kconfig: Kconfig
settings:
dts_root: .