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:
@@ -5,6 +5,7 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
|||||||
project(blinky)
|
project(blinky)
|
||||||
|
|
||||||
zephyr_include_directories(inc)
|
zephyr_include_directories(inc)
|
||||||
|
add_subdirectory(drivers)
|
||||||
|
|
||||||
target_sources(app PRIVATE
|
target_sources(app PRIVATE
|
||||||
src/main.c
|
src/main.c
|
||||||
|
|||||||
7
Kconfig
Normal file
7
Kconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mainmenu "blinky"
|
||||||
|
|
||||||
|
source "Kconfig.zephyr"
|
||||||
|
|
||||||
|
menu "Application Drivers"
|
||||||
|
rsource "drivers/Kconfig"
|
||||||
|
endmenu
|
||||||
@@ -1,2 +1,16 @@
|
|||||||
&pinctrl {
|
&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;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
&gpio0 {
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
|
|||||||
1
drivers/CMakeLists.txt
Normal file
1
drivers/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(pmic)
|
||||||
1
drivers/Kconfig
Normal file
1
drivers/Kconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rsource "pmic/Kconfig"
|
||||||
1
drivers/pmic/CMakeLists.txt
Normal file
1
drivers/pmic/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(ip5306)
|
||||||
1
drivers/pmic/Kconfig
Normal file
1
drivers/pmic/Kconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rsource "ip5306/Kconfig"
|
||||||
1
drivers/pmic/ip5306/CMakeLists.txt
Normal file
1
drivers/pmic/ip5306/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
target_sources_ifdef(CONFIG_IP5306 app PRIVATE ip5306.c)
|
||||||
11
drivers/pmic/ip5306/Kconfig
Normal file
11
drivers/pmic/ip5306/Kconfig
Normal 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
|
||||||
196
drivers/pmic/ip5306/ip5306.c
Normal file
196
drivers/pmic/ip5306/ip5306.c
Normal 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, ®_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)
|
||||||
21
dts/bindings/pmic/injoinic,ip5306.yaml
Normal file
21
dts/bindings/pmic/injoinic,ip5306.yaml
Normal 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.
|
||||||
1
dts/bindings/vendor-prefixes.txt
Normal file
1
dts/bindings/vendor-prefixes.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
injoinic Injoinic
|
||||||
44
inc/drivers/pmic/ip5306.h
Normal file
44
inc/drivers/pmic/ip5306.h
Normal 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_ */
|
||||||
1
prj.conf
1
prj.conf
@@ -2,6 +2,7 @@ CONFIG_CAF=y
|
|||||||
CONFIG_CAF_BUTTONS=y
|
CONFIG_CAF_BUTTONS=y
|
||||||
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
||||||
CONFIG_GPIO=y
|
CONFIG_GPIO=y
|
||||||
|
CONFIG_I2C=y
|
||||||
CONFIG_REBOOT=y
|
CONFIG_REBOOT=y
|
||||||
CONFIG_SENSOR=y
|
CONFIG_SENSOR=y
|
||||||
CONFIG_ADC=y
|
CONFIG_ADC=y
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
|
|
||||||
#define MODULE battery_module
|
#define MODULE battery_module
|
||||||
#include <caf/events/module_state_event.h>
|
#include <caf/events/module_state_event.h>
|
||||||
|
#include <caf/events/power_manager_event.h>
|
||||||
#include <caf/events/power_event.h>
|
#include <caf/events/power_event.h>
|
||||||
|
|
||||||
|
#include <drivers/pmic/ip5306.h>
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
#include <zephyr/devicetree.h>
|
#include <zephyr/devicetree.h>
|
||||||
#include <zephyr/drivers/sensor.h>
|
#include <zephyr/drivers/sensor.h>
|
||||||
@@ -17,12 +19,16 @@
|
|||||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
#define VBATT_NODE DT_PATH(vbatt)
|
#define VBATT_NODE DT_PATH(vbatt)
|
||||||
|
#define IP5306_NODE DT_NODELABEL(ip5306)
|
||||||
#define BATTERY_SAMPLE_INTERVAL K_SECONDS(1)
|
#define BATTERY_SAMPLE_INTERVAL K_SECONDS(1)
|
||||||
|
|
||||||
BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay),
|
BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay),
|
||||||
"Missing /vbatt voltage-divider node in devicetree");
|
"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 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 struct k_work_delayable battery_sample_work;
|
||||||
static bool initialized;
|
static bool initialized;
|
||||||
static bool running;
|
static bool running;
|
||||||
@@ -48,7 +54,9 @@ static int measurement_enable(bool enable)
|
|||||||
|
|
||||||
static void battery_sample_fn(struct k_work *work)
|
static void battery_sample_fn(struct k_work *work)
|
||||||
{
|
{
|
||||||
|
struct ip5306_status pmic_status;
|
||||||
struct sensor_value voltage;
|
struct sensor_value voltage;
|
||||||
|
int voltage_mv;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
ARG_UNUSED(work);
|
ARG_UNUSED(work);
|
||||||
@@ -69,7 +77,15 @@ static void battery_sample_fn(struct k_work *work)
|
|||||||
goto reschedule;
|
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:
|
reschedule:
|
||||||
if (running) {
|
if (running) {
|
||||||
@@ -84,7 +100,19 @@ static int module_init(void)
|
|||||||
return -ENODEV;
|
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);
|
k_work_init_delayable(&battery_sample_work, battery_sample_fn);
|
||||||
|
power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user