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