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  } else if (config.mode == kDifPwmModeBlink) {
103  param_reg =
104  bitfield_bit32_write(param_reg, PWM_PWM_PARAM_0_BLINK_EN_0_BIT, true);
105  }
106 
107  // Configure polarity register.
108  uint32_t invert_reg =
109  mmio_region_read32(pwm->base_addr, PWM_INVERT_REG_OFFSET);
110 
111  // Configure blink mode parameter register.
112  uint32_t blink_param_reg = 0;
113  if (config.mode == kDifPwmModeHeartbeat || config.mode == kDifPwmModeBlink) {
114  blink_param_reg = bitfield_field32_write(
115  blink_param_reg, PWM_BLINK_PARAM_0_X_0_FIELD, config.blink_parameter_x);
116  if (config.mode == kDifPwmModeHeartbeat) {
117  if (config.blink_parameter_y >= beats_per_pulse_cycle) {
118  return kDifBadArg;
119  }
120  // Convert "beats" to "phase counter ticks", since this value is added to
121  // the duty cycle (which hardware computes in "phase counter ticks").
122  blink_param_reg = bitfield_field32_write(
123  blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD,
124  phase_cntr_ticks_per_beat * config.blink_parameter_y);
125  } else if (config.mode == kDifPwmModeBlink) {
126  blink_param_reg =
127  bitfield_field32_write(blink_param_reg, PWM_BLINK_PARAM_0_Y_0_FIELD,
128  config.blink_parameter_y);
129  }
130  } else if (config.mode != kDifPwmModeFirmware) {
131  return kDifBadArg;
132  }
133 
134 #define DIF_PWM_CHANNEL_CONFIG_CASE_(channel_) \
135  case kDifPwmChannel##channel_: \
136  invert_reg = bitfield_bit32_write( \
137  invert_reg, PWM_INVERT_INVERT_##channel_##_BIT, config.polarity); \
138  mmio_region_write32(pwm->base_addr, \
139  PWM_DUTY_CYCLE_##channel_##_REG_OFFSET, \
140  duty_cycle_reg); \
141  mmio_region_write32(pwm->base_addr, PWM_PWM_PARAM_##channel_##_REG_OFFSET, \
142  param_reg); \
143  if (config.mode == kDifPwmModeHeartbeat || \
144  config.mode == kDifPwmModeBlink) { \
145  mmio_region_write32(pwm->base_addr, \
146  PWM_BLINK_PARAM_##channel_##_REG_OFFSET, \
147  blink_param_reg); \
148  } \
149  break;
150 
151  switch (channel) {
152  DIF_PWM_CHANNEL_LIST(DIF_PWM_CHANNEL_CONFIG_CASE_)
153  default:
154  return kDifBadArg;
155  }
156 #undef DIF_PWM_CHANNEL_CONFIG_CASE_
157 
158  mmio_region_write32(pwm->base_addr, PWM_INVERT_REG_OFFSET, invert_reg);
159 
160  return kDifOk;
161 }
162 
164  dif_toggle_t enabled) {
165  if (pwm == NULL || !dif_is_valid_toggle(enabled)) {
166  return kDifBadArg;
167  }
168 
169  if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
170  return kDifLocked;
171  }
172 
173  uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET);
174  config_reg = bitfield_bit32_write(config_reg, PWM_CFG_CNTR_EN_BIT,
175  dif_toggle_to_bool(enabled));
176  mmio_region_write32(pwm->base_addr, PWM_CFG_REG_OFFSET, config_reg);
177 
178  return kDifOk;
179 }
180 
182  dif_toggle_t *is_enabled) {
183  if (pwm == NULL || is_enabled == NULL) {
184  return kDifBadArg;
185  }
186 
187  uint32_t config_reg = mmio_region_read32(pwm->base_addr, PWM_CFG_REG_OFFSET);
188  *is_enabled =
189  dif_bool_to_toggle(bitfield_bit32_read(config_reg, PWM_CFG_CNTR_EN_BIT));
190 
191  return kDifOk;
192 }
193 
195  uint32_t channels,
196  dif_toggle_t enabled) {
197  if (pwm == NULL || channels >= (1U << PWM_PARAM_N_OUTPUTS) ||
198  !dif_is_valid_toggle(enabled)) {
199  return kDifBadArg;
200  }
201 
202  if (!mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET)) {
203  return kDifLocked;
204  }
205 
206  uint32_t enable_reg =
207  mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET);
208 
209  if (dif_toggle_to_bool(enabled)) {
210  enable_reg |= channels;
211  } else {
212  enable_reg &= ~channels;
213  }
214 
215  mmio_region_write32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET, enable_reg);
216 
217  return kDifOk;
218 }
219 
221  dif_pwm_channel_t channel,
222  dif_toggle_t *is_enabled) {
223  if (pwm == NULL || is_enabled == NULL) {
224  return kDifBadArg;
225  }
226 
227  uint32_t channel_bit = (uint32_t)bitfield_count_trailing_zeroes32(channel);
228 
229  if (channel_bit >= PWM_PARAM_N_OUTPUTS) {
230  return kDifBadArg;
231  }
232 
233  uint32_t enable_reg =
234  mmio_region_read32(pwm->base_addr, PWM_PWM_EN_REG_OFFSET);
235  *is_enabled =
236  dif_bool_to_toggle(bitfield_bit32_read(enable_reg, channel_bit));
237 
238  return kDifOk;
239 }
240 
242  if (pwm == NULL) {
243  return kDifBadArg;
244  }
245 
246  mmio_region_write32(pwm->base_addr, PWM_REGWEN_REG_OFFSET, 0);
247 
248  return kDifOk;
249 }
250 
251 dif_result_t dif_pwm_is_locked(const dif_pwm_t *pwm, bool *is_locked) {
252  if (pwm == NULL || is_locked == NULL) {
253  return kDifBadArg;
254  }
255 
256  *is_locked =
257  mmio_region_read32(pwm->base_addr, PWM_REGWEN_REG_OFFSET) ? false : true;
258 
259  return kDifOk;
260 }