2026-04-08 11:01:01 +08:00
|
|
|
#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>
|
2026-04-08 13:42:04 +08:00
|
|
|
#include <soc_nrf_common.h>
|
2026-04-08 11:01:01 +08:00
|
|
|
#include <zephyr/sys/util.h>
|
|
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
#include <helpers/nrfx_gppi.h>
|
2026-04-08 11:01:01 +08:00
|
|
|
#include <drivers/pmic/ip5306.h>
|
2026-04-08 13:42:04 +08:00
|
|
|
#include <gpiote_nrfx.h>
|
|
|
|
|
#include <nrfx_gpiote.h>
|
|
|
|
|
#include <nrfx_rtc.h>
|
2026-04-08 11:01:01 +08:00
|
|
|
|
|
|
|
|
#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)
|
2026-04-08 13:42:04 +08:00
|
|
|
#define IP5306_KEEPALIVE_RTC_FREQ_HZ 32768U
|
|
|
|
|
#define IP5306_KEEPALIVE_RTC_CHANNEL_END 0U
|
|
|
|
|
#define IP5306_KEEPALIVE_RTC_CHANNEL_START 1U
|
2026-04-08 11:01:01 +08:00
|
|
|
|
|
|
|
|
struct ip5306_config {
|
|
|
|
|
struct i2c_dt_spec bus;
|
2026-04-08 13:42:04 +08:00
|
|
|
nrfx_gpiote_t *gpiote;
|
2026-04-08 11:01:01 +08:00
|
|
|
struct gpio_dt_spec wakeup_gpio;
|
2026-04-08 13:42:04 +08:00
|
|
|
uint8_t wakeup_pin;
|
2026-04-08 11:01:01 +08:00
|
|
|
uint32_t keepalive_interval_ms;
|
|
|
|
|
uint32_t keepalive_pulse_width_ms;
|
2026-04-08 13:42:04 +08:00
|
|
|
bool keepalive_hardware;
|
2026-04-08 11:01:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ip5306_data {
|
|
|
|
|
const struct device *dev;
|
|
|
|
|
struct k_work_delayable keepalive_work;
|
2026-04-08 13:42:04 +08:00
|
|
|
uint8_t gpiote_channel;
|
|
|
|
|
nrfx_gppi_handle_t ppi_start;
|
|
|
|
|
nrfx_gppi_handle_t ppi_end;
|
|
|
|
|
nrfx_gppi_handle_t ppi_clear;
|
2026-04-08 11:01:01 +08:00
|
|
|
bool started;
|
|
|
|
|
bool pulse_active;
|
2026-04-08 13:42:04 +08:00
|
|
|
bool hardware_ready;
|
2026-04-08 11:01:01 +08:00
|
|
|
};
|
|
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
static const nrfx_rtc_t keepalive_rtc = NRFX_RTC_INSTANCE(2);
|
|
|
|
|
|
2026-04-08 11:01:01 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
static uint32_t keepalive_ms_to_rtc_ticks(uint32_t time_ms)
|
|
|
|
|
{
|
|
|
|
|
return (uint32_t)(((uint64_t)time_ms * IP5306_KEEPALIVE_RTC_FREQ_HZ) / 1000U);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 11:01:01 +08:00
|
|
|
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)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
static void ip5306_keepalive_rtc_handler(nrfx_rtc_int_type_t int_type)
|
|
|
|
|
{
|
|
|
|
|
ARG_UNUSED(int_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hardware_keepalive_init(const struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
const struct ip5306_config *config = dev->config;
|
|
|
|
|
struct ip5306_data *data = dev->data;
|
|
|
|
|
nrfx_gpiote_output_config_t output_config = NRFX_GPIOTE_DEFAULT_OUTPUT_CONFIG;
|
|
|
|
|
nrfx_gpiote_task_config_t task_config = {
|
|
|
|
|
.task_ch = 0,
|
|
|
|
|
.polarity = NRF_GPIOTE_POLARITY_TOGGLE,
|
|
|
|
|
.init_val = (config->wakeup_gpio.dt_flags & GPIO_ACTIVE_LOW) ?
|
|
|
|
|
NRF_GPIOTE_INITIAL_VALUE_HIGH :
|
|
|
|
|
NRF_GPIOTE_INITIAL_VALUE_LOW,
|
|
|
|
|
};
|
|
|
|
|
nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
|
|
|
|
|
uint32_t period_ticks = keepalive_ms_to_rtc_ticks(config->keepalive_interval_ms);
|
|
|
|
|
uint32_t pulse_ticks = keepalive_ms_to_rtc_ticks(config->keepalive_pulse_width_ms);
|
|
|
|
|
uint32_t start_ticks = period_ticks - pulse_ticks;
|
|
|
|
|
uint32_t eep_start;
|
|
|
|
|
uint32_t eep_end;
|
|
|
|
|
uint32_t tep_toggle;
|
|
|
|
|
uint32_t tep_clear;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (data->hardware_ready) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!nrfx_gpiote_init_check(config->gpiote)) {
|
|
|
|
|
LOG_ERR("GPIOTE shared instance is not initialized");
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = nrfx_gpiote_channel_alloc(config->gpiote, &data->gpiote_channel);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("GPIOTE channel alloc failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task_config.task_ch = data->gpiote_channel;
|
|
|
|
|
|
|
|
|
|
err = nrfx_gpiote_output_configure(config->gpiote,
|
|
|
|
|
config->wakeup_pin,
|
|
|
|
|
&output_config,
|
|
|
|
|
&task_config);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("GPIOTE output configure failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nrfx_gpiote_out_task_enable(config->gpiote, config->wakeup_pin);
|
|
|
|
|
|
|
|
|
|
err = nrfx_rtc_init(&keepalive_rtc, &rtc_cfg, ip5306_keepalive_rtc_handler);
|
|
|
|
|
if ((err != 0) && (err != -EALREADY)) {
|
|
|
|
|
LOG_ERR("RTC2 init failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = nrfx_rtc_cc_set(&keepalive_rtc,
|
|
|
|
|
IP5306_KEEPALIVE_RTC_CHANNEL_END,
|
|
|
|
|
period_ticks,
|
|
|
|
|
false);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("RTC2 CC end set failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = nrfx_rtc_cc_set(&keepalive_rtc,
|
|
|
|
|
IP5306_KEEPALIVE_RTC_CHANNEL_START,
|
|
|
|
|
start_ticks,
|
|
|
|
|
false);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("RTC2 CC start set failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eep_start = nrfx_rtc_event_address_get(&keepalive_rtc,
|
|
|
|
|
NRF_RTC_EVENT_COMPARE_1);
|
|
|
|
|
eep_end = nrfx_rtc_event_address_get(&keepalive_rtc,
|
|
|
|
|
NRF_RTC_EVENT_COMPARE_0);
|
|
|
|
|
tep_toggle = nrfx_gpiote_out_task_address_get(config->gpiote,
|
|
|
|
|
config->wakeup_pin);
|
|
|
|
|
tep_clear = nrfx_rtc_task_address_get(&keepalive_rtc, NRF_RTC_TASK_CLEAR);
|
|
|
|
|
|
|
|
|
|
err = nrfx_gppi_conn_alloc(eep_start, tep_toggle, &data->ppi_start);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("GPPI start alloc failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = nrfx_gppi_conn_alloc(eep_end, tep_toggle, &data->ppi_end);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("GPPI end alloc failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = nrfx_gppi_conn_alloc(eep_end, tep_clear, &data->ppi_clear);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("GPPI clear alloc failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nrfx_gppi_conn_enable(data->ppi_start);
|
|
|
|
|
nrfx_gppi_conn_enable(data->ppi_end);
|
|
|
|
|
nrfx_gppi_conn_enable(data->ppi_clear);
|
|
|
|
|
|
|
|
|
|
nrfx_rtc_counter_clear(&keepalive_rtc);
|
|
|
|
|
nrfx_rtc_enable(&keepalive_rtc);
|
|
|
|
|
|
|
|
|
|
data->hardware_ready = true;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int software_keepalive_init(const struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
struct ip5306_data *data = dev->data;
|
|
|
|
|
|
|
|
|
|
k_work_init_delayable(&data->keepalive_work, keepalive_work_handler);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 11:01:01 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2026-04-08 13:42:04 +08:00
|
|
|
ARG_UNUSED(reg_val);
|
2026-04-08 11:01:01 +08:00
|
|
|
|
|
|
|
|
data->started = true;
|
|
|
|
|
data->pulse_active = false;
|
2026-04-08 13:42:04 +08:00
|
|
|
|
|
|
|
|
if (config->keepalive_hardware) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 11:01:01 +08:00
|
|
|
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;
|
2026-04-08 13:42:04 +08:00
|
|
|
data->hardware_ready = false;
|
2026-04-08 11:01:01 +08:00
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
if (config->keepalive_hardware) {
|
|
|
|
|
return hardware_keepalive_init(dev);
|
|
|
|
|
}
|
2026-04-08 11:01:01 +08:00
|
|
|
|
2026-04-08 13:42:04 +08:00
|
|
|
return software_keepalive_init(dev);
|
2026-04-08 11:01:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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), \
|
2026-04-08 13:42:04 +08:00
|
|
|
.gpiote = &GPIOTE_NRFX_INST_BY_NODE( \
|
|
|
|
|
NRF_DT_GPIOTE_NODE(DT_DRV_INST(inst), wakeup_gpios)), \
|
2026-04-08 11:01:01 +08:00
|
|
|
.wakeup_gpio = GPIO_DT_SPEC_INST_GET(inst, wakeup_gpios), \
|
2026-04-08 13:42:04 +08:00
|
|
|
.wakeup_pin = NRF_DT_GPIOS_TO_PSEL(DT_DRV_INST(inst), wakeup_gpios), \
|
2026-04-08 11:01:01 +08:00
|
|
|
.keepalive_interval_ms = \
|
|
|
|
|
DT_INST_PROP(inst, keepalive_interval_ms), \
|
|
|
|
|
.keepalive_pulse_width_ms = \
|
|
|
|
|
DT_INST_PROP(inst, keepalive_pulse_width_ms), \
|
2026-04-08 13:42:04 +08:00
|
|
|
.keepalive_hardware = \
|
|
|
|
|
DT_INST_NODE_HAS_PROP(inst, keepalive_hardware), \
|
2026-04-08 11:01:01 +08:00
|
|
|
}; \
|
|
|
|
|
\
|
|
|
|
|
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)
|