Software APIs
dif_pwm.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
7#include <assert.h>
8
11
12#include "pwm_regs.h" // Generated.
13
14static_assert(PWM_PARAM_N_OUTPUTS == 6,
15 "Expected six PWM channels. May need to update `dif_pwm.h`.");
16static_assert(PWM_CFG_DC_RESN_MASK == 0xf,
17 "Expected duty cycle configuration register to be 4 bits.");
18
19dif_result_t dif_pwm_configure(const dif_pwm_t *pwm, dif_pwm_config_t config) {
20 if (pwm == NULL || config.clock_divisor > PWM_CFG_CLK_DIV_MASK ||
21 config.beats_per_pulse_cycle < 2 ||
22 config.beats_per_pulse_cycle > (1U << 16)) {
23 return kDifBadArg;
24 }
25
26 if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
27 return kDifLocked;
28 }
29
30 // We do a read-modify-write to restore the enablement state of the phase
31 // counter enablement bit, after temporarily disabling it to update the
32 // other configuration register fields.
33 uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET);
34 config_reg = bitfield_field32_write(config_reg, PWM_CFG_CLK_DIV_FIELD,
35 config.clock_divisor);
36
37 // Since `beats_per_pulse_cycle` = 2 ^ (`duty_cycle_resolution` + 1), we can
38 // compute the duty cycle resolution by:
39 //
40 // `DC_RESN` = log2(`beats_per_pulse_cycle`) - 1
41 //
42 // To compute the log2, we can find the index of the most-significant 1-bit,
43 // the lastly, subtract 1.
44 uint32_t dc_resn_val = 30u - (uint32_t)bitfield_count_leading_zeroes32(
46 config_reg =
47 bitfield_field32_write(config_reg, PWM_CFG_DC_RESN_FIELD, dc_resn_val);
48
49 // Clear the phase counter enable bit before updating the register.
50 mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, 0);
51 mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, config_reg);
52
53 return kDifOk;
54}
55
56dif_result_t dif_pwm_configure_channel(const dif_pwm_t *pwm,
57 dif_pwm_channel_t channel,
59 if (pwm == NULL || (config.polarity != kDifPwmPolarityActiveHigh &&
61 return kDifBadArg;
62 }
63
64 if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
65 return kDifLocked;
66 }
67
68 // Configure duty cycle register.
69
70 // Get "beats_per_pulse_cycle" from the PWM config register.
71 // There are 2 ^ (`duty_cycle_resolution` + 1) "beats" per "pulse cycle".
72 uint8_t duty_cycle_resolution = (uint8_t)bitfield_field32_read(
73 mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET),
74 PWM_CFG_DC_RESN_FIELD);
75 uint32_t beats_per_pulse_cycle = 1U << (duty_cycle_resolution + 1);
76
77 // Check duty cycle and phase delay configurations.
78 if (config.duty_cycle_a >= beats_per_pulse_cycle ||
79 config.duty_cycle_b >= beats_per_pulse_cycle ||
80 config.phase_delay >= beats_per_pulse_cycle) {
81 return kDifBadArg;
82 }
83
84 // There are 2 ^ 16 "phase counter ticks" in one "pulse cycle", and therefore
85 // 2 ^ (16 - `duty_cycle_resolution` - 1) "phase counter ticks" in one "beat".
86 uint16_t phase_cntr_ticks_per_beat =
87 (uint16_t)(1 << (16 - duty_cycle_resolution - 1));
88 uint32_t duty_cycle_reg =
89 bitfield_field32_write(0, PWM_DUTY_CYCLE_0_A_0_FIELD,
90 phase_cntr_ticks_per_beat * config.duty_cycle_a);
91 duty_cycle_reg =
92 bitfield_field32_write(duty_cycle_reg, PWM_DUTY_CYCLE_0_B_0_FIELD,
93 phase_cntr_ticks_per_beat * config.duty_cycle_b);
94
95 // Configure parameter register.
96 uint32_t param_reg =
97 bitfield_field32_write(0, PWM_PWM_PARAM_0_PHASE_DELAY_0_FIELD,
98 phase_cntr_ticks_per_beat * config.phase_delay);
99 if (config.mode == kDifPwmModeHeartbeat) {
100 param_reg =
101 bitfield_bit32_write(param_reg, PWM_PWM_PARAM_0_HTBT_EN_0_BIT, true);
102 }
103 // Blink behavior is modified by the heartbeat enable and to use heartbeat
104 // mode we must therefore enable blinking too.
105 if (config.mode == kDifPwmModeBlink || config.mode == kDifPwmModeHeartbeat) {
106 param_reg =
107 bitfield_bit32_write(param_reg, PWM_PWM_PARAM_0_BLINK_EN_0_BIT, true);
108 }
109
110 // Configure polarity register.
111 uint32_t invert_reg =
112 mmio_region_read32(pwm->base_addr, PWM_INVERT_REG_OFFSET);
113
114 // Configure blink mode parameter register.
115 uint32_t blink_param_reg = 0;
116 if (config.mode == kDifPwmModeHeartbeat || config.mode == kDifPwmModeBlink) {
117 blink_param_reg = bitfield_field32_write(
118 blink_param_reg, PWM_BLINK_PARAM_0_X_0_FIELD, config.blink_parameter_x);
119 if (config.mode == kDifPwmModeHeartbeat) {
120 if (config.blink_parameter_y >= beats_per_pulse_cycle) {
121 return kDifBadArg;
122 }
123 // Convert "beats" to "phase counter ticks", since this value is added to
124 // the duty cycle (which hardware computes in "phase counter ticks").
125 blink_param_reg = bitfield_field32_write(
126 blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD,
127 phase_cntr_ticks_per_beat * config.blink_parameter_y);
128 } else if (config.mode == kDifPwmModeBlink) {
129 blink_param_reg =
130 bitfield_field32_write(blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD,
131 config.blink_parameter_y);
132 }
133 } else if (config.mode != kDifPwmModeFirmware) {
134 return kDifBadArg;
135 }
136
137#define DIF_PWM_CHANNEL_CONFIG_CASE_(channel_) \
138 case kDifPwmChannel##channel_: \
139 invert_reg = bitfield_bit32_write( \
140 invert_reg, PWM_INVERT_INVERT_##channel_##_BIT, config.polarity); \
141 mmio_region_write32(pwm->base_addr, \
142 PWM_DUTY_CYCLE_##channel_##_REG_OFFSET, \
143 duty_cycle_reg); \
144 mmio_region_write32(pwm->base_addr, PWM_PWM_PARAM_##channel_##_REG_OFFSET, \
145 param_reg); \
146 if (config.mode == kDifPwmModeHeartbeat || \
147 config.mode == kDifPwmModeBlink) { \
148 mmio_region_write32(pwm->base_addr, \
149 PWM_BLINK_PARAM_##channel_##_REG_OFFSET, \
150 blink_param_reg); \
151 } \
152 break;
153
154 switch (channel) {
155 DIF_PWM_CHANNEL_LIST(DIF_PWM_CHANNEL_CONFIG_CASE_)
156 default:
157 return kDifBadArg;
158 }
159#undef DIF_PWM_CHANNEL_CONFIG_CASE_
160
161 mmio_region_write32(pwm->base_addr, PWM_INVERT_REG_OFFSET, invert_reg);
162
163 return kDifOk;
164}
165
166dif_result_t dif_pwm_phase_cntr_set_enabled(const dif_pwm_t *pwm,
167 dif_toggle_t enabled) {
168 if (pwm == NULL || !dif_is_valid_toggle(enabled)) {
169 return kDifBadArg;
170 }
171
172 if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
173 return kDifLocked;
174 }
175
176 uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET);
177 config_reg = bitfield_bit32_write(config_reg, PWM_CFG_CNTR_EN_BIT,
178 dif_toggle_to_bool(enabled));
179 mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, config_reg);
180
181 return kDifOk;
182}
183
184dif_result_t dif_pwm_phase_cntr_get_enabled(const dif_pwm_t *pwm,
185 dif_toggle_t *is_enabled) {
186 if (pwm == NULL || is_enabled == NULL) {
187 return kDifBadArg;
188 }
189
190 uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET);
191 *is_enabled =
192 dif_bool_to_toggle(bitfield_bit32_read(config_reg, PWM_CFG_CNTR_EN_BIT));
193
194 return kDifOk;
195}
196
197dif_result_t dif_pwm_channel_set_enabled(const dif_pwm_t *pwm,
198 uint32_t channels,
199 dif_toggle_t enabled) {
200 if (pwm == NULL || channels >= (1U << PWM_PARAM_N_OUTPUTS) ||
201 !dif_is_valid_toggle(enabled)) {
202 return kDifBadArg;
203 }
204
205 if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
206 return kDifLocked;
207 }
208
209 uint32_t enable_reg =
210 mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET);
211
212 if (dif_toggle_to_bool(enabled)) {
213 enable_reg |= channels;
214 } else {
215 enable_reg &= ~channels;
216 }
217
218 mmio_region_write32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET, enable_reg);
219
220 return kDifOk;
221}
222
223dif_result_t dif_pwm_channel_get_enabled(const dif_pwm_t *pwm,
224 dif_pwm_channel_t channel,
225 dif_toggle_t *is_enabled) {
226 if (pwm == NULL || is_enabled == NULL) {
227 return kDifBadArg;
228 }
229
230 uint32_t channel_bit = (uint32_t)bitfield_count_trailing_zeroes32(channel);
231
232 if (channel_bit >= PWM_PARAM_N_OUTPUTS) {
233 return kDifBadArg;
234 }
235
236 uint32_t enable_reg =
237 mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET);
238 *is_enabled =
239 dif_bool_to_toggle(bitfield_bit32_read(enable_reg, channel_bit));
240
241 return kDifOk;
242}
243
244dif_result_t dif_pwm_lock(const dif_pwm_t *pwm) {
245 if (pwm == NULL) {
246 return kDifBadArg;
247 }
248
249 mmio_region_write32(pwm->base_addr, PWM_REGWEN_REG_OFFSET, 0);
250
251 return kDifOk;
252}
253
254dif_result_t dif_pwm_is_locked(const dif_pwm_t *pwm, bool *is_locked) {
255 if (pwm == NULL || is_locked == NULL) {
256 return kDifBadArg;
257 }
258
259 *is_locked =
260 mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET) ? false : true;
261
262 return kDifOk;
263}