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 
14 static_assert(PWM_PARAM_N_OUTPUTS == 6,
15  "Expected six PWM channels. May need to update `dif_pwm.h`.");
16 static_assert(PWM_CFG_DC_RESN_MASK == 0xf,
17  "Expected duty cycle configuration register to be 4 bits.");
18 
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(
45  config.beats_per_pulse_cycle);
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 
57  dif_pwm_channel_t channel,
58  dif_pwm_channel_config_t config) {
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 
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 
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 
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 
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 
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 
254 dif_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 }