#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) #define IP5306_KEEPALIVE_RTC_FREQ_HZ 32768U #define IP5306_KEEPALIVE_RTC_CHANNEL_END 0U #define IP5306_KEEPALIVE_RTC_CHANNEL_START 1U struct ip5306_config { struct i2c_dt_spec bus; nrfx_gpiote_t *gpiote; struct gpio_dt_spec wakeup_gpio; uint8_t wakeup_pin; uint32_t keepalive_interval_ms; uint32_t keepalive_pulse_width_ms; bool keepalive_hardware; }; struct ip5306_data { const struct device *dev; struct k_work_delayable keepalive_work; uint8_t gpiote_channel; nrfx_gppi_handle_t ppi_start; nrfx_gppi_handle_t ppi_end; nrfx_gppi_handle_t ppi_clear; bool started; bool pulse_active; bool hardware_ready; }; static const nrfx_rtc_t keepalive_rtc = NRFX_RTC_INSTANCE(2); 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 uint32_t keepalive_ms_to_rtc_ticks(uint32_t time_ms) { return (uint32_t)(((uint64_t)time_ms * IP5306_KEEPALIVE_RTC_FREQ_HZ) / 1000U); } 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 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; } 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; } ARG_UNUSED(reg_val); data->started = true; data->pulse_active = false; if (config->keepalive_hardware) { return 0; } 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; data->hardware_ready = false; if (config->keepalive_hardware) { return hardware_keepalive_init(dev); } return software_keepalive_init(dev); } 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), \ .gpiote = &GPIOTE_NRFX_INST_BY_NODE( \ NRF_DT_GPIOTE_NODE(DT_DRV_INST(inst), wakeup_gpios)), \ .wakeup_gpio = GPIO_DT_SPEC_INST_GET(inst, wakeup_gpios), \ .wakeup_pin = NRF_DT_GPIOS_TO_PSEL(DT_DRV_INST(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), \ .keepalive_hardware = \ DT_INST_NODE_HAS_PROP(inst, keepalive_hardware), \ }; \ \ 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)