This commit is contained in:
ReFil 2024-09-01 21:31:35 +08:00 committed by GitHub
commit ea12ee438b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 312 additions and 13 deletions

View file

@ -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

View file

@ -7,3 +7,4 @@
#pragma once
uint8_t zmk_battery_state_of_charge(void);
bool zmk_battery_charging(void);

View file

@ -12,6 +12,7 @@
struct zmk_battery_state_changed {
// TODO: Other battery channels
uint8_t state_of_charge;
bool charging;
};
ZMK_EVENT_DECLARE(zmk_battery_state_changed);
@ -19,6 +20,7 @@ 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;
};

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: MIT
zephyr_include_directories(.)
zephyr_include_directories(${CMAKE_SOURCE_DIR}/include)
zephyr_library()

View file

@ -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,
@ -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;
}

View file

@ -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,

View file

@ -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);
@ -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;
}
@ -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;
}
@ -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,
};
@ -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);

View file

@ -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);
@ -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;
};
@ -33,8 +35,47 @@ 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;
@ -42,7 +83,7 @@ static int bvd_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;
}
@ -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;
}
@ -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,
};
@ -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,
@ -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),

View file

@ -4,3 +4,9 @@
description: Battery SoC monitoring using nRF VDDH
compatible: "zmk,battery-nrf-vddh"
properties:
chg-gpios:
required: false
type: phandle-array
description: "A GPIO pin to report charging state to"

View file

@ -6,3 +6,9 @@ description: Battery SoC monitoring using voltage divider
compatible: "zmk,battery-voltage-divider"
include: voltage-divider.yaml
properties:
chg-gpios:
required: false
type: phandle-array
description: "A GPIO pin to report charging state to"

View file

@ -22,9 +22,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/activity.h>
#include <zmk/workqueue.h>
#include <drivers/sensor/battery/battery_charging.h>
static uint8_t last_state_of_charge = 0;
static bool charging = 0;
uint8_t zmk_battery_state_of_charge(void) { return last_state_of_charge; }
bool zmk_battery_charging(void) { return charging; }
#if DT_HAS_CHOSEN(zmk_battery)
static const struct device *const battery = DEVICE_DT_GET(DT_CHOSEN(zmk_battery));
@ -91,8 +95,31 @@ static int zmk_battery_update(const struct device *battery) {
#error "Not a supported reporting fetch mode"
#endif
if (last_state_of_charge != state_of_charge.val1) {
#if DT_NODE_HAS_PROP(DT_CHOSEN(zmk_battery), chg_gpios)
rc = sensor_sample_fetch_chan(battery, SENSOR_CHAN_CHARGING);
if (rc != 0) {
LOG_DBG("Failed to fetch charging value: %d", rc);
return rc;
}
struct sensor_value charging_state;
rc = sensor_channel_get(battery, SENSOR_CHAN_CHARGING, &charging_state);
if (rc != 0) {
LOG_DBG("Failed to get battery charging status: %d", rc);
return rc;
}
#endif
if (last_state_of_charge != state_of_charge.val1
#if DT_NODE_HAS_PROP(DT_CHOSEN(zmk_battery), chg_gpios)
|| charging != charging_state.val1
#endif
) {
last_state_of_charge = state_of_charge.val1;
#if DT_NODE_HAS_PROP(DT_CHOSEN(zmk_battery), chg_gpios)
charging = charging_state.val1;
#endif
#if IS_ENABLED(CONFIG_BT_BAS)
LOG_DBG("Setting BAS GATT battery level to %d.", last_state_of_charge);
@ -103,8 +130,8 @@ static int zmk_battery_update(const struct device *battery) {
return rc;
}
#endif
rc = raise_zmk_battery_state_changed(
(struct zmk_battery_state_changed){.state_of_charge = last_state_of_charge});
rc = raise_zmk_battery_state_changed((struct zmk_battery_state_changed){
.state_of_charge = last_state_of_charge, .charging = charging});
}
return rc;
@ -131,7 +158,11 @@ static void zmk_battery_start_reporting() {
k_timer_start(&battery_timer, K_NO_WAIT, K_SECONDS(CONFIG_ZMK_BATTERY_REPORT_INTERVAL));
}
}
#if DT_NODE_HAS_PROP(DT_CHOSEN(zmk_battery), chg_gpios)
static void handle_chg_trig(const struct device *dev, const struct sensor_trigger *trig) {
zmk_battery_update(dev);
}
#endif
static int zmk_battery_init(void) {
#if !DT_HAS_CHOSEN(zmk_battery)
battery = device_get_binding("BATTERY");
@ -148,6 +179,16 @@ static int zmk_battery_init(void) {
return -ENODEV;
}
#if DT_NODE_HAS_PROP(DT_CHOSEN(zmk_battery), chg_gpios)
struct sensor_trigger trigger = {
.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_ALL,
};
if (sensor_trigger_set(battery, &trigger, handle_chg_trig) < 0) {
LOG_ERR("can't set batt chg trigger");
};
#endif
zmk_battery_start_reporting();
return 0;
}

View file

@ -49,22 +49,42 @@ Applies to: [`/chosen` node](https://docs.zephyrproject.org/3.5.0/build/dts/intr
## Battery Voltage Divider Sensor
Driver for reading the voltage of a battery using an ADC connected to a voltage divider.
Driver for reading the voltage of a battery using an ADC connected to a voltage divider. This driver can also read a GPIO pin to detect whether the battery is charging or not. This requires supported hardware (a battery charging IC with an output to indicate charging status). This functionality is optional, if the hardware doesn't support it the `chg-gpios` devicetree configuration does not have to be set.
### Devicetree
Applies to: `compatible = "zmk,battery-voltage-divider"`
See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/3.5.0/build/dts/api/bindings/iio/afe/voltage-divider.html).
Definition file: [zmk/app/module/dts/bindings/sensor/zmk,battery-voltage-divider.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/sensor/zmk,battery-voltage-divider.yaml)
The ZMK battery voltage divider includes the [Zephyr voltage divider](https://docs.zephyrproject.org/latest/build/dts/api/bindings/adc/voltage-divider.html) and adds on additional functionality.
| Property | Type | Description | Default |
| ----------- | ---------- | ------------------------------------------------ | ------- |
| `chg-gpios` | GPIO array | GPIO connected to the charging IC's charging pin | |
:::note Charging indication
The battery charging status is not consumed by any built-in indicators currently and it cannot be conveyed to the host over BLE.
:::
## nRF VDDH Battery Sensor
Driver for reading the voltage of a battery using a Nordic nRF52's VDDH pin.
Driver for reading the voltage of a battery using a Nordic nRF52's VDDH pin. This driver can also read a GPIO pin to detect whether the battery is charging or not. This requires supported hardware (a battery charging IC with an output to indicate charging status). This functionality is optional, if the hardware doesn't support it the `chg-gpios` devicetree configuration does not have to be set.
### Devicetree
Applies to: `compatible = "zmk,battery-nrf-vddh"`
Definition file: [zmk/app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/sensor/zmk%2Cbattery-nrf-vddh.yaml)
Definition file: [zmk/app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml)
This driver has no configuration.
| Property | Type | Description | Default |
| ----------- | ---------- | ------------------------------------------------ | ------- |
| `chg-gpios` | GPIO array | GPIO connected to the charging IC's charging pin | |
:::note Charging indication
The battery charging status is not consumed by any built-in indicators currently and it cannot be conveyed to the host over BLE.
:::