| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/dif/dif_pwm.h" |
| |
| #include <assert.h> |
| |
| #include "sw/device/lib/base/bitfield.h" |
| #include "sw/device/lib/dif/dif_base.h" |
| |
| #include "pwm_regs.h" // Generated. |
| |
| static_assert(PWM_PARAM_N_OUTPUTS == 6, |
| "Expected six PWM channels. May need to update `dif_pwm.h`."); |
| static_assert(PWM_CFG_DC_RESN_MASK == 0xf, |
| "Expected duty cycle configuration register to be 4 bits."); |
| |
| dif_result_t dif_pwm_configure(const dif_pwm_t *pwm, dif_pwm_config_t config) { |
| if (pwm == NULL || config.clock_divisor > PWM_CFG_CLK_DIV_MASK || |
| config.beats_per_pulse_cycle < 2 || |
| config.beats_per_pulse_cycle > (1U << 16)) { |
| return kDifBadArg; |
| } |
| |
| if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) { |
| return kDifLocked; |
| } |
| |
| // We do a read-modify-write to restore the enablement state of the phase |
| // counter enablement bit, after temporarily disabling it to update the |
| // other configuration register fields. |
| uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET); |
| config_reg = bitfield_field32_write(config_reg, PWM_CFG_CLK_DIV_FIELD, |
| config.clock_divisor); |
| |
| // Since `beats_per_pulse_cycle` = 2 ^ (`duty_cycle_resolution` + 1), we can |
| // compute the duty cycle resolution by: |
| // |
| // `DC_RESN` = log2(`beats_per_pulse_cycle`) - 1 |
| // |
| // To compute the log2, we can find the index of the most-significant 1-bit, |
| // the lastly, substract 1. |
| uint32_t dc_resn_val = |
| 30 - bitfield_count_leading_zeroes32(config.beats_per_pulse_cycle); |
| config_reg = |
| bitfield_field32_write(config_reg, PWM_CFG_DC_RESN_FIELD, dc_resn_val); |
| |
| // Clear the phase counter enable bit before updating the register. |
| mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, 0); |
| mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, config_reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_configure_channel(const dif_pwm_t *pwm, |
| dif_pwm_channel_t channel, |
| dif_pwm_channel_config_t config) { |
| if (pwm == NULL || (config.polarity != kDifPwmPolarityActiveHigh && |
| config.polarity != kDifPwmPolarityActiveLow)) { |
| return kDifBadArg; |
| } |
| |
| if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) { |
| return kDifLocked; |
| } |
| |
| // Configure duty cycle register. |
| |
| // Get "beats_per_pulse_cycle" from the PWM config register. |
| // There are 2 ^ (`duty_cycle_resolution` + 1) "beats" per "pulse cycle". |
| uint8_t duty_cycle_resolution = bitfield_field32_read( |
| mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET), |
| PWM_CFG_DC_RESN_FIELD); |
| uint32_t beats_per_pulse_cycle = 1U << (duty_cycle_resolution + 1); |
| |
| // Check duty cycle and phase delay configurations. |
| if (config.duty_cycle_a >= beats_per_pulse_cycle || |
| config.duty_cycle_b >= beats_per_pulse_cycle || |
| config.phase_delay >= beats_per_pulse_cycle) { |
| return kDifBadArg; |
| } |
| |
| // There are 2 ^ 16 "phase counter ticks" in one "pulse cycle", and therefore |
| // 2 ^ (16 - `duty_cycle_resolution` - 1) "phase counter ticks" in one "beat". |
| uint16_t phase_cntr_ticks_per_beat = 1U << (16 - duty_cycle_resolution - 1); |
| uint32_t duty_cycle_reg = |
| bitfield_field32_write(0, PWM_DUTY_CYCLE_0_A_0_FIELD, |
| phase_cntr_ticks_per_beat * config.duty_cycle_a); |
| duty_cycle_reg = |
| bitfield_field32_write(duty_cycle_reg, PWM_DUTY_CYCLE_0_B_0_FIELD, |
| phase_cntr_ticks_per_beat * config.duty_cycle_b); |
| |
| // Configure parameter register. |
| uint32_t param_reg = |
| bitfield_field32_write(0, PWM_PWM_PARAM_0_PHASE_DELAY_0_FIELD, |
| phase_cntr_ticks_per_beat * config.phase_delay); |
| if (config.mode == kDifPwmModeHeartbeat) { |
| param_reg = |
| bitfield_bit32_write(param_reg, PWM_PWM_PARAM_0_HTBT_EN_0_BIT, true); |
| } else if (config.mode == kDifPwmModeBlink) { |
| param_reg = |
| bitfield_bit32_write(param_reg, PWM_PWM_PARAM_0_BLINK_EN_0_BIT, true); |
| } |
| |
| // Configure polarity register. |
| uint32_t invert_reg = |
| mmio_region_read32(pwm->base_addr, PWM_INVERT_REG_OFFSET); |
| |
| // Configure blink mode parameter register. |
| uint32_t blink_param_reg = 0; |
| if (config.mode == kDifPwmModeHeartbeat || config.mode == kDifPwmModeBlink) { |
| blink_param_reg = bitfield_field32_write( |
| blink_param_reg, PWM_BLINK_PARAM_0_X_0_FIELD, config.blink_parameter_x); |
| if (config.mode == kDifPwmModeHeartbeat) { |
| if (config.blink_parameter_y >= beats_per_pulse_cycle) { |
| return kDifBadArg; |
| } |
| // Convert "beats" to "phase counter ticks", since this value is added to |
| // the duty cycle (which hardware computes in "phase counter ticks"). |
| blink_param_reg = bitfield_field32_write( |
| blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD, |
| phase_cntr_ticks_per_beat * config.blink_parameter_y); |
| } else if (config.mode == kDifPwmModeBlink) { |
| blink_param_reg = |
| bitfield_field32_write(blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD, |
| config.blink_parameter_y); |
| } |
| } else if (config.mode != kDifPwmModeFirmware) { |
| return kDifBadArg; |
| } |
| |
| #define DIF_PWM_CHANNEL_CONFIG_CASE_(channel_) \ |
| case kDifPwmChannel##channel_: \ |
| invert_reg = bitfield_bit32_write( \ |
| invert_reg, PWM_INVERT_INVERT_##channel_##_BIT, config.polarity); \ |
| mmio_region_write32(pwm->base_addr, \ |
| PWM_DUTY_CYCLE_##channel_##_REG_OFFSET, \ |
| duty_cycle_reg); \ |
| mmio_region_write32(pwm->base_addr, PWM_PWM_PARAM_##channel_##_REG_OFFSET, \ |
| param_reg); \ |
| if (config.mode == kDifPwmModeHeartbeat || \ |
| config.mode == kDifPwmModeBlink) { \ |
| mmio_region_write32(pwm->base_addr, \ |
| PWM_BLINK_PARAM_##channel_##_REG_OFFSET, \ |
| blink_param_reg); \ |
| } \ |
| break; |
| |
| switch (channel) { |
| DIF_PWM_CHANNEL_LIST(DIF_PWM_CHANNEL_CONFIG_CASE_) |
| default: |
| return kDifBadArg; |
| } |
| #undef DIF_PWM_CHANNEL_CONFIG_CASE_ |
| |
| mmio_region_write32(pwm->base_addr, PWM_INVERT_REG_OFFSET, invert_reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_phase_cntr_set_enabled(const dif_pwm_t *pwm, |
| dif_toggle_t enabled) { |
| if (pwm == NULL || !dif_is_valid_toggle(enabled)) { |
| return kDifBadArg; |
| } |
| |
| if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) { |
| return kDifLocked; |
| } |
| |
| uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET); |
| config_reg = bitfield_bit32_write(config_reg, PWM_CFG_CNTR_EN_BIT, |
| dif_toggle_to_bool(enabled)); |
| mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, config_reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_phase_cntr_get_enabled(const dif_pwm_t *pwm, |
| dif_toggle_t *is_enabled) { |
| if (pwm == NULL || is_enabled == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET); |
| *is_enabled = |
| dif_bool_to_toggle(bitfield_bit32_read(config_reg, PWM_CFG_CNTR_EN_BIT)); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_channel_set_enabled(const dif_pwm_t *pwm, |
| uint32_t channels, |
| dif_toggle_t enabled) { |
| if (pwm == NULL || channels >= (1U << PWM_PARAM_N_OUTPUTS) || |
| !dif_is_valid_toggle(enabled)) { |
| return kDifBadArg; |
| } |
| |
| if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) { |
| return kDifLocked; |
| } |
| |
| uint32_t enable_reg = |
| mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET); |
| |
| if (dif_toggle_to_bool(enabled)) { |
| enable_reg |= channels; |
| } else { |
| enable_reg &= ~channels; |
| } |
| |
| mmio_region_write32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET, enable_reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_channel_get_enabled(const dif_pwm_t *pwm, |
| dif_pwm_channel_t channel, |
| dif_toggle_t *is_enabled) { |
| if (pwm == NULL || is_enabled == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t channel_bit = bitfield_count_trailing_zeroes32(channel); |
| |
| if (channel_bit >= PWM_PARAM_N_OUTPUTS) { |
| return kDifBadArg; |
| } |
| |
| uint32_t enable_reg = |
| mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET); |
| *is_enabled = |
| dif_bool_to_toggle(bitfield_bit32_read(enable_reg, channel_bit)); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_lock(const dif_pwm_t *pwm) { |
| if (pwm == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_write32(pwm->base_addr, PWM_REGWEN_REG_OFFSET, 0); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_pwm_is_locked(const dif_pwm_t *pwm, bool *is_locked) { |
| if (pwm == NULL || is_locked == NULL) { |
| return kDifBadArg; |
| } |
| |
| *is_locked = |
| mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET) ? false : true; |
| |
| return kDifOk; |
| } |