Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sensors): Battery charging status detection #1923

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/include/drivers/sensor/battery/battery_charging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_BATTERY_BATTERY_CHARGING_H_
#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_BATTERY_BATTERY_CHARGING_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <zephyr/drivers/sensor.h>

enum sensor_channel_bvd {
/** Charging state, bool **/
SENSOR_CHAN_CHARGING = SENSOR_CHAN_PRIV_START,
};

#endif
1 change: 1 addition & 0 deletions app/include/zmk/battery.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
#pragma once

uint8_t zmk_battery_state_of_charge(void);
bool zmk_battery_charging(void);
2 changes: 2 additions & 0 deletions app/include/zmk/events/battery_state_changed.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
struct zmk_battery_state_changed {
// TODO: Other battery channels
uint8_t state_of_charge;
bool charging;
};

ZMK_EVENT_DECLARE(zmk_battery_state_changed);

struct zmk_peripheral_battery_state_changed {
uint8_t source;
// TODO: Other battery channels
// Charging state not broadcast over BAS so no need to have it in peripheral event
uint8_t state_of_charge;
};

Expand Down
1 change: 1 addition & 0 deletions app/module/drivers/sensor/battery/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: MIT

zephyr_include_directories(.)
zephyr_include_directories(${CMAKE_SOURCE_DIR}/include)

zephyr_library()

Expand Down
7 changes: 7 additions & 0 deletions app/module/drivers/sensor/battery/battery_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <errno.h>
#include <zephyr/drivers/sensor.h>

#include <drivers/sensor/battery/battery_charging.h>

#include "battery_common.h"

int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
Expand All @@ -22,6 +24,11 @@ int battery_channel_get(const struct battery_value *value, enum sensor_channel c
val_out->val2 = 0;
break;

case SENSOR_CHAN_CHARGING:
val_out->val1 = value->charging;
val_out->val2 = 0;
break;

default:
return -ENOTSUP;
}
Expand Down
1 change: 1 addition & 0 deletions app/module/drivers/sensor/battery/battery_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct battery_value {
uint16_t adc_raw;
uint16_t millivolts;
uint8_t state_of_charge;
bool charging;
};

int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
Expand Down
107 changes: 104 additions & 3 deletions app/module/drivers/sensor/battery/battery_nrf_vddh.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

#include <drivers/sensor/battery/battery_charging.h>
#include "battery_common.h"

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
Expand All @@ -23,16 +25,59 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

static const struct device *adc = DEVICE_DT_GET(DT_NODELABEL(adc));

struct vddh_config {
struct gpio_dt_spec chg;
};

struct vddh_data {
struct adc_channel_cfg acc;
struct adc_sequence as;
struct battery_value value;
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
const struct device *dev;
const struct sensor_trigger *data_ready_trigger;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t data_ready_handler;
struct k_work work;
#endif
};

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
static void set_int(const struct device *dev, const bool en) {
const struct vddh_config *drv_cfg = dev->config;
int ret =
gpio_pin_interrupt_configure_dt(&drv_cfg->chg, en ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE);
if (ret < 0) {
LOG_ERR("can't set interrupt");
}
}

static int vddh_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler) {
struct vddh_data *drv_data = dev->data;

set_int(dev, false);
if (trig->type != SENSOR_TRIG_DATA_READY) {
return -ENOTSUP;
}
drv_data->data_ready_trigger = trig;
drv_data->data_ready_handler = handler;
set_int(dev, true);
return 0;
}

static void vddh_int_cb(const struct device *dev) {
struct vddh_data *drv_data = dev->data;
drv_data->data_ready_handler(dev, drv_data->data_ready_trigger);
LOG_DBG("Setting int on %d", 0);
set_int(dev, true);
}
#endif

static int vddh_sample_fetch(const struct device *dev, enum sensor_channel chan) {
// Make sure selected channel is supported
if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE &&
chan != SENSOR_CHAN_ALL) {
(enum sensor_channel_bvd)chan != SENSOR_CHAN_CHARGING && chan != SENSOR_CHAN_ALL) {
LOG_DBG("Selected channel is not supported: %d.", chan);
return -ENOTSUP;
}
Expand Down Expand Up @@ -61,6 +106,19 @@ static int vddh_sample_fetch(const struct device *dev, enum sensor_channel chan)
LOG_DBG("ADC raw %d ~ %d mV => %d%%", drv_data->value.adc_raw, drv_data->value.millivolts,
drv_data->value.state_of_charge);

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
const struct vddh_config *drv_cfg = dev->config;
int raw = gpio_pin_get_dt(&drv_cfg->chg);
if (raw == -EIO || raw == -EWOULDBLOCK) {
LOG_DBG("Failed to read chg status: %d", raw);
return raw;
} else {
bool charging = raw;
LOG_DBG("Charging state: %d", raw);
drv_data->value.charging = charging;
}
#endif

return rc;
}

Expand All @@ -70,7 +128,22 @@ static int vddh_channel_get(const struct device *dev, enum sensor_channel chan,
return battery_channel_get(&drv_data->value, chan, val);
}

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
static void vddh_work_cb(struct k_work *work) {
struct vddh_data *drv_data = CONTAINER_OF(work, struct vddh_data, work);
vddh_int_cb(drv_data->dev);
}
static void vddh_gpio_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins) {
struct vddh_data *drv_data = CONTAINER_OF(cb, struct vddh_data, gpio_cb);
set_int(drv_data->dev, false);
k_work_submit(&drv_data->work);
}
#endif

static const struct sensor_driver_api vddh_api = {
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
.trigger_set = vddh_trigger_set,
#endif
.sample_fetch = vddh_sample_fetch,
.channel_get = vddh_channel_get,
};
Expand Down Expand Up @@ -104,13 +177,41 @@ static int vddh_init(const struct device *dev) {
#error Unsupported ADC
#endif

const int rc = adc_channel_setup(adc, &drv_data->acc);
int rc = adc_channel_setup(adc, &drv_data->acc);
LOG_DBG("VDDHDIV5 setup returned %d", rc);

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
const struct vddh_config *drv_cfg = dev->config;
if (!device_is_ready(drv_cfg->chg.port)) {
LOG_ERR("GPIO port for chg reading is not ready");
return -ENODEV;
}
rc = gpio_pin_configure_dt(&drv_cfg->chg, GPIO_INPUT);
if (rc != 0) {
LOG_ERR("Failed to set chg feed %u: %d", drv_cfg->chg.pin, rc);
return rc;
}

drv_data->dev = dev;
gpio_init_callback(&drv_data->gpio_cb, vddh_gpio_cb, BIT(drv_cfg->chg.pin));
int ret = gpio_add_callback(drv_cfg->chg.port, &drv_data->gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to set chg callback: %d", ret);
return -EIO;
}
k_work_init(&drv_data->work, vddh_work_cb);
#endif // DT_INST_NODE_HAS_PROP(0, chg_gpios)

return rc;
}

static struct vddh_data vddh_data;

DEVICE_DT_INST_DEFINE(0, &vddh_init, NULL, &vddh_data, NULL, POST_KERNEL,
static const struct vddh_config vddh_cfg = {
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
.chg = GPIO_DT_SPEC_INST_GET(0, chg_gpios),
#endif
};

DEVICE_DT_INST_DEFINE(0, &vddh_init, NULL, &vddh_data, &vddh_cfg, POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY, &vddh_api);
94 changes: 93 additions & 1 deletion app/module/drivers/sensor/battery/battery_voltage_divider.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <zephyr/logging/log.h>

#include "battery_common.h"
#include <drivers/sensor/battery/battery_charging.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

Expand All @@ -24,6 +25,7 @@ struct io_channel_config {
struct bvd_config {
struct io_channel_config io_channel;
struct gpio_dt_spec power;
struct gpio_dt_spec chg;
uint32_t output_ohm;
uint32_t full_ohm;
};
Expand All @@ -33,16 +35,55 @@ struct bvd_data {
struct adc_channel_cfg acc;
struct adc_sequence as;
struct battery_value value;
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
const struct device *dev;
const struct sensor_trigger *data_ready_trigger;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t data_ready_handler;
struct k_work work;
#endif
};

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
static void set_int(const struct device *dev, const bool en) {
const struct bvd_config *drv_cfg = dev->config;
int ret =
gpio_pin_interrupt_configure_dt(&drv_cfg->chg, en ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE);
if (ret < 0) {
LOG_ERR("can't set interrupt");
}
}

static int bvd_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler) {
struct bvd_data *drv_data = dev->data;

set_int(dev, false);
if (trig->type != SENSOR_TRIG_DATA_READY) {
return -ENOTSUP;
}
drv_data->data_ready_trigger = trig;
drv_data->data_ready_handler = handler;
set_int(dev, true);
return 0;
}

static void bvd_int_cb(const struct device *dev) {
struct bvd_data *drv_data = dev->data;
drv_data->data_ready_handler(dev, drv_data->data_ready_trigger);
LOG_DBG("Setting int on %d", 0);
set_int(dev, true);
}
#endif

static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) {
struct bvd_data *drv_data = dev->data;
const struct bvd_config *drv_cfg = dev->config;
struct adc_sequence *as = &drv_data->as;

// Make sure selected channel is supported
if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE &&
chan != SENSOR_CHAN_ALL) {
(enum sensor_channel_bvd)chan != SENSOR_CHAN_CHARGING && chan != SENSOR_CHAN_ALL) {
LOG_DBG("Selected channel is not supported: %d.", chan);
return -ENOTSUP;
}
Expand Down Expand Up @@ -93,6 +134,18 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan)
}
#endif // DT_INST_NODE_HAS_PROP(0, power_gpios)

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
int raw = gpio_pin_get_dt(&drv_cfg->chg);
if (raw == -EIO || raw == -EWOULDBLOCK) {
LOG_DBG("Failed to read chg status: %d", raw);
return raw;
} else {
bool charging = raw;
LOG_DBG("Charging state: %d", raw);
drv_data->value.charging = charging;
}

#endif
return rc;
}

Expand All @@ -102,7 +155,22 @@ static int bvd_channel_get(const struct device *dev, enum sensor_channel chan,
return battery_channel_get(&drv_data->value, chan, val);
}

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
static void bvd_work_cb(struct k_work *work) {
struct bvd_data *drv_data = CONTAINER_OF(work, struct bvd_data, work);
bvd_int_cb(drv_data->dev);
}
static void bvd_gpio_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins) {
struct bvd_data *drv_data = CONTAINER_OF(cb, struct bvd_data, gpio_cb);
set_int(drv_data->dev, false);
k_work_submit(&drv_data->work);
}
#endif

static const struct sensor_driver_api bvd_api = {
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
.trigger_set = bvd_trigger_set,
#endif
.sample_fetch = bvd_sample_fetch,
.channel_get = bvd_channel_get,
};
Expand Down Expand Up @@ -130,6 +198,27 @@ static int bvd_init(const struct device *dev) {
}
#endif // DT_INST_NODE_HAS_PROP(0, power_gpios)

#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
if (!device_is_ready(drv_cfg->chg.port)) {
LOG_ERR("GPIO port for chg reading is not ready");
return -ENODEV;
}
rc = gpio_pin_configure_dt(&drv_cfg->chg, GPIO_INPUT);
if (rc != 0) {
LOG_ERR("Failed to set chg feed %u: %d", drv_cfg->chg.pin, rc);
return rc;
}

drv_data->dev = dev;
gpio_init_callback(&drv_data->gpio_cb, bvd_gpio_cb, BIT(drv_cfg->chg.pin));
int ret = gpio_add_callback(drv_cfg->chg.port, &drv_data->gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to set chg callback: %d", ret);
return -EIO;
}
k_work_init(&drv_data->work, bvd_work_cb);
#endif // DT_INST_NODE_HAS_PROP(0, chg_gpios)

drv_data->as = (struct adc_sequence){
.channels = BIT(0),
.buffer = &drv_data->value.adc_raw,
Expand Down Expand Up @@ -166,6 +255,9 @@ static const struct bvd_config bvd_cfg = {
},
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
.power = GPIO_DT_SPEC_INST_GET(0, power_gpios),
#endif
#if DT_INST_NODE_HAS_PROP(0, chg_gpios)
.chg = GPIO_DT_SPEC_INST_GET(0, chg_gpios),
#endif
.output_ohm = DT_INST_PROP(0, output_ohms),
.full_ohm = DT_INST_PROP(0, full_ohms),
Expand Down
Loading
Loading