| /* |
| * Copyright (c) 2019, Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <ztest.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(test); |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) |
| #include <zephyr/drivers/clock_control/nrf_clock_control.h> |
| #endif |
| |
| struct device_subsys_data { |
| clock_control_subsys_t subsys; |
| uint32_t startup_us; |
| }; |
| |
| struct device_data { |
| const char *name; |
| const struct device_subsys_data *subsys_data; |
| uint32_t subsys_cnt; |
| }; |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) |
| static const struct device_subsys_data subsys_data[] = { |
| { |
| .subsys = CLOCK_CONTROL_NRF_SUBSYS_HF, |
| .startup_us = |
| IS_ENABLED(CONFIG_SOC_SERIES_NRF91X) ? |
| 3000 : 500 |
| }, |
| #ifndef CONFIG_SOC_NRF52832 |
| /* On nrf52832 LF clock cannot be stopped because it leads |
| * to RTC COUNTER register reset and that is unexpected by |
| * system clock which is disrupted and may hang in the test. |
| */ |
| { |
| .subsys = CLOCK_CONTROL_NRF_SUBSYS_LF, |
| .startup_us = (CLOCK_CONTROL_NRF_K32SRC == |
| NRF_CLOCK_LFCLK_RC) ? 1000 : 500000 |
| } |
| #endif /* !CONFIG_SOC_NRF52832 */ |
| }; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) */ |
| |
| static const struct device_data devices[] = { |
| #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) |
| { |
| .name = DT_LABEL(DT_INST(0, nordic_nrf_clock)), |
| .subsys_data = subsys_data, |
| .subsys_cnt = ARRAY_SIZE(subsys_data) |
| } |
| #endif |
| }; |
| |
| |
| typedef void (*test_func_t)(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us); |
| |
| typedef bool (*test_capability_check_t)(const char *dev_name, |
| clock_control_subsys_t subsys); |
| |
| static void setup_instance(const char *dev_name, clock_control_subsys_t subsys) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| int err; |
| k_busy_wait(1000); |
| do { |
| err = clock_control_off(dev, subsys); |
| #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) |
| if (err == -EPERM) { |
| struct onoff_manager *mgr = |
| z_nrf_clock_control_get_onoff(subsys); |
| |
| err = onoff_release(mgr); |
| if (err >= 0) { |
| break; |
| } |
| } |
| #endif |
| } while (clock_control_get_status(dev, subsys) != |
| CLOCK_CONTROL_STATUS_OFF); |
| |
| LOG_INF("setup done"); |
| } |
| |
| static void tear_down_instance(const char *dev_name, |
| clock_control_subsys_t subsys) |
| { |
| #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_clock) |
| /* Turn on LF clock using onoff service if it is disabled. */ |
| const struct device *clk = |
| device_get_binding(DT_LABEL(DT_INST(0, nordic_nrf_clock))); |
| struct onoff_client cli; |
| struct onoff_manager *mgr = |
| z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_LF); |
| int err; |
| |
| if (clock_control_get_status(clk, CLOCK_CONTROL_NRF_SUBSYS_LF) != |
| CLOCK_CONTROL_STATUS_OFF) { |
| return; |
| } |
| |
| sys_notify_init_spinwait(&cli.notify); |
| err = onoff_request(mgr, &cli); |
| zassert_true(err >= 0, ""); |
| |
| while (sys_notify_fetch_result(&cli.notify, &err) < 0) { |
| /*empty*/ |
| } |
| zassert_true(err >= 0, ""); |
| #endif |
| } |
| |
| static void test_with_single_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_time, |
| test_func_t func, |
| test_capability_check_t capability_check) |
| { |
| setup_instance(dev_name, subsys); |
| |
| if ((capability_check == NULL) || capability_check(dev_name, subsys)) { |
| func(dev_name, subsys, startup_time); |
| } else { |
| PRINT("test skipped for subsys:%d\n", (int)subsys); |
| } |
| |
| tear_down_instance(dev_name, subsys); |
| /* Allow logs to be printed. */ |
| k_sleep(K_MSEC(100)); |
| } |
| static void test_all_instances(test_func_t func, |
| test_capability_check_t capability_check) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(devices); i++) { |
| for (size_t j = 0; j < devices[i].subsys_cnt; j++) { |
| test_with_single_instance(devices[i].name, |
| devices[i].subsys_data[j].subsys, |
| devices[i].subsys_data[j].startup_us, |
| func, capability_check); |
| } |
| } |
| } |
| |
| /* |
| * Basic test for checking correctness of getting clock status. |
| */ |
| static void test_on_off_status_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| enum clock_control_status status; |
| int err; |
| |
| zassert_true(dev != NULL, "%s: Unknown device", dev_name); |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| err = clock_control_on(dev, subsys); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(status, CLOCK_CONTROL_STATUS_ON, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| err = clock_control_off(dev, subsys); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| } |
| |
| static void test_on_off_status(void) |
| { |
| test_all_instances(test_on_off_status_instance, NULL); |
| } |
| |
| static void async_capable_callback(const struct device *dev, |
| clock_control_subsys_t subsys, |
| void *user_data) |
| { |
| /* empty */ |
| } |
| |
| /* Function checks if clock supports asynchronous starting. */ |
| static bool async_capable(const char *dev_name, clock_control_subsys_t subsys) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| int err; |
| |
| err = clock_control_async_on(dev, subsys, async_capable_callback, NULL); |
| if (err < 0) { |
| printk("failed %d", err); |
| return false; |
| } |
| |
| while (clock_control_get_status(dev, subsys) != |
| CLOCK_CONTROL_STATUS_ON) { |
| /* pend util clock is started */ |
| } |
| |
| err = clock_control_off(dev, subsys); |
| if (err < 0) { |
| printk("clock_control_off failed %d", err); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Test checks that callbacks are called after clock is started. |
| */ |
| static void clock_on_callback(const struct device *dev, |
| clock_control_subsys_t subsys, |
| void *user_data) |
| { |
| bool *executed = (bool *)user_data; |
| |
| *executed = true; |
| } |
| |
| static void test_async_on_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| enum clock_control_status status; |
| int err; |
| bool executed = false; |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| err = clock_control_async_on(dev, subsys, clock_on_callback, &executed); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| /* wait for clock started. */ |
| k_busy_wait(startup_us); |
| |
| zassert_true(executed, "%s: Expected flag to be true", dev_name); |
| zassert_equal(CLOCK_CONTROL_STATUS_ON, |
| clock_control_get_status(dev, subsys), |
| "Unexpected clock status"); |
| } |
| |
| static void test_async_on(void) |
| { |
| test_all_instances(test_async_on_instance, async_capable); |
| } |
| |
| /* |
| * Test checks that when asynchronous clock enabling is scheduled but clock |
| * is disabled before being started then callback is never called and error |
| * is reported. |
| */ |
| static void test_async_on_stopped_on_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| enum clock_control_status status; |
| int err; |
| int key; |
| bool executed = false; |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| /* lock to prevent clock interrupt for fast starting clocks.*/ |
| key = irq_lock(); |
| err = clock_control_async_on(dev, subsys, clock_on_callback, &executed); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| /* Attempt to stop clock while it is being started. */ |
| err = clock_control_off(dev, subsys); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| irq_unlock(key); |
| |
| k_busy_wait(10000); |
| |
| zassert_false(executed, "%s: Expected flag to be false", dev_name); |
| } |
| |
| static void test_async_on_stopped(void) |
| { |
| test_all_instances(test_async_on_stopped_on_instance, async_capable); |
| } |
| |
| /* |
| * Test checks that that second start returns error. |
| */ |
| static void test_double_start_on_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| enum clock_control_status status; |
| int err; |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| err = clock_control_on(dev, subsys); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| |
| err = clock_control_on(dev, subsys); |
| zassert_true(err < 0, "%s: Unexpected return value:%d", dev_name, err); |
| } |
| |
| static void test_double_start(void) |
| { |
| test_all_instances(test_double_start_on_instance, NULL); |
| } |
| |
| /* |
| * Test checks that that second stop returns 0. |
| * Test precondition: clock is stopped. |
| */ |
| static void test_double_stop_on_instance(const char *dev_name, |
| clock_control_subsys_t subsys, |
| uint32_t startup_us) |
| { |
| const struct device *dev = device_get_binding(dev_name); |
| enum clock_control_status status; |
| int err; |
| |
| status = clock_control_get_status(dev, subsys); |
| zassert_equal(CLOCK_CONTROL_STATUS_OFF, status, |
| "%s: Unexpected status (%d)", dev_name, status); |
| |
| err = clock_control_off(dev, subsys); |
| zassert_equal(0, err, "%s: Unexpected err (%d)", dev_name, err); |
| } |
| |
| static void test_double_stop(void) |
| { |
| test_all_instances(test_double_stop_on_instance, NULL); |
| } |
| |
| void test_main(void) |
| { |
| ztest_test_suite(test_clock_control, |
| ztest_unit_test(test_on_off_status), |
| ztest_unit_test(test_async_on), |
| ztest_unit_test(test_async_on_stopped), |
| ztest_unit_test(test_double_start), |
| ztest_unit_test(test_double_stop) |
| ); |
| ztest_run_test_suite(test_clock_control); |
| } |