|
| 1 | +/* |
| 2 | + * Copyright (c) 2022, Andrey Kunitsyn |
| 3 | + * |
| 4 | + * This file is part of the modm project. |
| 5 | + * |
| 6 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 7 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 8 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 9 | + */ |
| 10 | +// ---------------------------------------------------------------------------- |
| 11 | + |
| 12 | +#pragma once |
| 13 | + |
| 14 | +#include <cstdint> |
| 15 | +#include <algorithm> |
| 16 | +#include "../device.hpp" |
| 17 | +#include <hardware/structs/dma.h> |
| 18 | +#include <hardware/regs/dreq.h> |
| 19 | + |
| 20 | +namespace modm::platform |
| 21 | +{ |
| 22 | + |
| 23 | +/// @ingroup modm_platform_dma |
| 24 | +struct DmaBase |
| 25 | +{ |
| 26 | + using IrqHandler = void (*)(void); |
| 27 | + |
| 28 | + enum class Channel : uint32_t |
| 29 | + { |
| 30 | +%% for channel in dma.channel |
| 31 | + Channel{{channel.name}}, |
| 32 | +%% endfor |
| 33 | + }; |
| 34 | + enum class Request : uint32_t |
| 35 | + { |
| 36 | + Pio0_Tx0 = DREQ_PIO0_TX0, |
| 37 | + Pio0_Tx1 = DREQ_PIO0_TX1, |
| 38 | + Pio0_Tx2 = DREQ_PIO0_TX2, |
| 39 | + Pio0_Tx3 = DREQ_PIO0_TX3, |
| 40 | + Pio0_Rx0 = DREQ_PIO0_RX0, |
| 41 | + Pio0_Rx1 = DREQ_PIO0_RX1, |
| 42 | + Pio0_Rx2 = DREQ_PIO0_RX2, |
| 43 | + Pio0_Rx3 = DREQ_PIO0_RX3, |
| 44 | + Pio1_Tx0 = DREQ_PIO1_TX0, |
| 45 | + Pio1_Tx1 = DREQ_PIO1_TX1, |
| 46 | + Pio1_Tx2 = DREQ_PIO1_TX2, |
| 47 | + Pio1_Tx3 = DREQ_PIO1_TX3, |
| 48 | + Pio1_Rx0 = DREQ_PIO1_RX0, |
| 49 | + Pio1_Rx1 = DREQ_PIO1_RX1, |
| 50 | + Pio1_Rx2 = DREQ_PIO1_RX2, |
| 51 | + Pio1_Rx3 = DREQ_PIO1_RX3, |
| 52 | + Spi0_Tx = DREQ_SPI0_TX, |
| 53 | + Spi0_Rx = DREQ_SPI0_RX, |
| 54 | + Spi1_Tx = DREQ_SPI1_TX, |
| 55 | + Spi1_Rx = DREQ_SPI1_RX, |
| 56 | + Uart0_Tx = DREQ_UART0_TX, |
| 57 | + Uart0_Rx = DREQ_UART0_RX, |
| 58 | + Uart1_Tx = DREQ_UART1_TX, |
| 59 | + Uart1_Rx = DREQ_UART1_RX, |
| 60 | + Pwm_Wrap0 = DREQ_PWM_WRAP0, |
| 61 | + Pwm_Wrap1 = DREQ_PWM_WRAP1, |
| 62 | + Pwm_Wrap2 = DREQ_PWM_WRAP2, |
| 63 | + Pwm_Wrap3 = DREQ_PWM_WRAP3, |
| 64 | + Pwm_Wrap4 = DREQ_PWM_WRAP4, |
| 65 | + Pwm_Wrap5 = DREQ_PWM_WRAP5, |
| 66 | + Pwm_Wrap6 = DREQ_PWM_WRAP6, |
| 67 | + Pwm_Wrap7 = DREQ_PWM_WRAP7, |
| 68 | + I2c0_Tx = DREQ_I2C0_TX, |
| 69 | + I2c0_Rx = DREQ_I2C0_RX, |
| 70 | + I2c1_Tx = DREQ_I2C1_TX, |
| 71 | + I2c1_Rx = DREQ_I2C1_RX, |
| 72 | + Adc = DREQ_ADC, |
| 73 | + Xip_Stream = DREQ_XIP_STREAM, |
| 74 | + Xip_SsiTx = DREQ_XIP_SSITX, |
| 75 | + Xip_SsiRx = DREQ_XIP_SSIRX, |
| 76 | + Dma_Timer0 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER0, |
| 77 | + Dma_Timer1 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER1, |
| 78 | + Dma_Timer2 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER2, |
| 79 | + Dma_Timer3 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER3, |
| 80 | + Force = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT, |
| 81 | + }; |
| 82 | + |
| 83 | + enum class |
| 84 | + TransferDataSize : uint32_t |
| 85 | + { |
| 86 | + Byte = 0, |
| 87 | + Bit8 = Byte, |
| 88 | + HalfWord = 1, |
| 89 | + Bit16 = HalfWord, |
| 90 | + Word = 2, |
| 91 | + Bit32 = Word, |
| 92 | + }; |
| 93 | + |
| 94 | + enum class |
| 95 | + Priority : uint32_t |
| 96 | + { |
| 97 | + Normal = 0, |
| 98 | + High = 1, |
| 99 | + }; |
| 100 | +}; |
| 101 | + |
| 102 | +/// @author Andrey Kunitsyn |
| 103 | +/// @ingroup modm_platform_dma |
| 104 | +class DmaController : public DmaBase |
| 105 | +{ |
| 106 | +public: |
| 107 | + /** |
| 108 | + * Start multiple channels simultaneously |
| 109 | + */ |
| 110 | + template <class... Channels> |
| 111 | + static inline void start() { |
| 112 | + dma_hw->multi_channel_trigger = (Channels::mask | ...); |
| 113 | + } |
| 114 | + |
| 115 | + class ChannelDummy |
| 116 | + { |
| 117 | + public: |
| 118 | + static constexpr uint32_t mask = 0; |
| 119 | + static void |
| 120 | + configure(TransferDataSize transferDataSize, |
| 121 | + bool readIncrement, |
| 122 | + bool writeIncrement) { |
| 123 | + (void)transferDataSize; |
| 124 | + (void)readIncrement; |
| 125 | + (void)writeIncrement; |
| 126 | + } |
| 127 | + static void |
| 128 | + start() {} |
| 129 | + static void |
| 130 | + setReadAddress(uintptr_t address) { |
| 131 | + (void)address; |
| 132 | + } |
| 133 | + static void |
| 134 | + setWriteAddress(uintptr_t address) { |
| 135 | + (void)address; |
| 136 | + } |
| 137 | + static void |
| 138 | + setReadIncrementMode(bool increment) { |
| 139 | + (void)increment; |
| 140 | + } |
| 141 | + static void |
| 142 | + setWriteIncrementMode(bool increment) { |
| 143 | + (void)increment; |
| 144 | + } |
| 145 | + static void |
| 146 | + setDataLength(std::size_t length) { |
| 147 | + (void)length; |
| 148 | + } |
| 149 | + |
| 150 | + template <Request dmaRequest> |
| 151 | + static void |
| 152 | + setPeripheralRequest() {} |
| 153 | + static bool isBusy() { return false; } |
| 154 | + }; |
| 155 | + |
| 156 | + /// Class representing a DMA channel/stream |
| 157 | + template <DmaBase::Channel ChannelID> |
| 158 | + class Channel |
| 159 | + { |
| 160 | + public: |
| 161 | + static constexpr DmaBase::Channel name = ChannelID; |
| 162 | + static constexpr uint32_t idx = uint32_t(ChannelID); |
| 163 | + static constexpr uint32_t mask = (1u << idx); |
| 164 | + |
| 165 | + /** |
| 166 | + * Configure the DMA channel |
| 167 | + * |
| 168 | + * Stops the DMA channel and writes the new values to its control register. |
| 169 | + * |
| 170 | + * @param[in] transferDataSize Size of data in transfer (byte, halfword, word) |
| 171 | + * @param[in] readIncrement Defines whether the read address is incremented |
| 172 | + * after a transfer completed |
| 173 | + * @param[in] writeIncrement Defines whether the write address is |
| 174 | + * incremented after a transfer completed |
| 175 | + */ |
| 176 | + static void |
| 177 | + configure(TransferDataSize transferDataSize, bool readIncrement, bool writeIncrement) |
| 178 | + { |
| 179 | + const uint32_t ctrl = |
| 180 | + (uint32_t(transferDataSize) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB) | |
| 181 | + (readIncrement ? DMA_CH0_CTRL_TRIG_INCR_READ_BITS : 0) | |
| 182 | + (writeIncrement ? DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS : 0) | |
| 183 | + (idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB) | |
| 184 | + DMA_CH0_CTRL_TRIG_EN_BITS | |
| 185 | + DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS; |
| 186 | + // write to alt1, dnt start |
| 187 | + dma_hw->ch[idx].al1_ctrl = ctrl; |
| 188 | + } |
| 189 | + |
| 190 | + /// Start the transfer of the DMA channel |
| 191 | + static void |
| 192 | + start() |
| 193 | + { |
| 194 | + dma_hw->multi_channel_trigger = mask; |
| 195 | + } |
| 196 | + |
| 197 | + /// Stop a DMA channel transfer |
| 198 | + static void |
| 199 | + stop() |
| 200 | + { |
| 201 | + dma_hw->abort = mask; |
| 202 | + while (dma_hw->abort & mask) __NOP(); |
| 203 | + } |
| 204 | + |
| 205 | + /** |
| 206 | + * Set the read address of the DMA channel |
| 207 | + * |
| 208 | + * @note In Mem2Mem mode use this method to set the memory source address. |
| 209 | + * |
| 210 | + * @param[in] address Source address |
| 211 | + */ |
| 212 | + static void |
| 213 | + setReadAddress(uintptr_t address) |
| 214 | + { |
| 215 | + dma_hw->ch[idx].read_addr = address; |
| 216 | + } |
| 217 | + |
| 218 | + /** |
| 219 | + * Set the write address of the DMA channel |
| 220 | + * |
| 221 | + * @note In Mem2Mem mode use this method to set the memory destination address. |
| 222 | + * |
| 223 | + * @param[in] address Destination address |
| 224 | + */ |
| 225 | + static void |
| 226 | + setWriteAddress(uintptr_t address) |
| 227 | + { |
| 228 | + dma_hw->ch[idx].write_addr = address; |
| 229 | + } |
| 230 | + |
| 231 | + /** |
| 232 | + * Enable/disable read increment |
| 233 | + * |
| 234 | + * When enabled, the read address is incremented by the size of the data |
| 235 | + * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer |
| 236 | + * completed. |
| 237 | + * |
| 238 | + * @param[in] increment Enable/disable |
| 239 | + */ |
| 240 | + static void |
| 241 | + setReadIncrementMode(bool increment) |
| 242 | + { |
| 243 | + if (increment) { |
| 244 | + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS); |
| 245 | + } else { |
| 246 | + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS); |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + /** |
| 251 | + * Enable/disable write increment |
| 252 | + * |
| 253 | + * When enabled, the write address is incremented by the size of the data |
| 254 | + * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer |
| 255 | + * completed. |
| 256 | + * |
| 257 | + * @param[in] increment Enable/disable |
| 258 | + */ |
| 259 | + static void |
| 260 | + setWriteIncrementMode(bool increment) |
| 261 | + { |
| 262 | + if (increment) { |
| 263 | + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); |
| 264 | + } else { |
| 265 | + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); |
| 266 | + } |
| 267 | + } |
| 268 | + |
| 269 | + /// Set the length of data to be transfered (num transactions, not bytes) |
| 270 | + static void |
| 271 | + setDataLength(std::size_t length) |
| 272 | + { |
| 273 | + dma_hw->ch[idx].transfer_count = length; |
| 274 | + } |
| 275 | + |
| 276 | + /// Set the peripheral that operates the channel |
| 277 | + template <Request dmaRequest> |
| 278 | + static void |
| 279 | + setPeripheralRequest() |
| 280 | + { |
| 281 | + hw_write_masked(&dma_hw->ch[idx].al1_ctrl, |
| 282 | + (uint32_t(dmaRequest) << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB), |
| 283 | + DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS); |
| 284 | + } |
| 285 | + |
| 286 | + /// Enable the specified interrupt of the channel |
| 287 | + static void |
| 288 | + enableInterrupt() |
| 289 | + { |
| 290 | + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); |
| 291 | + } |
| 292 | + |
| 293 | + /// Disable the specified interrupt of the channel |
| 294 | + static void |
| 295 | + disableInterrupt() |
| 296 | + { |
| 297 | + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); |
| 298 | + } |
| 299 | + |
| 300 | + static inline bool isBusy() |
| 301 | + { |
| 302 | + return dma_hw->ch[idx].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS; |
| 303 | + } |
| 304 | + |
| 305 | + static inline bool isCompleted() |
| 306 | + { |
| 307 | + return !isBusy(); |
| 308 | + } |
| 309 | + |
| 310 | + static inline void startWrite(const void* src,std::size_t count) |
| 311 | + { |
| 312 | + dma_hw->ch[idx].read_addr = (uint32_t)src; |
| 313 | + dma_hw->ch[idx].al1_transfer_count_trig = count; |
| 314 | + } |
| 315 | + |
| 316 | + static inline void startRead(void* dst,std::size_t count) |
| 317 | + { |
| 318 | + dma_hw->ch[idx].write_addr = (uint32_t)dst; |
| 319 | + dma_hw->ch[idx].al1_transfer_count_trig = count; |
| 320 | + } |
| 321 | + |
| 322 | + template <class OtherChannel> |
| 323 | + static inline void chainTo() |
| 324 | + { |
| 325 | + hw_write_masked(&dma_hw->ch[idx].al1_ctrl, |
| 326 | + (OtherChannel::idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB), |
| 327 | + DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS); |
| 328 | + } |
| 329 | + }; |
| 330 | +}; |
| 331 | + |
| 332 | +/** |
| 333 | + * Derive DMA controller classes for convenience. Every derived class defines |
| 334 | + * the channels available on that controller. |
| 335 | + * @ingroup modm_platform_dma |
| 336 | + */ |
| 337 | +class Dma: public DmaController |
| 338 | +{ |
| 339 | +public: |
| 340 | +%% for channel in dma.channel |
| 341 | + using Channel{{ channel.name }} = DmaController::Channel<DmaBase::Channel::Channel{{ channel.name }}>; |
| 342 | +%% endfor |
| 343 | +}; |
| 344 | + |
| 345 | +} // namespace modm::platform |
0 commit comments