Linux Audio

Check our new training course

Loading...
/*
 * Copyright 2020 Peter Bigot Consulting, LLC
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <drivers/gpio.h>
#include <drivers/regulator.h>
#include <ztest.h>

#define REGULATOR_NODE DT_PATH(regulator)
#define CHECK_NODE DT_PATH(resources)

BUILD_ASSERT(DT_NODE_HAS_COMPAT_STATUS(REGULATOR_NODE, regulator_fixed, okay));
BUILD_ASSERT(DT_NODE_HAS_COMPAT_STATUS(CHECK_NODE, test_regulator_fixed, okay));

#define IS_REGULATOR_SYNC DT_NODE_HAS_COMPAT_STATUS(REGULATOR_NODE, regulator_fixed_sync, okay)
#define BOOT_ON DT_PROP(REGULATOR_NODE, regulator_boot_on)
#define ALWAYS_ON DT_PROP(REGULATOR_NODE, regulator_always_on)
#define STARTUP_DELAY_US DT_PROP(REGULATOR_NODE, startup_delay_us)
#define OFF_ON_DELAY_US DT_PROP(REGULATOR_NODE, off_on_delay_us)

static const struct device *check_gpio;
static const struct device *reg_dev;
static const gpio_pin_t check_pin = DT_GPIO_PIN(CHECK_NODE, check_gpios);
static enum {
	PC_UNCHECKED,
	PC_FAIL_REG_INIT,
	PC_FAIL_DEVICES,
	PC_FAIL_CFG_OUTPUT,
	PC_FAIL_CFG_INPUT,
	PC_FAIL_INACTIVE,
	PC_FAIL_ACTIVE,
	PC_FAIL_UNCONFIGURE,
	PC_OK,
} precheck = PC_UNCHECKED;
static const char *const pc_errstr[] = {
	[PC_UNCHECKED] = "precheck not verified",
	[PC_FAIL_REG_INIT] = "regulator already initialized",
	[PC_FAIL_DEVICES] = "bad GPIO devices",
	[PC_FAIL_CFG_OUTPUT] = "failed to configure output",
	[PC_FAIL_CFG_INPUT] = "failed to configure input",
	[PC_FAIL_INACTIVE] = "inactive check failed",
	[PC_FAIL_ACTIVE] = "active check failed",
	[PC_FAIL_UNCONFIGURE] = "failed to disconnect regulator GPIO",
	[PC_OK] = "precheck OK",
};

static struct onoff_client cli;
static struct onoff_manager *callback_srv;
static struct onoff_client *callback_cli;
static uint32_t callback_state;
static int callback_res;
static onoff_client_callback callback_fn;

static void callback(struct onoff_manager *srv,
		     struct onoff_client *cli,
		     uint32_t state,
		     int res)
{
	onoff_client_callback cb = callback_fn;

	callback_srv = srv;
	callback_cli = cli;
	callback_state = state;
	callback_res = res;
	callback_fn = NULL;

	if (cb != NULL) {
		cb(srv, cli, state, res);
	}
}

static void reset_callback(void)
{
	callback_srv = NULL;
	callback_cli = NULL;
	callback_state = INT_MIN;
	callback_res = 0;
	callback_fn = NULL;
}

static void reset_client(void)
{
	cli = (struct onoff_client){};
	reset_callback();
	sys_notify_init_callback(&cli.notify, callback);
}

static int reg_status(void)
{
	return gpio_pin_get(check_gpio, check_pin);
}

static int setup(const struct device *dev)
{
#if 0
	reg_dev = device_get_binding(DT_LABEL(REGULATOR_NODE));

	/* todo: figure out how to verify the device hasn't been initialized. */
	if (reg_dev != NULL) {
		precheck = PC_FAIL_REG_INIT;
		return -EBUSY;
	}
#endif

	/* Configure the regulator GPIO as an output inactive, and the check
	 * GPIO as an input, then start testing whether they track.
	 */

	const char *reg_label = DT_GPIO_LABEL(REGULATOR_NODE, enable_gpios);
	const struct device *reg_gpio = device_get_binding(reg_label);
	const char *check_label = DT_GPIO_LABEL(CHECK_NODE, check_gpios);
	const gpio_pin_t reg_pin = DT_GPIO_PIN(REGULATOR_NODE, enable_gpios);

	check_gpio = device_get_binding(check_label);

	if ((reg_gpio == NULL) || (check_gpio == NULL)) {
		precheck = PC_FAIL_DEVICES;
		return -EINVAL;
	}

	int rc = gpio_pin_configure(reg_gpio, reg_pin,
				    GPIO_OUTPUT_INACTIVE
				    | DT_GPIO_FLAGS(REGULATOR_NODE, enable_gpios));
	if (rc != 0) {
		precheck = PC_FAIL_CFG_OUTPUT;
		return -EIO;
	}

	rc = gpio_pin_configure(check_gpio, check_pin,
				GPIO_INPUT
				| DT_GPIO_FLAGS(CHECK_NODE, check_gpios));
	if (rc != 0) {
		precheck = PC_FAIL_CFG_INPUT;
		return -EIO;
	}

	rc = reg_status();

	if (rc != 0) {		/* should be inactive */
		precheck = PC_FAIL_INACTIVE;
		return -EIO;
	}

	rc = gpio_pin_set(reg_gpio, reg_pin, true);

	if (rc == 0) {
		rc = reg_status();
	}

	if (rc != 1) {		/* should be active */
		precheck = PC_FAIL_ACTIVE;
		return -EIO;
	}

	rc = gpio_pin_configure(reg_gpio, reg_pin, GPIO_DISCONNECTED);
	if (rc == -ENOTSUP) {
		rc = gpio_pin_configure(reg_gpio, reg_pin, GPIO_INPUT);
	}
	if (rc == 0) {
		rc = reg_status();
	}

	if (rc != 0) {		/* should be inactive */
		precheck = PC_FAIL_UNCONFIGURE;
		return -EIO;
	}

	precheck = PC_OK;

	return rc;
}

/* The regulator driver initializes in POST_KERNEL since it has
 * thread-related stuff in it.  We need to verify the shorted signals
 * required by the test before the driver configures its GPIO.  This
 * should be done late PRE_KERNEL_9, but it can't because Nordic and
 * possibly other systems initialize GPIO drivers post-kernel. */
BUILD_ASSERT(CONFIG_REGULATOR_FIXED_INIT_PRIORITY > 74);
SYS_INIT(setup, POST_KERNEL, 74);

static void test_preconditions(void)
{
	zassert_equal(precheck, PC_OK,
		      "precheck failed: %s",
		      pc_errstr[precheck]);

	zassert_not_equal(reg_dev, NULL,
			  "no regulator device");
}

static void test_basic(void)
{
	zassert_equal(precheck, PC_OK,
		      "precheck failed: %s",
		      pc_errstr[precheck]);

	zassert_not_equal(reg_dev, NULL,
			  "no regulator device");

	int rs = reg_status();

	/* Initial state: on if and only if it's always on or was enabled at
	 * boot.
	 */

	if (IS_ENABLED(BOOT_ON) || IS_ENABLED(ALWAYS_ON)) {
		zassert_equal(rs, 1,
			      "not on at boot: %d", rs);
	} else {
		zassert_equal(rs, 0,
			      "not off at boot: %d", rs);
	}

	reset_client();

	/* Turn it on */
	int rc = regulator_enable(reg_dev, &cli);
	zassert_true(rc >= 0,
		     "first enable failed: %d", rc);

	if (STARTUP_DELAY_US > 0) {
		rc = sys_notify_fetch_result(&cli.notify, &rc);

		zassert_equal(rc, -EAGAIN,
			      "startup notify early: %d", rc);

		while (sys_notify_fetch_result(&cli.notify, &rc) == -EAGAIN) {
			k_yield();
		}
	}

	zassert_equal(callback_cli, &cli,
		      "callback not invoked");
	zassert_equal(callback_res, 0,
		      "callback res: %d", callback_res);
	zassert_equal(callback_state, ONOFF_STATE_ON,
		      "callback state: 0x%x", callback_res);

	/* Make sure it's on */

	rs = reg_status();
	zassert_equal(rs, 1,
		      "bad on state: %d", rs);

	/* Turn it on again (another client) */

	reset_client();
	rc = regulator_enable(reg_dev, &cli);
	zassert_true(rc >= 0,
		     "second enable failed: %d", rc);

	zassert_equal(callback_cli, &cli,
		      "callback not invoked");
	zassert_true(callback_res >= 0,
		      "callback res: %d", callback_res);
	zassert_equal(callback_state, ONOFF_STATE_ON,
		      "callback state: 0x%x", callback_res);

	/* Make sure it's still on */

	rs = reg_status();
	zassert_equal(rs, 1,
		      "bad 2x on state: %d", rs);

	/* Turn it off once (still has a client) */

	rc = regulator_disable(reg_dev);
	zassert_true(rc >= 0,
		     "first disable failed: %d", rc);

	/* Make sure it's still on */

	rs = reg_status();
	zassert_equal(rs, 1,
		      "bad 2x on 1x off state: %d", rs);

	/* Turn it off again (no more clients) */

	rc = regulator_disable(reg_dev);
	zassert_true(rc >= 0,
		     "first disable failed: %d", rc);

	/* On if and only if it can't be turned off */

	rs = reg_status();
	zassert_equal(rs, IS_ENABLED(ALWAYS_ON),
		      "bad 2x on 2x off state: %d", rs);
}

void test_main(void)
{
	const char * const compats[] = DT_PROP(REGULATOR_NODE, compatible);
	reg_dev = device_get_binding(DT_LABEL(REGULATOR_NODE));

	printk("reg %p gpio %p\n", reg_dev, check_gpio);
	TC_PRINT("Regulator: %s%s%s\n", compats[0],
		 IS_ENABLED(BOOT_ON) ? ", boot-on" : "",
		 IS_ENABLED(ALWAYS_ON) ? ", always-on" : "");
	TC_PRINT("startup-delay: %u us\n", STARTUP_DELAY_US);
	TC_PRINT("off-on-delay: %u us\n", OFF_ON_DELAY_US);

	ztest_test_suite(regulator_test,
			 ztest_unit_test(test_preconditions),
			 ztest_unit_test(test_basic));
	ztest_run_test_suite(regulator_test);
}