charger: Sample sbs charger driver with tests

Adds a sample sbs charger driver and basics tests.

Signed-off-by: Ricardo Rivera-Matos <rriveram@opensource.cirrus.com>
This commit is contained in:
Ricardo Rivera-Matos 2023-03-31 13:43:51 -05:00 committed by Anas Nashif
parent a7adb06ff4
commit aee815f19d
17 changed files with 589 additions and 0 deletions

View file

@ -20,6 +20,7 @@ add_subdirectory_ifdef(CONFIG_BT_DRIVERS bluetooth)
add_subdirectory_ifdef(CONFIG_CACHE_MANAGEMENT cache)
add_subdirectory_ifdef(CONFIG_CAN can)
add_subdirectory_ifdef(CONFIG_CLOCK_CONTROL clock_control)
add_subdirectory_ifdef(CONFIG_CHARGER charger)
add_subdirectory_ifdef(CONFIG_CONSOLE console)
add_subdirectory_ifdef(CONFIG_COREDUMP_DEVICE coredump)
add_subdirectory_ifdef(CONFIG_COUNTER counter)

View file

@ -12,6 +12,7 @@ source "drivers/bbram/Kconfig"
source "drivers/bluetooth/Kconfig"
source "drivers/cache/Kconfig"
source "drivers/can/Kconfig"
source "drivers/charger/Kconfig"
source "drivers/clock_control/Kconfig"
source "drivers/console/Kconfig"
source "drivers/coredump/Kconfig"

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/charger.h)
zephyr_library_sources_ifdef(CONFIG_SBS_CHARGER sbs_charger.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE charger_handlers.c)
zephyr_library_sources_ifdef(CONFIG_EMUL_SBS_CHARGER emul_sbs_charger.c)

24
drivers/charger/Kconfig Normal file
View file

@ -0,0 +1,24 @@
# Copyright 2023 Cirrus Logic, Inc.
#
# SPDX-License-Identifier: Apache-2.0
menuconfig CHARGER
bool "Battery charger drivers"
help
Enable battery charger driver configuration.
if CHARGER
module = CHARGER
module-str = charger
source "subsys/logging/Kconfig.template.log_config"
config CHARGER_INIT_PRIORITY
int "Battery charger init priority"
default 90
help
Battery charger initialization priority.
source "drivers/charger/Kconfig.sbs_charger"
endif # CHARGER

View file

@ -0,0 +1,19 @@
# Copyright 2023 Cirrus Logic, Inc.
# SPDX-License-Identifier: Apache-2.0
config SBS_CHARGER
bool "Smart Battery Charger"
default y
depends on DT_HAS_SBS_SBS_CHARGER_ENABLED
select I2C
help
Enable I2C-based/SMBus-based driver for a Smart Battery Charger.
config EMUL_SBS_CHARGER
bool "Emulate an SBS 1.1 compliant smart battery charger"
default y
depends on EMUL
depends on SBS_CHARGER
help
It provides reading which follow a simple sequence, thus allowing
test code to check that things are working as expected.

View file

@ -0,0 +1,141 @@
/*
* Copyright 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
* Emulator for SBS 1.1 compliant smart battery charger.
*/
#define DT_DRV_COMPAT sbs_sbs_charger
#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include "sbs_charger.h"
LOG_MODULE_REGISTER(sbs_sbs_charger);
/** Static configuration for the emulator */
struct sbs_charger_emul_cfg {
/** I2C address of emulator */
uint16_t addr;
};
static int emul_sbs_charger_reg_write(const struct emul *target, int reg, int val)
{
LOG_INF("write %x = %x", reg, val);
switch (reg) {
default:
LOG_ERR("Unknown write %x", reg);
return -EIO;
}
return 0;
}
static int emul_sbs_charger_reg_read(const struct emul *target, int reg, int *val)
{
switch (reg) {
case SBS_CHARGER_REG_SPEC_INFO:
case SBS_CHARGER_REG_CHARGER_MODE:
case SBS_CHARGER_REG_STATUS:
case SBS_CHARGER_REG_ALARM_WARNING:
/* Arbitrary stub value. */
*val = 1;
break;
default:
LOG_ERR("Unknown register 0x%x read", reg);
return -EIO;
}
LOG_INF("read 0x%x = 0x%x", reg, *val);
return 0;
}
static int sbs_charger_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
int num_msgs, int addr)
{
/* Largely copied from emul_sbs_gauge.c */
struct sbs_charger_emul_data *data;
unsigned int val;
int reg;
int rc;
data = target->data;
i2c_dump_msgs_rw("emul", msgs, num_msgs, addr, false);
switch (num_msgs) {
case 2:
if (msgs->flags & I2C_MSG_READ) {
LOG_ERR("Unexpected read");
return -EIO;
}
if (msgs->len != 1) {
LOG_ERR("Unexpected msg0 length %d", msgs->len);
return -EIO;
}
reg = msgs->buf[0];
/* Now process the 'read' part of the message */
msgs++;
if (msgs->flags & I2C_MSG_READ) {
switch (msgs->len - 1) {
case 1:
rc = emul_sbs_charger_reg_read(target, reg, &val);
if (rc) {
/* Return before writing bad value to message buffer */
return rc;
}
/* SBS uses SMBus, which sends data in little-endian format. */
sys_put_le16(val, msgs->buf);
break;
default:
LOG_ERR("Unexpected msg1 length %d", msgs->len);
return -EIO;
}
} else {
/* We write a word (2 bytes by the SBS spec) */
if (msgs->len != 2) {
LOG_ERR("Unexpected msg1 length %d", msgs->len);
}
uint16_t value = sys_get_le16(msgs->buf);
rc = emul_sbs_charger_reg_write(target, reg, value);
}
break;
default:
LOG_ERR("Invalid number of messages: %d", num_msgs);
return -EIO;
}
return rc;
}
static const struct i2c_emul_api sbs_charger_emul_api_i2c = {
.transfer = sbs_charger_emul_transfer_i2c,
};
static int emul_sbs_sbs_charger_init(const struct emul *target, const struct device *parent)
{
ARG_UNUSED(target);
ARG_UNUSED(parent);
return 0;
}
/*
* Main instantiation macro. SBS Charger Emulator only implemented for I2C
*/
#define SBS_CHARGER_EMUL(n) \
static const struct sbs_charger_emul_cfg sbs_charger_emul_cfg_##n = { \
.addr = DT_INST_REG_ADDR(n), \
}; \
EMUL_DT_INST_DEFINE(n, emul_sbs_sbs_charger_init, NULL, &sbs_charger_emul_cfg_##n, \
&sbs_charger_emul_api_i2c, NULL)
DT_INST_FOREACH_STATUS_OKAY(SBS_CHARGER_EMUL)

View file

@ -0,0 +1,171 @@
/*
* Copyright 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sbs_sbs_charger
#include <zephyr/drivers/charger.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include "sbs_charger.h"
struct sbs_charger_config {
struct i2c_dt_spec i2c;
};
LOG_MODULE_REGISTER(sbs_charger);
static int sbs_cmd_reg_read(const struct device *dev, uint8_t reg_addr, uint16_t *val)
{
const struct sbs_charger_config *cfg;
uint8_t i2c_data[2];
int status;
cfg = dev->config;
status = i2c_burst_read_dt(&cfg->i2c, reg_addr, i2c_data, sizeof(i2c_data));
if (status < 0) {
LOG_ERR("Unable to read register");
return status;
}
*val = sys_get_le16(i2c_data);
return 0;
}
static int sbs_cmd_reg_write(const struct device *dev, uint8_t reg_addr, uint16_t val)
{
const struct sbs_charger_config *config = dev->config;
uint8_t buf[2];
sys_put_le16(val, buf);
return i2c_burst_write_dt(&config->i2c, reg_addr, buf, sizeof(buf));
}
static int sbs_cmd_reg_update(const struct device *dev, uint8_t reg_addr, uint16_t mask,
uint16_t val)
{
uint16_t old_val, new_val;
int ret;
ret = sbs_cmd_reg_read(dev, SBS_CHARGER_REG_STATUS, &old_val);
if (ret < 0) {
return ret;
}
new_val = (old_val & ~mask) | (val & mask);
if (new_val == old_val) {
return 0;
}
return sbs_cmd_reg_write(dev, reg_addr, new_val);
}
static int sbs_charger_get_prop(const struct device *dev, const charger_prop_t prop,
union charger_propval *val)
{
uint16_t reg_val;
int ret;
switch (prop) {
case CHARGER_PROP_ONLINE:
ret = sbs_cmd_reg_read(dev, SBS_CHARGER_REG_STATUS, &reg_val);
if (ret < 0) {
return ret;
}
if (reg_val & SBS_CHARGER_STATUS_AC_PRESENT) {
val->online = CHARGER_ONLINE_FIXED;
} else {
val->online = CHARGER_ONLINE_OFFLINE;
}
return 0;
case CHARGER_PROP_PRESENT:
ret = sbs_cmd_reg_read(dev, SBS_CHARGER_REG_STATUS, &reg_val);
if (ret < 0) {
return ret;
}
if (reg_val & SBS_CHARGER_STATUS_BATTERY_PRESENT) {
val->present = true;
} else {
val->present = false;
}
return 0;
case CHARGER_PROP_STATUS:
ret = sbs_cmd_reg_read(dev, SBS_CHARGER_REG_STATUS, &reg_val);
if (ret < 0) {
return ret;
}
if (!(reg_val & SBS_CHARGER_STATUS_BATTERY_PRESENT)) {
val->status = CHARGER_STATUS_NOT_CHARGING;
} else if (reg_val & SBS_CHARGER_STATUS_AC_PRESENT &&
!(reg_val & SBS_CHARGER_STATUS_CHARGE_INHIBITED)) {
val->status = CHARGER_STATUS_CHARGING;
} else {
val->status = CHARGER_STATUS_DISCHARGING;
}
return 0;
default:
return -ENOTSUP;
}
}
static int sbs_charger_set_prop(const struct device *dev, const charger_prop_t prop,
const union charger_propval *val)
{
uint16_t reg_val = 0;
switch (prop) {
case CHARGER_PROP_STATUS:
if (val->status != CHARGER_STATUS_CHARGING) {
reg_val = SBS_CHARGER_MODE_INHIBIT_CHARGE;
}
return sbs_cmd_reg_update(dev, SBS_CHARGER_REG_CHARGER_MODE,
SBS_CHARGER_MODE_INHIBIT_CHARGE, reg_val);
default:
return -ENOTSUP;
}
}
/**
* @brief initialize the charger
*
* @return 0 for success
*/
static int sbs_charger_init(const struct device *dev)
{
const struct sbs_charger_config *cfg = dev->config;
if (!i2c_is_ready_dt(&cfg->i2c)) {
LOG_ERR("Bus device is not ready");
return -ENODEV;
}
return 0;
}
static const struct charger_driver_api sbs_charger_driver_api = {
.get_property = &sbs_charger_get_prop,
.set_property = &sbs_charger_set_prop,
};
#define SBS_CHARGER_INIT(inst) \
\
static const struct sbs_charger_config sbs_charger_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, &sbs_charger_init, NULL, NULL, &sbs_charger_config_##inst, \
POST_KERNEL, CONFIG_CHARGER_INIT_PRIORITY, &sbs_charger_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SBS_CHARGER_INIT)

View file

@ -0,0 +1,20 @@
/*
* Copyright 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define SBS_CHARGER_REG_SPEC_INFO 0x11
#define SBS_CHARGER_REG_CHARGER_MODE 0x12
#define SBS_CHARGER_REG_STATUS 0x13
#define SBS_CHARGER_REG_ALARM_WARNING 0x16
#define SBS_CHARGER_MODE_INHIBIT_CHARGE BIT(0)
#define SBS_CHARGER_STATUS_CHARGE_INHIBITED BIT(0)
#define SBS_CHARGER_STATUS_RES_COLD BIT(9)
#define SBS_CHARGER_STATUS_RES_HOT BIT(10)
#define SBS_CHARGER_STATUS_BATTERY_PRESENT BIT(14)
#define SBS_CHARGER_STATUS_AC_PRESENT BIT(15)
#define SBS_CHARGER_POLL_TIME 500

View file

@ -0,0 +1,11 @@
#
# Copyright 2023 Cirrus Logic, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
description: SBS 1.1 compliant charger (http://www.sbs-forum.org/specs)
compatible: "sbs,sbs-charger"
include: [i2c-device.yaml]

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(device)
FILE(GLOB app_sources src/test_sbs_charger.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,4 @@
# Copyright (c) 2023 Cirrus Logic, Inc.
# SPDX-License-Identifier: Apache-2.0
CONFIG_EMUL=y

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/dt-bindings/i2c/i2c.h>
/ {
fake_i2c_bus: i2c@100 {
status = "okay";
compatible = "zephyr,i2c-emul-controller";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x100 4>;
};
};
&fake_i2c_bus {
clock-frequency = <I2C_BITRATE_STANDARD>;
compatible = "zephyr,i2c-emul-controller";
smartcharger0: smartcharger@b {
compatible = "sbs,sbs-charger";
reg = <0x0B>;
status = "okay";
};
};

View file

@ -0,0 +1,4 @@
# Copyright (c) 2023 Cirrus Logic, Inc.
# SPDX-License-Identifier: Apache-2.0
CONFIG_EMUL=y

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/dt-bindings/i2c/i2c.h>
/ {
fake_i2c_bus: i2c@100 {
status = "okay";
compatible = "zephyr,i2c-emul-controller";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
/*
* qemu_cortex_a53 SoC requires a 64 bit child addresses (reg properties)
* See its /soc #address-cells & #size-cells properties.
*/
reg = <0x0 0x100 0 4>;
};
};
&fake_i2c_bus {
clock-frequency = <I2C_BITRATE_STANDARD>;
compatible = "zephyr,i2c-emul-controller";
smartcharger0: smartcharger@b {
compatible = "sbs,sbs-charger";
reg = <0x0B>;
status = "okay";
};
};

View file

@ -0,0 +1,6 @@
CONFIG_ZTEST=y
CONFIG_ZTEST_NEW_API=y
CONFIG_TEST_USERSPACE=y
CONFIG_LOG=y
CONFIG_CHARGER=y

View file

@ -0,0 +1,77 @@
/*
* Copyright 2023 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/charger.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/ztest.h>
#include <zephyr/ztest_assert.h>
struct sbs_charger_fixture {
const struct device *dev;
const struct charger_driver_api *api;
};
static void *sbs_charger_setup(void)
{
static ZTEST_DMEM struct sbs_charger_fixture fixture;
fixture.dev = DEVICE_DT_GET_ANY(sbs_sbs_charger);
k_object_access_all_grant(fixture.dev);
zassert_true(device_is_ready(fixture.dev), "Charger not found");
return &fixture;
}
ZTEST_USER_F(sbs_charger, test_get_prop_failed_returns_negative)
{
/* Grab a bogus property */
charger_prop_t prop = CHARGER_PROP_MAX;
union charger_propval val = {0};
int ret = charger_get_prop(fixture->dev, prop, &val);
zassert_equal(ret, -ENOTSUP, "Getting bad property %d has a good status.", prop);
}
ZTEST_USER_F(sbs_charger, test_get_prop_success_returns_zero)
{
/* Validate what props are supported by the driver */
charger_prop_t prop = CHARGER_PROP_ONLINE;
union charger_propval val = {0};
int ret = charger_get_prop(fixture->dev, prop, &val);
zassert_equal(ret, 0, "Getting good property %d has a good status.", prop);
}
ZTEST_USER_F(sbs_charger, test_set_prop_failed_returns_negative)
{
/* Set a bogus property */
charger_prop_t prop = CHARGER_PROP_MAX;
union charger_propval val = {0};
int ret = charger_set_prop(fixture->dev, prop, &val);
zassert_equal(ret, -ENOTSUP, "Setting bad property %d has a good status.", prop);
}
ZTEST_USER_F(sbs_charger, test_set_prop_success_returns_zero)
{
union charger_propval val = {.status = CHARGER_STATUS_NOT_CHARGING};
charger_prop_t prop = CHARGER_PROP_STATUS;
int ret = charger_set_prop(fixture->dev, prop, &val);
zassert_equal(ret, 0, "Setting good property %d has a good status.", prop);
}
ZTEST_SUITE(sbs_charger, NULL, sbs_charger_setup, NULL, NULL, NULL);

View file

@ -0,0 +1,34 @@
tests:
# section.subsection
drivers.sbs_charger.emulated:
tags: drivers charger
filter: >
dt_compat_enabled("sbs,sbs-charger") and
(CONFIG_QEMU_TARGET or CONFIG_BOARD_NATIVE_POSIX)
extra_args:
CONF_FILE="prj.conf;boards/emulated_board.conf"
DTC_OVERLAY_FILE="boards/emulated_board.overlay"
platform_exclude:
qemu_cortex_a53
qemu_cortex_a53_smp
qemu_kvm_arm64
xenvm
xenvm_gicv3
hifive_unmatched
rcar_h3ulcb_ca57
rcar_salvator_xs_m3
numaker_pfm_m467
drivers.sbs_charger.emulated_64_bit_i2c_addr:
tags: drivers charger
filter: >
dt_compat_enabled("sbs,sbs-charger") and
(CONFIG_QEMU_TARGET or CONFIG_BOARD_NATIVE_POSIX)
platform_allow:
qemu_cortex_a53
qemu_cortex_a53_smp
qemu_kvm_arm64
xenvm
xenvm_gicv3
extra_args:
CONF_FILE="prj.conf;boards/qemu_cortex_a53.conf"
DTC_OVERLAY_FILE="boards/qemu_cortex_a53.overlay"