feat(drivers): 添加IP5306 PMIC驱动支持

- 添加IP5306 PMIC驱动实现,包括I2C通信和GPIO唤醒功能
- 实现电源管理芯片的状态读取接口(充电状态、满电状态)
- 集成Wakeup保持脉冲功能,支持可配置的脉冲宽度和间隔时间
- 添加设备树绑定文件和Kconfig配置选项

refactor(blinky): 集成IP5306电源管理芯片到电池模块

- 在电池模块中集成IP5306 PMIC状态监控功能
- 修改日志输出格式,显示电池电压及充电/满电状态
- 增加设备初始化检查和错误处理机制
- 配置电源管理限制级别为暂停模式

build: 配置CMakeLists.txt以包含驱动子目录

- 更新主CMakeLists.txt文件添加drivers子目录
- 配置驱动程序的构建层次结构(pmic -> ip5306)
- 设置条件编译目标源文件

docs: 添加设备树和板级配置支持

- 添加mini_keyboard板的I2C引脚控制配置
- 配置IP5306设备节点和相关GPIO引脚定义
- 启用I2C配置选项以支持PMIC通信
This commit is contained in:
2026-04-08 11:01:01 +08:00
parent 42aee4c511
commit cfcefbf28a
16 changed files with 347 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky)
zephyr_include_directories(inc)
add_subdirectory(drivers)
target_sources(app PRIVATE
src/main.c

7
Kconfig Normal file
View File

@@ -0,0 +1,7 @@
mainmenu "blinky"
source "Kconfig.zephyr"
menu "Application Drivers"
rsource "drivers/Kconfig"
endmenu

View File

@@ -1,2 +1,16 @@
&pinctrl {
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
<NRF_PSEL(TWIM_SCL, 0, 24)>;
};
};
i2c0_sleep: i2c0_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
<NRF_PSEL(TWIM_SCL, 0, 24)>;
low-power-enable;
};
};
};

View File

@@ -82,6 +82,23 @@
};
};
&i2c0 {
status = "okay";
pinctrl-0 = <&i2c0_default>;
pinctrl-1 = <&i2c0_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <400000>;
ip5306: pmic@75 {
compatible = "injoinic,ip5306";
reg = <0x75>;
wakeup-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
keepalive-interval-ms = <8000>;
keepalive-pulse-width-ms = <500>;
status = "okay";
};
};
&gpio0 {
status = "okay";
};

1
drivers/CMakeLists.txt Normal file
View File

@@ -0,0 +1 @@
add_subdirectory(pmic)

1
drivers/Kconfig Normal file
View File

@@ -0,0 +1 @@
rsource "pmic/Kconfig"

View File

@@ -0,0 +1 @@
add_subdirectory(ip5306)

1
drivers/pmic/Kconfig Normal file
View File

@@ -0,0 +1 @@
rsource "ip5306/Kconfig"

View File

@@ -0,0 +1 @@
target_sources_ifdef(CONFIG_IP5306 app PRIVATE ip5306.c)

View File

@@ -0,0 +1,11 @@
menu "PMIC Drivers"
config IP5306
bool "IP5306 PMIC driver"
default y
depends on I2C
depends on DT_HAS_INJOINIC_IP5306_ENABLED
help
Enable the out-of-tree IP5306 PMIC driver.
endmenu

View File

@@ -0,0 +1,196 @@
#include <errno.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include <drivers/pmic/ip5306.h>
#define DT_DRV_COMPAT injoinic_ip5306
LOG_MODULE_REGISTER(ip5306, LOG_LEVEL_INF);
#define IP5306_REG_READ0 0x70
#define IP5306_REG_READ1 0x71
#define IP5306_CHARGING_BIT BIT(3)
#define IP5306_FULL_BIT BIT(3)
struct ip5306_config {
struct i2c_dt_spec bus;
struct gpio_dt_spec wakeup_gpio;
uint32_t keepalive_interval_ms;
uint32_t keepalive_pulse_width_ms;
};
struct ip5306_data {
const struct device *dev;
struct k_work_delayable keepalive_work;
bool started;
bool pulse_active;
};
static uint32_t keepalive_low_delay_ms(const struct ip5306_config *config)
{
if (config->keepalive_interval_ms > config->keepalive_pulse_width_ms) {
return config->keepalive_interval_ms -
config->keepalive_pulse_width_ms;
}
return 0;
}
static void keepalive_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 device *dev = data->dev;
const struct ip5306_config *config = dev->config;
int err;
if (!data->started) {
return;
}
if (!data->pulse_active) {
err = gpio_pin_set_dt(&config->wakeup_gpio, 1);
if (err) {
LOG_ERR("Failed to assert wakeup pulse (%d)", err);
}
data->pulse_active = true;
k_work_reschedule(&data->keepalive_work,
K_MSEC(config->keepalive_pulse_width_ms));
return;
}
err = gpio_pin_set_dt(&config->wakeup_gpio, 0);
if (err) {
LOG_ERR("Failed to deassert wakeup pulse (%d)", err);
}
data->pulse_active = false;
k_work_reschedule(&data->keepalive_work,
K_MSEC(keepalive_low_delay_ms(config)));
}
static int ip5306_init_api(const struct device *dev)
{
const struct ip5306_config *config = dev->config;
struct ip5306_data *data = dev->data;
uint8_t reg_val;
int err;
if (data->started) {
return 0;
}
err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ0, &reg_val);
if (err) {
LOG_ERR("IP5306 probe failed (%d)", err);
return err;
}
data->started = true;
data->pulse_active = false;
k_work_reschedule(&data->keepalive_work,
K_MSEC(keepalive_low_delay_ms(config)));
return 0;
}
static int ip5306_get_status_api(const struct device *dev,
struct ip5306_status *status)
{
const struct ip5306_config *config = dev->config;
uint8_t read0;
uint8_t read1;
int err;
if (status == NULL) {
return -EINVAL;
}
err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ0, &read0);
if (err) {
return err;
}
err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ1, &read1);
if (err) {
return err;
}
status->charging = (read0 & IP5306_CHARGING_BIT) != 0U;
status->full = (read1 & IP5306_FULL_BIT) != 0U;
return 0;
}
static int ip5306_dev_init(const struct device *dev)
{
const struct ip5306_config *config = dev->config;
struct ip5306_data *data = dev->data;
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("I2C bus not ready");
return -ENODEV;
}
if (!gpio_is_ready_dt(&config->wakeup_gpio)) {
LOG_ERR("Wakeup GPIO not ready");
return -ENODEV;
}
if (config->keepalive_pulse_width_ms == 0U) {
LOG_ERR("Invalid keepalive pulse width");
return -EINVAL;
}
if (config->keepalive_interval_ms == 0U) {
LOG_ERR("Invalid keepalive interval");
return -EINVAL;
}
if (config->keepalive_pulse_width_ms > config->keepalive_interval_ms) {
LOG_ERR("Pulse width cannot exceed interval");
return -EINVAL;
}
data->dev = dev;
data->started = false;
data->pulse_active = false;
k_work_init_delayable(&data->keepalive_work, keepalive_work_handler);
return gpio_pin_configure_dt(&config->wakeup_gpio, GPIO_OUTPUT_INACTIVE);
}
static const struct ip5306_driver_api ip5306_driver_api = {
.init = ip5306_init_api,
.get_status = ip5306_get_status_api,
};
#define IP5306_DEFINE(inst) \
static struct ip5306_data ip5306_data_##inst; \
\
static const struct ip5306_config ip5306_config_##inst = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.wakeup_gpio = GPIO_DT_SPEC_INST_GET(inst, wakeup_gpios), \
.keepalive_interval_ms = \
DT_INST_PROP(inst, keepalive_interval_ms), \
.keepalive_pulse_width_ms = \
DT_INST_PROP(inst, keepalive_pulse_width_ms), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, ip5306_dev_init, NULL, \
&ip5306_data_##inst, &ip5306_config_##inst, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&ip5306_driver_api)
DT_INST_FOREACH_STATUS_OKAY(IP5306_DEFINE)

View File

@@ -0,0 +1,21 @@
description: Injoinic IP5306 PMIC with software wakeup keepalive support
compatible: "injoinic,ip5306"
include: i2c-device.yaml
properties:
wakeup-gpios:
type: phandle-array
required: true
description: GPIO used to generate the wakeup keepalive pulse.
keepalive-interval-ms:
type: int
required: true
description: Period between two keepalive pulses, measured from pulse start.
keepalive-pulse-width-ms:
type: int
required: true
description: Active-high pulse width for the keepalive wakeup GPIO.

View File

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

44
inc/drivers/pmic/ip5306.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef BLINKY_DRIVERS_PMIC_IP5306_H_
#define BLINKY_DRIVERS_PMIC_IP5306_H_
#include <errno.h>
#include <stdbool.h>
#include <zephyr/device.h>
struct ip5306_status {
bool charging;
bool full;
};
struct ip5306_driver_api {
int (*init)(const struct device *dev);
int (*get_status)(const struct device *dev, struct ip5306_status *status);
};
static inline int ip5306_init(const struct device *dev)
{
const struct ip5306_driver_api *api =
(const struct ip5306_driver_api *)dev->api;
if ((api == NULL) || (api->init == NULL)) {
return -ENOSYS;
}
return api->init(dev);
}
static inline int ip5306_get_status(const struct device *dev,
struct ip5306_status *status)
{
const struct ip5306_driver_api *api =
(const struct ip5306_driver_api *)dev->api;
if ((api == NULL) || (api->get_status == NULL)) {
return -ENOSYS;
}
return api->get_status(dev, status);
}
#endif /* BLINKY_DRIVERS_PMIC_IP5306_H_ */

View File

@@ -2,6 +2,7 @@ CONFIG_CAF=y
CONFIG_CAF_BUTTONS=y
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_REBOOT=y
CONFIG_SENSOR=y
CONFIG_ADC=y

View File

@@ -5,8 +5,10 @@
#define MODULE battery_module
#include <caf/events/module_state_event.h>
#include <caf/events/power_manager_event.h>
#include <caf/events/power_event.h>
#include <drivers/pmic/ip5306.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sensor.h>
@@ -17,12 +19,16 @@
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define VBATT_NODE DT_PATH(vbatt)
#define IP5306_NODE DT_NODELABEL(ip5306)
#define BATTERY_SAMPLE_INTERVAL K_SECONDS(1)
BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay),
"Missing /vbatt voltage-divider node in devicetree");
BUILD_ASSERT(DT_NODE_HAS_STATUS(IP5306_NODE, okay),
"Missing ip5306 node in devicetree");
static const struct device *const vbatt_dev = DEVICE_DT_GET(VBATT_NODE);
static const struct device *const ip5306_dev = DEVICE_DT_GET(IP5306_NODE);
static struct k_work_delayable battery_sample_work;
static bool initialized;
static bool running;
@@ -48,7 +54,9 @@ static int measurement_enable(bool enable)
static void battery_sample_fn(struct k_work *work)
{
struct ip5306_status pmic_status;
struct sensor_value voltage;
int voltage_mv;
int err;
ARG_UNUSED(work);
@@ -69,7 +77,15 @@ static void battery_sample_fn(struct k_work *work)
goto reschedule;
}
LOG_INF("Battery voltage: %d mV", sensor_value_to_mv(&voltage));
err = ip5306_get_status(ip5306_dev, &pmic_status);
if (err) {
LOG_WRN("IP5306 status read failed (%d)", err);
goto reschedule;
}
voltage_mv = sensor_value_to_mv(&voltage);
LOG_INF("Battery: %d mV, charging=%d, full=%d",
voltage_mv, pmic_status.charging, pmic_status.full);
reschedule:
if (running) {
@@ -84,7 +100,19 @@ static int module_init(void)
return -ENODEV;
}
if (!device_is_ready(ip5306_dev)) {
LOG_ERR("ip5306 device not ready");
return -ENODEV;
}
int err = ip5306_init(ip5306_dev);
if (err) {
LOG_ERR("ip5306 init failed (%d)", err);
return err;
}
k_work_init_delayable(&battery_sample_work, battery_sample_fn);
power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED);
return 0;
}