CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
platform-adc.hh
1#pragma once
2#include <debug.hh>
3#include <stdint.h>
4#include <utils.hh>
5
6/**
7 * A simple driver for Sonata's XADC (Xilinx Analogue to Digital Converter).
8 *
9 * Documentation source can be found at:
10 * https://github.com/lowRISC/sonata-system/blob/97a525c48f7bf051b999d0178dba04859819bc5e/doc/ip/adc.md
11 *
12 * Rendered documentation is served from:
13 * https://lowrisc.github.io/sonata-system/doc/ip/adc.html
14 */
16{
17 /**
18 * Flag to set when debugging the driver for UART log messages.
19 */
20 static constexpr bool DebugDriver = false;
21
22 /**
23 * Helper for conditional debug logs and assertions.
24 */
25 using Debug = ConditionalDebug<DebugDriver, "ADC">;
26
27 /**
28 * Results of measurements / analogue conversions are stored in Dynamic
29 * Reconfiguration Port (DRP) status registers as most-significant-bit
30 * justified 12 bit values, represented by this mask.
31 */
32 static constexpr uint16_t MeasurementMask = 0xFFF0;
33
34 /**
35 * The location (offset) of the Xilinx Analogue-to-Digital Converter's
36 * Dynamic Reconfiguration Port (DRP) registers, which are 16-bit registers
37 * that are sequentially mapped to memory in 4-byte (word) intervals, in the
38 * lower 2 bytes of each word. This includes both status (read-only) and
39 * control (read/write) registers.
40 *
41 * https://docs.amd.com/r/en-US/ug480_7Series_XADC/XADC-Register-Interface
42 */
43 enum class RegisterOffset : uint8_t
44 {
45 Temperature = 0x00,
46 VoltageInternalSupply = 0x01,
47 VoltageAuxiliarySupply = 0x02,
48 VoltageDedicated = 0x03,
49 VoltageInternalPositiveReference = 0x04,
50 VoltageInternalNegativeReference = 0x05,
51 VoltageBlockRamSupply = 0x06,
52
53 /* Offset 0x07 is undefined. */
54
55 /* ADC A's calibration coefficient status registers omitted. */
56
57 /* Offsets 0x0B and 0x0C are undefined. */
58
59 /* Zynq-700 SoC-specific voltage status registers omitted. */
60
61 VoltageAuxiliary0 = 0x10,
62 VoltageAuxiliary1 = 0x11,
63 VoltageAuxiliary2 = 0x12,
64 VoltageAuxiliary3 = 0x13,
65 VoltageAuxiliary4 = 0x14,
66 VoltageAuxiliary5 = 0x15,
67 VoltageAuxiliary6 = 0x16,
68 VoltageAuxiliary7 = 0x17,
69 VoltageAuxiliary8 = 0x18,
70 VoltageAuxiliary9 = 0x19,
71 VoltageAuxiliary10 = 0x1A,
72 VoltageAuxiliary11 = 0x1B,
73 VoltageAuxiliary12 = 0x1C,
74 VoltageAuxiliary13 = 0x1D,
75 VoltageAuxiliary14 = 0x1E,
76 VoltageAuxiliary15 = 0x1F,
77
78 /* Maximum & minimum sensor measurement status registers omitted. */
79
80 /* Offsets 0x2B and 0x2F are undefined. */
81
82 /* ADC B's calibration coefficient status registers omitted. */
83
84 /* Offsets 0x33 to 0x3E are undefined. */
85
86 FlagRegister = 0x3F,
87 ConfigRegister0 = 0x40,
88 ConfigRegister1 = 0x41,
89 ConfigRegister2 = 0x42,
90
91 /* 0x43 to 0x47 are factory test registers and so are omitted. */
92
93 /* Sequence and Alarm control registers are emitted. */
94 };
95
96 /**
97 * Definitions of fields (and their locations) within the Xilinx
98 * Analogue-to-digital Converter's Config Register 2 (offset 0x42).
99 *
100 * https://docs.amd.com/r/en-US/ug480_7Series_XADC/Control-Registers?section=XREF_53021_Configuration
101 */
102 enum ConfigRegister2Field : uint16_t
103 {
104 /* Bits 0-3 are invalid and should not be interacted with. */
105
106 /**
107 * Power-down bits for the Analogue-to-Digital Converter.
108 */
109 PowerDownMask = 0x3 << 4,
110
111 /* Bits 6-7 are invalid and should not be interacted with. */
112
113 /**
114 * Bits used to select the division ratio between the Dynamic
115 * Reconfiguration Port clock (DCLK) and the lower frequency
116 * Analogue-to- Digital Converter Clock (ADCCLK). Values of 0 and 1 are
117 * mapped to a divider of 2 by the DCLK divider selection specification.
118 * All other values are mapped identically; the minimum division ratio
119 * is 2.
120 */
121 ClockDividerMask = 0xFF << 8,
122 };
123
124 /**
125 * A helper that returns a pointer to the Analogue-to-Digital Converter
126 * (ADC)'s memory, which is mapped to the Dynamic Reconfiguration Port (DRP)
127 * registers used by the ADC.
128 */
129 [[nodiscard, gnu::always_inline]] volatile uint32_t *registers() const
130 {
131 return MMIO_CAPABILITY(uint32_t, adc);
132 }
133
134 /**
135 * Read the contents of a Dynamic Reconfiguration Port (DRP) register from
136 * memory. DRP registers are 16 bits wide but are mapped sequentially in
137 * memory, using 4 bytes per register, with the relevant data written in the
138 * lower 16 bits.
139 *
140 * The single argument is the register to read the value of.
141 */
142 [[nodiscard]] uint16_t register_read(RegisterOffset reg) const
143 {
144 uint8_t registerOffset = static_cast<uint8_t>(reg);
145 return static_cast<uint16_t>(registers()[registerOffset]);
146 }
147
148 /**
149 * Write to the contents of a Dynamic Reconfiguration Port (DRP) register in
150 * memory. DRP Registers are 16 bits wide but are mapped sequentially in
151 * memory, using 4 bytes per register, with the relevant data written to the
152 * lower 16 bits.
153 *
154 * The first argument is the register to write to.
155 * The second argument is the value to write to the register.
156 */
157 void register_write(RegisterOffset reg, uint16_t value) const
158 {
159 uint8_t registerOffset = static_cast<uint8_t>(reg);
160 registers()[registerOffset] = static_cast<uint32_t>(value);
161 }
162
163 /**
164 * Sets the relevant bits of a Dynamic Reconfiguration Port (DRP) register
165 * in memory. This acts like `register_write`, but additionally takes a
166 * mask so that it only overwrites specified bits of the register, and
167 * retains the value of all unselected bits.
168 *
169 * The first argument is the register to write to.
170 * The second argument is the mask of bits in the register to write to.
171 * The third argument is the values that will be written to the register
172 * according to the mask.
173 */
174 void register_set_bits(RegisterOffset reg,
175 uint16_t bitMask,
176 uint16_t bitValues) const
177 {
178 uint16_t registerBits = register_read(reg);
179 registerBits &= ~bitMask; /* Clear bits in the mask. */
180 registerBits |= bitMask & bitValues; /* Set values of masked bits. */
181 return register_write(reg, registerBits);
182 }
183
184 public:
185 /**
186 * Represents the offsets of the Xilinx Analogue-to-Digital Converter's
187 * (XADC) Dynamic Reconfiguration Port (DRP) status registers that are used
188 * to store values that are measured/sampled by the XADC, forming a mapping
189 * that provides a more comprehensible interface.
190 *
191 * https://lowrisc.github.io/sonata-system/doc/ip/adc.html
192 */
193 enum class MeasurementRegister : uint8_t
194 {
195 ArduinoA0 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary4),
196 ArduinoA1 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary12),
197 ArduinoA2 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary5),
198 ArduinoA3 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary13),
199 ArduinoA4 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary6),
200 ArduinoA5 = static_cast<uint8_t>(RegisterOffset::VoltageAuxiliary14),
201 Temperature = static_cast<uint8_t>(RegisterOffset::Temperature),
202 VoltageInternalSupply =
203 static_cast<uint8_t>(RegisterOffset::VoltageInternalSupply),
204 VoltageAuxiliarySupply =
205 static_cast<uint8_t>(RegisterOffset::VoltageAuxiliarySupply),
206 VoltageInternalReferencePositive = static_cast<uint8_t>(
207 RegisterOffset::VoltageInternalPositiveReference),
208 VoltageInternalReferenceNegative = static_cast<uint8_t>(
209 RegisterOffset::VoltageInternalNegativeReference),
210 VoltageBlockRamSupply =
211 static_cast<uint8_t>(RegisterOffset::VoltageBlockRamSupply),
212 };
213
214 /**
215 * Possible power down modes that can be set in Config Register 2.
216 *
217 * https://docs.amd.com/r/en-US/ug480_7Series_XADC/Control-Registers?section=XREF_93518_Power_Down
218 */
219 enum class PowerDownMode : uint8_t
220 {
221 None = 0b00, /* Default */
222 /* 0b01 is not valid, and should not be selected. */
223 ConverterB = 0b10,
224 BothConverters = 0b11,
225 };
226
227 /**
228 * The Xilinx Analogue-to-Digtal Converter can sample at a maximum rate of 1
229 * Megasample per second. It uses 16 bit Dynamic Reconfiguration Port (DRP)
230 * registers, and stores 12-bit measurements, which are stored most-
231 * significant-bit justified.
232 *
233 * https://docs.amd.com/r/en-US/ug480_7Series_XADC/XADC-Overview
234 */
235 static constexpr size_t MaxSamples = 1 * 1000 * 1000;
236 static constexpr size_t RegisterSize = 16;
237 static constexpr size_t MeasurementBitWidth = 12;
238
239 /**
240 * The minimum permissible Analogue-to-Digital Clock speed is 1 MHz, and the
241 * maximum is 26 MHz, as per the data sheet.
242 *
243 * See table 65 on page 57:
244 * https://docs.amd.com/v/u/en-US/ds181_Artix_7_Data_Sheet
245 */
246 static constexpr size_t MinClockFrequencyHz = 1 * 1000 * 1000;
247 static constexpr size_t MaxClockFrequencyHz = 26 * 1000 * 1000;
248
249 /**
250 * A clock divider value to be used by the Xilinx Analogue-to-Digital
251 * Converter (XADC), which divides its input clock (the system clock)
252 * by this divider to determine the clock of the XADC. This clock
253 * must fall between specified maximum and minimum frequency, and the
254 * divider must be an 8-bit integer, greater than 2.
255 */
256 typedef uint8_t ClockDivider;
257
258 /**
259 * Constructor - initialises the MMIO capability for the Analogue-to-
260 * Digital converter, and then sets its clock divider and power down
261 * mode.
262 *
263 * Takes the clock divider and power down modes to initialise. The
264 * clock divider should divide the system clock to create a signal
265 * between 1 and 26 MHz, and should be at least 2.
266 */
268 PowerDownMode powerDown)
269 {
270 set_clock_divider(divider);
271 set_power_down(powerDown);
272 /* The analogue-to-digital converter starts up in independent ADC mode
273 by default, monitoring all channels, and so no further initialisation
274 logic is needed. */
275 }
276
277 /**
278 * Constructor, without any manual setting of the clock divider. Initialises
279 * the MMIO capability for the Analogue-to-Digtal converter, and then sets
280 * its power down mode.
281 *
282 * Takes the power down mode to initialise.
283 */
285 {
286 set_power_down(powerDown);
287 /* The Analogue-to-digital converter starts up in independent ADC mode
288 by default, monitoring all channels, and so no further initialisation
289 logic is needed. */
290 }
291
292 /**
293 * Sets the clock divider for the Sonata Analogue-to-Digital Converter
294 * (ADC), which is used to divide the system clock to create the ADC clock.
295 *
296 * The clock divider to use is provided as the single argument. It must be
297 * such that it creates a signal between 1 and 26 MHz, and should be at
298 * least 2.
299 */
301 {
302 Debug::Assert((divider >= 2), "The ADC divider must be at least 2");
303 Debug::Assert(
304 (CPU_TIMER_HZ / divider >= MinClockFrequencyHz),
305 "The given divider causes the ADC clock to underclock its minimum.");
306 Debug::Assert(
307 (CPU_TIMER_HZ / divider <= MaxClockFrequencyHz),
308 "The given divider causes the ADC clock to overclock its maximum.");
309 register_set_bits(
310 RegisterOffset::ConfigRegister2, ClockDividerMask, (divider << 8));
311 }
312
313 /**
314 * Sets the power down configuration for the Sonata Analogue-to-Digital
315 * Converter (ADC). Calling this function allows either ADC B or the entire
316 * XADC to be **permanently** powered down.
317 *
318 * The power down mode to set is provided as an argument.
319 */
321 {
322 register_set_bits(RegisterOffset::ConfigRegister2,
323 PowerDownMask,
324 (static_cast<uint16_t>(powerdown) << 4));
325 }
326
327 /**
328 * Reads the most recent output of the analogue-to-digital converter's
329 * measurements from a specified status register, corresponding to
330 * measurement of some channel. This currently assumes unipolar operation,
331 * which means all values are positive.
332 *
333 * The status register to read the last measurement of is given as the
334 * single argument.
335 *
336 * The output values can be translated to the relevant units being measured
337 * by using the transfer functions defined in documentation:
338 * https://docs.amd.com/r/en-US/ug480_7Series_XADC/ADC-Transfer-Functions
339 */
340 [[nodiscard]] int16_t read_last_measurement(MeasurementRegister reg) const
341 {
342 uint16_t measurement = register_read(static_cast<RegisterOffset>(reg));
343 measurement &= MeasurementMask;
344 measurement >>= (RegisterSize - MeasurementBitWidth);
345
346 /* Currently just simple logic that assumes a unipolar analogue
347 measurement: to extend support to bipolar measurement, a mechanism for
348 tracking whether a measurement is bipolar or not must be introduced, and
349 if so, then the negative 12-bit two's complement values must be sign
350 extended first. */
351 return static_cast<int16_t>(measurement);
352 }
353};
SonataAnalogueDigitalConverter(ClockDivider divider, PowerDownMode powerDown)
Constructor - initialises the MMIO capability for the Analogue-to- Digital converter,...
int16_t read_last_measurement(MeasurementRegister reg) const
Reads the most recent output of the analogue-to-digital converter's measurements from a specified sta...
static constexpr size_t MinClockFrequencyHz
The minimum permissible Analogue-to-Digital Clock speed is 1 MHz, and the maximum is 26 MHz,...
PowerDownMode
Possible power down modes that can be set in Config Register 2.
MeasurementRegister
Represents the offsets of the Xilinx Analogue-to-Digital Converter's (XADC) Dynamic Reconfiguration P...
uint8_t ClockDivider
A clock divider value to be used by the Xilinx Analogue-to-Digital Converter (XADC),...
SonataAnalogueDigitalConverter(PowerDownMode powerDown)
Constructor, without any manual setting of the clock divider.
void set_clock_divider(ClockDivider divider)
Sets the clock divider for the Sonata Analogue-to-Digital Converter (ADC), which is used to divide th...
static constexpr size_t MaxSamples
The Xilinx Analogue-to-Digtal Converter can sample at a maximum rate of 1 Megasample per second.
void set_power_down(PowerDownMode powerdown)
Sets the power down configuration for the Sonata Analogue-to-Digital Converter (ADC).
Utility class to delete copy and move contructors.
Definition utils.hh:53
#define MMIO_CAPABILITY(type, name)
Provide a capability of the type volatile type * referring to the MMIO region exported in the linker ...
C++ APIs for assertions, invariants, and writing formatted debug messages to a UART.
Miscellaneous utility functions and classes.