| /* |
| * Copyright 2023 Google LLC |
| * Copyright lowRISC contributors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| #include "sw/device/lib/dif/dif_clkmgr.h" |
| |
| #include <assert.h> |
| |
| #include "sw/device/lib/base/bitfield.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/base/multibits.h" |
| #include "sw/device/lib/dif/dif_base.h" |
| |
| #include "clkmgr_regs.h" // Generated |
| |
| // TODO: For the moment, CLKMGR_PARAM_NUM_SW_GATEABLE_CLOCKS has to be <= than |
| // 32, as we only support one enable register for gateable clocks. |
| // https://github.com/lowRISC/opentitan/issues/4201 |
| static_assert( |
| CLKMGR_PARAM_NUM_SW_GATEABLE_CLOCKS <= CLKMGR_PARAM_REG_WIDTH, |
| "Expected the number of gateable clocks to be <= the width of a CSR."); |
| |
| // TODO: For the moment, CLKMGR_PARAM_NUM_HINTABLE_CLOCKS has to be <= than |
| // 32, as we only support one enable/hint_status register for hintable clocks. |
| // https://github.com/lowRISC/opentitan/issues/4201 |
| static_assert( |
| CLKMGR_PARAM_NUM_HINTABLE_CLOCKS <= CLKMGR_PARAM_REG_WIDTH, |
| "Expected the number of hintable clocks to be <= the width of a CSR."); |
| |
| static bool clkmgr_valid_gateable_clock(dif_clkmgr_gateable_clock_t clock) { |
| return clock < CLKMGR_PARAM_NUM_SW_GATEABLE_CLOCKS; |
| } |
| |
| static bool clkmgr_valid_hintable_clock(dif_clkmgr_hintable_clock_t clock) { |
| return clock < CLKMGR_PARAM_NUM_HINTABLE_CLOCKS; |
| } |
| |
| static bool clkmgr_measure_ctrl_regwen(const dif_clkmgr_t *clkmgr) { |
| uint32_t measure_ctrl_regwen_val = mmio_region_read32( |
| clkmgr->base_addr, CLKMGR_MEASURE_CTRL_REGWEN_REG_OFFSET); |
| return bitfield_bit32_read(measure_ctrl_regwen_val, |
| CLKMGR_MEASURE_CTRL_REGWEN_EN_BIT); |
| } |
| |
| dif_result_t dif_clkmgr_external_clock_is_settled(const dif_clkmgr_t *clkmgr, |
| bool *status) { |
| if (clkmgr == NULL || status == NULL) { |
| return kDifBadArg; |
| } |
| uint32_t extclk_status_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_EXTCLK_STATUS_REG_OFFSET); |
| *status = bitfield_field32_read(extclk_status_val, |
| CLKMGR_EXTCLK_STATUS_ACK_FIELD) == |
| kMultiBitBool4True; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_jitter_get_enabled(const dif_clkmgr_t *clkmgr, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| |
| multi_bit_bool_t clk_jitter_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_JITTER_ENABLE_REG_OFFSET); |
| // The documentation states that kMultiBitBool4False disables the jittery |
| // clock and all other values enable the jittery clock. |
| *state = clk_jitter_val != kMultiBitBool4False; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_jitter_set_enabled(const dif_clkmgr_t *clkmgr, |
| dif_toggle_t new_state) { |
| multi_bit_bool_t new_jitter_enable_val; |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| |
| switch (new_state) { |
| case kDifToggleEnabled: |
| new_jitter_enable_val = kMultiBitBool4True; |
| break; |
| case kDifToggleDisabled: |
| new_jitter_enable_val = kMultiBitBool4False; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_JITTER_ENABLE_REG_OFFSET, |
| new_jitter_enable_val); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_gateable_clock_get_enabled( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_gateable_clock_t clock, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL || !clkmgr_valid_gateable_clock(clock)) { |
| return kDifBadArg; |
| } |
| |
| uint32_t clk_enables_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_CLK_ENABLES_REG_OFFSET); |
| *state = dif_bool_to_toggle(bitfield_bit32_read(clk_enables_val, clock)); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_gateable_clock_set_enabled( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_gateable_clock_t clock, |
| dif_toggle_t new_state) { |
| if (clkmgr == NULL || !clkmgr_valid_gateable_clock(clock) || |
| !dif_is_valid_toggle(new_state)) { |
| return kDifBadArg; |
| } |
| |
| bool new_clk_enables_bit = dif_toggle_to_bool(new_state); |
| uint32_t clk_enables_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_CLK_ENABLES_REG_OFFSET); |
| clk_enables_val = |
| bitfield_bit32_write(clk_enables_val, clock, new_clk_enables_bit); |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_CLK_ENABLES_REG_OFFSET, |
| clk_enables_val); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_hintable_clock_get_enabled( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_hintable_clock_t clock, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL || !clkmgr_valid_hintable_clock(clock)) { |
| return kDifBadArg; |
| } |
| |
| uint32_t clk_hints_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_CLK_HINTS_STATUS_REG_OFFSET); |
| *state = dif_bool_to_toggle(bitfield_bit32_read(clk_hints_val, clock)); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_hintable_clock_set_hint( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_hintable_clock_t clock, |
| dif_toggle_t new_state) { |
| if (clkmgr == NULL || !clkmgr_valid_hintable_clock(clock) || |
| !dif_is_valid_toggle(new_state)) { |
| return kDifBadArg; |
| } |
| |
| bool new_clk_hints_bit = dif_toggle_to_bool(new_state); |
| uint32_t clk_hints_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_CLK_HINTS_REG_OFFSET); |
| clk_hints_val = bitfield_bit32_write(clk_hints_val, clock, new_clk_hints_bit); |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_CLK_HINTS_REG_OFFSET, |
| clk_hints_val); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_hintable_clock_get_hint( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_hintable_clock_t clock, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL || !clkmgr_valid_hintable_clock(clock)) { |
| return kDifBadArg; |
| } |
| |
| uint32_t clk_hints_val = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_CLK_HINTS_REG_OFFSET); |
| *state = dif_bool_to_toggle(bitfield_bit32_read(clk_hints_val, clock)); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_external_clock_set_enabled(const dif_clkmgr_t *clkmgr, |
| bool is_low_speed) { |
| uint32_t extclk_ctrl_reg = 0; |
| |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| |
| extclk_ctrl_reg = bitfield_field32_write( |
| extclk_ctrl_reg, CLKMGR_EXTCLK_CTRL_SEL_FIELD, kMultiBitBool4True); |
| extclk_ctrl_reg = bitfield_field32_write( |
| extclk_ctrl_reg, CLKMGR_EXTCLK_CTRL_HI_SPEED_SEL_FIELD, |
| is_low_speed ? kMultiBitBool4False : kMultiBitBool4True); |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_EXTCLK_CTRL_REG_OFFSET, |
| extclk_ctrl_reg); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_measure_ctrl_disable(const dif_clkmgr_t *clkmgr) { |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_MEASURE_CTRL_REGWEN_REG_OFFSET, |
| 0); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_measure_ctrl_get_enable(const dif_clkmgr_t *clkmgr, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| *state = dif_bool_to_toggle(clkmgr_measure_ctrl_regwen(clkmgr)); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_enable_measure_counts(const dif_clkmgr_t *clkmgr, |
| dif_clkmgr_measure_clock_t clock, |
| uint32_t lo_threshold, |
| uint32_t hi_threshold) { |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| if (!clkmgr_measure_ctrl_regwen(clkmgr)) { |
| return kDifLocked; |
| } |
| |
| uint32_t en_offset; |
| uint32_t reg_offset; |
| bitfield_field32_t en_field; |
| bitfield_field32_t lo_field; |
| bitfield_field32_t hi_field; |
| switch (clock) { |
| #define PICK_COUNT_CTRL_FIELDS(kind_) \ |
| en_offset = CLKMGR_##kind_##_MEAS_CTRL_EN_REG_OFFSET; \ |
| reg_offset = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_REG_OFFSET; \ |
| en_field = CLKMGR_##kind_##_MEAS_CTRL_EN_EN_FIELD; \ |
| lo_field = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_LO_FIELD; \ |
| hi_field = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_HI_FIELD; \ |
| break // No semicolon to force semicolon below. |
| case kDifClkmgrMeasureClockAudio: |
| PICK_COUNT_CTRL_FIELDS(AUDIO); |
| case kDifClkmgrMeasureClockIo: |
| PICK_COUNT_CTRL_FIELDS(IO); |
| case kDifClkmgrMeasureClockIoDiv2: |
| PICK_COUNT_CTRL_FIELDS(IO_DIV2); |
| case kDifClkmgrMeasureClockIoDiv4: |
| PICK_COUNT_CTRL_FIELDS(IO_DIV4); |
| case kDifClkmgrMeasureClockMain: |
| PICK_COUNT_CTRL_FIELDS(MAIN); |
| case kDifClkmgrMeasureClockMl: |
| PICK_COUNT_CTRL_FIELDS(ML); |
| case kDifClkmgrMeasureClockSmc: |
| PICK_COUNT_CTRL_FIELDS(SMC); |
| case kDifClkmgrMeasureClockUsb: |
| PICK_COUNT_CTRL_FIELDS(USB); |
| case kDifClkmgrMeasureClockVideo: |
| PICK_COUNT_CTRL_FIELDS(VIDEO); |
| default: |
| return kDifBadArg; |
| #undef PICK_COUNT_CTRL_FIELDS |
| } |
| |
| uint32_t measure_en_reg = 0; |
| measure_en_reg = |
| bitfield_field32_write(measure_en_reg, en_field, kMultiBitBool4True); |
| mmio_region_write32(clkmgr->base_addr, en_offset, measure_en_reg); |
| |
| uint32_t measure_ctrl_reg = 0; |
| measure_ctrl_reg = |
| bitfield_field32_write(measure_ctrl_reg, lo_field, lo_threshold); |
| measure_ctrl_reg = |
| bitfield_field32_write(measure_ctrl_reg, hi_field, hi_threshold); |
| // Two writes, because these registers are shadowed. |
| mmio_region_write32(clkmgr->base_addr, reg_offset, measure_ctrl_reg); |
| mmio_region_write32(clkmgr->base_addr, reg_offset, measure_ctrl_reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_disable_measure_counts( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_measure_clock_t clock) { |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| if (!clkmgr_measure_ctrl_regwen(clkmgr)) { |
| return kDifLocked; |
| } |
| |
| uint32_t en_offset; |
| switch (clock) { |
| case kDifClkmgrMeasureClockAudio: |
| en_offset = CLKMGR_AUDIO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIo: |
| en_offset = CLKMGR_IO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIoDiv2: |
| en_offset = CLKMGR_IO_DIV2_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIoDiv4: |
| en_offset = CLKMGR_IO_DIV4_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockMain: |
| en_offset = CLKMGR_MAIN_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockMl: |
| en_offset = CLKMGR_ML_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockSmc: |
| en_offset = CLKMGR_SMC_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockUsb: |
| en_offset = CLKMGR_USB_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockVideo: |
| en_offset = CLKMGR_VIDEO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| mmio_region_write32(clkmgr->base_addr, en_offset, kMultiBitBool4False); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_measure_counts_get_enable( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_measure_clock_t clock, |
| dif_toggle_t *state) { |
| if (clkmgr == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t en_offset; |
| switch (clock) { |
| case kDifClkmgrMeasureClockAudio: |
| en_offset = CLKMGR_AUDIO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIo: |
| en_offset = CLKMGR_IO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIoDiv2: |
| en_offset = CLKMGR_IO_DIV2_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockIoDiv4: |
| en_offset = CLKMGR_IO_DIV4_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockMain: |
| en_offset = CLKMGR_MAIN_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockMl: |
| en_offset = CLKMGR_ML_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockSmc: |
| en_offset = CLKMGR_SMC_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockUsb: |
| en_offset = CLKMGR_USB_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| case kDifClkmgrMeasureClockVideo: |
| en_offset = CLKMGR_VIDEO_MEAS_CTRL_EN_REG_OFFSET; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| multi_bit_bool_t en_val = mmio_region_read32(clkmgr->base_addr, en_offset); |
| *state = dif_multi_bit_bool_to_toggle(en_val); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_measure_counts_get_thresholds( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_measure_clock_t clock, |
| uint32_t *min_threshold, uint32_t *max_threshold) { |
| if (clkmgr == NULL || min_threshold == NULL || max_threshold == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t reg_offset; |
| bitfield_field32_t lo_field; |
| bitfield_field32_t hi_field; |
| switch (clock) { |
| #define PICK_THRESHOLD_FIELDS(kind_) \ |
| reg_offset = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_REG_OFFSET; \ |
| lo_field = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_LO_FIELD; \ |
| hi_field = CLKMGR_##kind_##_MEAS_CTRL_SHADOWED_HI_FIELD; \ |
| break // No semicolon to force semicolon below. |
| case kDifClkmgrMeasureClockAudio: |
| PICK_THRESHOLD_FIELDS(AUDIO); |
| case kDifClkmgrMeasureClockIo: |
| PICK_THRESHOLD_FIELDS(IO); |
| case kDifClkmgrMeasureClockIoDiv2: |
| PICK_THRESHOLD_FIELDS(IO_DIV2); |
| case kDifClkmgrMeasureClockIoDiv4: |
| PICK_THRESHOLD_FIELDS(IO_DIV4); |
| case kDifClkmgrMeasureClockMain: |
| PICK_THRESHOLD_FIELDS(MAIN); |
| case kDifClkmgrMeasureClockMl: |
| PICK_THRESHOLD_FIELDS(ML); |
| case kDifClkmgrMeasureClockSmc: |
| PICK_THRESHOLD_FIELDS(SMC); |
| case kDifClkmgrMeasureClockUsb: |
| PICK_THRESHOLD_FIELDS(USB); |
| case kDifClkmgrMeasureClockVideo: |
| PICK_THRESHOLD_FIELDS(VIDEO); |
| default: |
| return kDifBadArg; |
| #undef PICK_THRESHOLD_FIELDS |
| } |
| uint32_t thresholds_val = mmio_region_read32(clkmgr->base_addr, reg_offset); |
| *min_threshold = bitfield_field32_read(thresholds_val, lo_field); |
| *max_threshold = bitfield_field32_read(thresholds_val, hi_field); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_recov_err_code_get_codes( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_recov_err_codes_t *codes) { |
| if (clkmgr == NULL || codes == NULL) { |
| return kDifBadArg; |
| } |
| *codes = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_RECOV_ERR_CODE_REG_OFFSET); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_recov_err_code_clear_codes( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_recov_err_codes_t codes) { |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| mmio_region_write32(clkmgr->base_addr, CLKMGR_RECOV_ERR_CODE_REG_OFFSET, |
| codes); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_fatal_err_code_get_codes( |
| const dif_clkmgr_t *clkmgr, dif_clkmgr_fatal_err_codes_t *codes) { |
| if (clkmgr == NULL || codes == NULL) { |
| return kDifBadArg; |
| } |
| *codes = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_FATAL_ERR_CODE_REG_OFFSET); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_clkmgr_wait_for_ext_clk_switch(const dif_clkmgr_t *clkmgr) { |
| if (clkmgr == NULL) { |
| return kDifBadArg; |
| } |
| uint32_t ext_status; |
| do { |
| ext_status = |
| mmio_region_read32(clkmgr->base_addr, CLKMGR_EXTCLK_STATUS_REG_OFFSET); |
| } while (ext_status != kMultiBitBool4True); |
| return kDifOk; |
| } |