diff --git a/drivers/reset/CMakeLists.txt b/drivers/reset/CMakeLists.txt index 84f0443cabb5..e94d51e2d2aa 100644 --- a/drivers/reset/CMakeLists.txt +++ b/drivers/reset/CMakeLists.txt @@ -12,3 +12,4 @@ zephyr_library_sources_ifdef(CONFIG_RESET_INTEL_SOCFPGA reset_intel_socfpga.c) zephyr_library_sources_ifdef(CONFIG_RESET_NPCX reset_npcx.c) zephyr_library_sources_ifdef(CONFIG_RESET_NXP_SYSCON reset_lpc_syscon.c) zephyr_library_sources_ifdef(CONFIG_RESET_NXP_RSTCTL reset_nxp_rstctl.c) +zephyr_library_sources_ifdef(CONFIG_RESET_MMIO reset_mmio.c) diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 87ba9c7576ca..10e34c47f0b4 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -36,5 +36,6 @@ rsource "Kconfig.intel_socfpga" rsource "Kconfig.npcx" rsource "Kconfig.lpc_syscon" rsource "Kconfig.nxp_rstctl" +rsource "Kconfig.mmio" endif # RESET diff --git a/drivers/reset/Kconfig.mmio b/drivers/reset/Kconfig.mmio new file mode 100644 index 000000000000..dc1c88a233d0 --- /dev/null +++ b/drivers/reset/Kconfig.mmio @@ -0,0 +1,11 @@ +# MMIO reset driver configuration options + +# Copyright (c) 2025 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config RESET_MMIO + bool "Generic MMIO reset driver" + default y + depends on DT_HAS_RESET_MMIO_ENABLED + help + This option enables the generic MMIO reset driver. This is meant for IPs with a single memory mapped reset bit required to take them out of reset. diff --git a/drivers/reset/reset_mmio.c b/drivers/reset/reset_mmio.c new file mode 100644 index 000000000000..033e8e00c667 --- /dev/null +++ b/drivers/reset/reset_mmio.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2025 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +struct reset_mmio_dev_config { + uint32_t base; + uint8_t num_resets; + bool active_low; +}; + +struct reset_mmio_dev_data { + struct k_spinlock lock; +}; + +static inline int reset_mmio_status(const struct device *dev, uint32_t id, uint8_t *status) +{ + const struct reset_mmio_dev_config *config = dev->config; + uint32_t value; + + if (id >= config->num_resets) { + return -EINVAL; + } + + value = sys_read32(config->base); + *status = FIELD_GET(BIT(id), value); + + /* If active low, invert the logic */ + if (config->active_low) { + *status = !(*status); + } + + return 0; +} + +static inline int reset_mmio_update(const struct device *dev, uint32_t id, uint8_t assert) +{ + const struct reset_mmio_dev_config *config = dev->config; + struct reset_mmio_dev_data *data = dev->data; + uint32_t value; + bool set; + + if (id >= config->num_resets) { + return -EINVAL; + } + + /* If active low, invert the logic */ + set = config->active_low ? !assert : assert; + + K_SPINLOCK(&data->lock) { + value = sys_read32(config->base); + + if (set) { + value |= BIT(id); + } else { + value &= ~BIT(id); + } + + sys_write32(value, config->base); + } + + return 0; +} + +static int reset_mmio_line_assert(const struct device *dev, uint32_t id) +{ + return reset_mmio_update(dev, id, 1); +} + +static int reset_mmio_line_deassert(const struct device *dev, uint32_t id) +{ + return reset_mmio_update(dev, id, 0); +} + +static int reset_mmio_line_toggle(const struct device *dev, uint32_t id) +{ + uint8_t reset_status = 0; + int status; + + status = reset_mmio_status(dev, id, &reset_status); + if (status) { + return status; + } + + return reset_mmio_update(dev, id, !reset_status); +} + +static DEVICE_API(reset, reset_mmio_driver_api) = { + .status = reset_mmio_status, + .line_assert = reset_mmio_line_assert, + .line_deassert = reset_mmio_line_deassert, + .line_toggle = reset_mmio_line_toggle, +}; + +#define DT_DRV_COMPAT reset_mmio +#define RESET_MMIO_INIT(n) \ + BUILD_ASSERT(DT_INST_PROP(n, num_resets) > 0 && DT_INST_PROP(n, num_resets) < 32, \ + "num-resets needs to be in [1, 31]."); \ + static const struct reset_mmio_dev_config reset_mmio_dev_config_##n = { \ + .base = DT_INST_REG_ADDR(n), \ + .num_resets = DT_INST_PROP(n, num_resets), \ + .active_low = DT_INST_PROP(n, active_low)}; \ + static struct reset_mmio_dev_data reset_mmio_dev_data_##n; \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, &reset_mmio_dev_data_##n, &reset_mmio_dev_config_##n, \ + POST_KERNEL, CONFIG_RESET_INIT_PRIORITY, &reset_mmio_driver_api); +DT_INST_FOREACH_STATUS_OKAY(RESET_MMIO_INIT) diff --git a/dts/bindings/reset/reset-mmio.yaml b/dts/bindings/reset/reset-mmio.yaml new file mode 100644 index 000000000000..ab3688394829 --- /dev/null +++ b/dts/bindings/reset/reset-mmio.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2025 Google, LLC +# SPDX-License-Identifier: Apache-2.0 + +description: | + Generic MMIO Reset driver for devices with a single memory mapped reset bit + required to take them out of reset. + +compatible: "reset-mmio" + +include: [base.yaml] + +properties: + reg: + required: true + num-resets: + type: int + required: true + description: | + Number of resets controlled by the register. + Can be in the range [1, 31]. + active-low: + description: Reset is active in low state. + type: boolean diff --git a/tests/drivers/reset/mmio/CMakeLists.txt b/tests/drivers/reset/mmio/CMakeLists.txt new file mode 100644 index 000000000000..b012e9582d31 --- /dev/null +++ b/tests/drivers/reset/mmio/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(reset_mmio) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/reset/mmio/boards/qemu_cortex_m3.overlay b/tests/drivers/reset/mmio/boards/qemu_cortex_m3.overlay new file mode 100644 index 000000000000..04be3315bd2d --- /dev/null +++ b/tests/drivers/reset/mmio/boards/qemu_cortex_m3.overlay @@ -0,0 +1,14 @@ +/ { + reset0: reset@20000004 { + compatible = "reset-mmio"; + reg = <0x20000004 0x4>; + num-resets = <16>; + }; + + reset1: reset@20000008 { + compatible = "reset-mmio"; + reg = <0x20000008 0x4>; + num-resets = <16>; + active-low; + }; +}; diff --git a/tests/drivers/reset/mmio/prj.conf b/tests/drivers/reset/mmio/prj.conf new file mode 100644 index 000000000000..4a544c4c6622 --- /dev/null +++ b/tests/drivers/reset/mmio/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_RESET=y +CONFIG_RESET_MMIO=y diff --git a/tests/drivers/reset/mmio/src/main.c b/tests/drivers/reset/mmio/src/main.c new file mode 100644 index 000000000000..29df00f0abf6 --- /dev/null +++ b/tests/drivers/reset/mmio/src/main.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +ZTEST_SUITE(reset_mmio_tests, NULL, NULL, NULL, NULL, NULL); + +#define RESET_MAX_NUM 16 + +void check_status(const struct device *dev, uint32_t base, uint32_t id, bool expected_state, + bool active_low) +{ + uint8_t actual_state; + + zassert_ok(reset_status(dev, id, &actual_state), "Failed getting reset state"); + zassert_equal(actual_state, expected_state, + "reset state %u doesn't match expected state %u", actual_state, + expected_state); + zassert_equal(FIELD_GET(BIT(id), sys_read32(base)), active_low ^ expected_state); +} + +/* Tests that the reset driver assert functionality is correct for active + * low devices. + */ +ZTEST(reset_mmio_tests, test_reset_mmio_assert_active_low) +{ + const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(reset1)); + uint32_t base = DT_REG_ADDR(DT_NODELABEL(reset1)); + bool active_low = true; + uint8_t i; + + for (i = 0; i < RESET_MAX_NUM; i++) { + reset_line_deassert(dev, i); + check_status(dev, base, i, false, active_low); + /* Check idempotency */ + reset_line_deassert(dev, i); + check_status(dev, base, i, false, active_low); + + /* Check ressserting resets */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + /* Check idempotency */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + } +} + +/* Tests that the reset driver assert functionality is correct for active + * high devices. + */ +ZTEST(reset_mmio_tests, test_reset_mmio_assert_active_high) +{ + const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(reset0)); + uint32_t base = DT_REG_ADDR(DT_NODELABEL(reset0)); + bool active_low = false; + uint8_t i; + + for (i = 0; i < RESET_MAX_NUM; i++) { + reset_line_deassert(dev, i); + check_status(dev, base, i, false, active_low); + /* Check idempotency */ + reset_line_deassert(dev, i); + check_status(dev, base, i, false, active_low); + + /* Check ressserting resets */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + /* Check idempotency */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + } +} + +/* Tests that the reset driver toggle functionality is correct for active + * low devices + */ +ZTEST(reset_mmio_tests, test_reset_mmio_toggle_active_low) +{ + const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(reset1)); + uint32_t base = DT_REG_ADDR(DT_NODELABEL(reset1)); + bool active_low = true; + uint8_t i; + + for (i = 0; i < RESET_MAX_NUM; i++) { + /* Begin by making sure the reset is asserted */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + reset_line_toggle(dev, i); + check_status(dev, base, i, false, active_low); + reset_line_toggle(dev, i); + check_status(dev, base, i, true, active_low); + } +} + +/* Tests that the reset driver toggle functionality is correct for active + * high devices + */ +ZTEST(reset_mmio_tests, test_reset_mmio_toggle_active_high) +{ + const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(reset0)); + uint32_t base = DT_REG_ADDR(DT_NODELABEL(reset0)); + bool active_low = false; + uint8_t i; + + for (i = 0; i < RESET_MAX_NUM; i++) { + /* Begin by making sure the reset is asserted */ + reset_line_assert(dev, i); + check_status(dev, base, i, true, active_low); + reset_line_toggle(dev, i); + check_status(dev, base, i, false, active_low); + reset_line_toggle(dev, i); + check_status(dev, base, i, true, active_low); + } +} + +/* Tests that the reset driver rejects out of bounds bits */ +ZTEST(reset_mmio_tests, test_reset_mmio_oob) +{ + const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(reset0)); + uint8_t i, status; + + for (i = RESET_MAX_NUM; i < 32; i++) { + zassert_not_ok(reset_line_assert(dev, i)); + zassert_not_ok(reset_line_deassert(dev, i)); + zassert_not_ok(reset_status(dev, i, &status)); + zassert_not_ok(reset_line_toggle(dev, i)); + } +} diff --git a/tests/drivers/reset/mmio/testcase.yaml b/tests/drivers/reset/mmio/testcase.yaml new file mode 100644 index 000000000000..6dbc4de530aa --- /dev/null +++ b/tests/drivers/reset/mmio/testcase.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.reset.mmio: + platform_allow: + - qemu_cortex_m3