Fri, 13 Aug 2010 10:43:05 +0100
Initial commit, DMAC version 3.1
| philpem@0 | 1 | /**************************************************************************** |
| philpem@0 | 2 | ** |
| philpem@0 | 3 | ** Name: MicoDMA.c |
| philpem@0 | 4 | ** |
| philpem@0 | 5 | ** Description: |
| philpem@0 | 6 | ** Implements functions for manipulating LatticeMico32 DMA |
| philpem@0 | 7 | ** |
| philpem@0 | 8 | ** Revision: 3.0 |
| philpem@0 | 9 | ** |
| philpem@0 | 10 | ** Disclaimer: |
| philpem@0 | 11 | ** |
| philpem@0 | 12 | ** This source code is intended as a design reference which |
| philpem@0 | 13 | ** illustrates how these types of functions can be implemented. It |
| philpem@0 | 14 | ** is the user's responsibility to verify their design for |
| philpem@0 | 15 | ** consistency and functionality through the use of formal |
| philpem@0 | 16 | ** verification methods. Lattice Semiconductor provides no warranty |
| philpem@0 | 17 | ** regarding the use or functionality of this code. |
| philpem@0 | 18 | ** |
| philpem@0 | 19 | ** -------------------------------------------------------------------- |
| philpem@0 | 20 | ** |
| philpem@0 | 21 | ** Lattice Semiconductor Corporation |
| philpem@0 | 22 | ** 5555 NE Moore Court |
| philpem@0 | 23 | ** Hillsboro, OR 97214 |
| philpem@0 | 24 | ** U.S.A |
| philpem@0 | 25 | ** |
| philpem@0 | 26 | ** TEL: 1-800-Lattice (USA and Canada) |
| philpem@0 | 27 | ** (503)268-8001 (other locations) |
| philpem@0 | 28 | ** |
| philpem@0 | 29 | ** web: http://www.latticesemi.com |
| philpem@0 | 30 | ** email: techsupport@latticesemi.com |
| philpem@0 | 31 | ** |
| philpem@0 | 32 | ** -------------------------------------------------------------------------- |
| philpem@0 | 33 | ** |
| philpem@0 | 34 | ** Change History (Latest changes on top) |
| philpem@0 | 35 | ** |
| philpem@0 | 36 | ** Ver Date Description |
| philpem@0 | 37 | ** -------------------------------------------------------------------------- |
| philpem@0 | 38 | ** |
| philpem@0 | 39 | ** 3.0 Mar-25-2008 Added Header |
| philpem@0 | 40 | ** |
| philpem@0 | 41 | **--------------------------------------------------------------------------- |
| philpem@0 | 42 | *****************************************************************************/ |
| philpem@0 | 43 | |
| philpem@0 | 44 | |
| philpem@0 | 45 | #include "MicoDMA.h" |
| philpem@0 | 46 | #include "MicoDMAService.h" |
| philpem@0 | 47 | #include "MicoInterrupts.h" |
| philpem@0 | 48 | |
| philpem@0 | 49 | |
| philpem@0 | 50 | #ifdef __cplusplus |
| philpem@0 | 51 | extern "C" { |
| philpem@0 | 52 | #endif |
| philpem@0 | 53 | |
| philpem@0 | 54 | #define MICODMA_CTX_DMA_PAUSED (0x00000001) |
| philpem@0 | 55 | |
| philpem@0 | 56 | |
| philpem@0 | 57 | /* program a DMA operation based on the descriptor contents */ |
| philpem@0 | 58 | static void MicoDMA_Program(unsigned int DMABase, DMADesc_t *desc) |
| philpem@0 | 59 | { |
| philpem@0 | 60 | volatile MicoDMA_t *dma = (volatile MicoDMA_t *)DMABase; |
| philpem@0 | 61 | |
| philpem@0 | 62 | /* program the source-address */ |
| philpem@0 | 63 | dma->sAddr = desc->sAddr; |
| philpem@0 | 64 | |
| philpem@0 | 65 | /* program the destination-address */ |
| philpem@0 | 66 | dma->dAddr = desc->dAddr; |
| philpem@0 | 67 | |
| philpem@0 | 68 | /* program length */ |
| philpem@0 | 69 | dma->len = desc->reserved; |
| philpem@0 | 70 | |
| philpem@0 | 71 | /* program type */ |
| philpem@0 | 72 | dma->control = desc->type; |
| philpem@0 | 73 | |
| philpem@0 | 74 | /* start DMA */ |
| philpem@0 | 75 | dma->status = (MICODMA_STATUS_IE|MICODMA_STATUS_START); |
| philpem@0 | 76 | |
| philpem@0 | 77 | /* all done */ |
| philpem@0 | 78 | return; |
| philpem@0 | 79 | } |
| philpem@0 | 80 | |
| philpem@0 | 81 | |
| philpem@0 | 82 | /* handles interrupts from the DMA device */ |
| philpem@0 | 83 | static void MicoDMA_ISR(unsigned int isrCtx, void *data) |
| philpem@0 | 84 | { |
| philpem@0 | 85 | volatile unsigned int iStatus; |
| philpem@0 | 86 | DMADesc_t *desc; |
| philpem@0 | 87 | MicoDMACtx_t *ctx; |
| philpem@0 | 88 | volatile MicoDMA_t *dma; |
| philpem@0 | 89 | |
| philpem@0 | 90 | ctx = (MicoDMACtx_t *)data; |
| philpem@0 | 91 | dma = (volatile MicoDMA_t *)ctx->base; |
| philpem@0 | 92 | desc = (DMADesc_t *)ctx->pHead; |
| philpem@0 | 93 | |
| philpem@0 | 94 | |
| philpem@0 | 95 | /* read the status: this acknowledges device's interrupt */ |
| philpem@0 | 96 | iStatus = dma->status; |
| philpem@0 | 97 | |
| philpem@0 | 98 | /* if this is a spurious interrupt, ignore it */ |
| philpem@0 | 99 | if(desc == 0){ |
| philpem@0 | 100 | return; |
| philpem@0 | 101 | } |
| philpem@0 | 102 | |
| philpem@0 | 103 | /* set the head-descriptor's status accordingly */ |
| philpem@0 | 104 | desc->state = ((iStatus & MICODMA_STATUS_SUCCESS) == 0)?MICODMA_STATE_SUCCESS: |
| philpem@0 | 105 | MICODMA_STATE_ERROR; |
| philpem@0 | 106 | |
| philpem@0 | 107 | /* fetch the next descriptor */ |
| philpem@0 | 108 | if(desc->next == desc){ |
| philpem@0 | 109 | /* this was the last descriptor in the list */ |
| philpem@0 | 110 | ctx->pHead = 0; |
| philpem@0 | 111 | }else{ |
| philpem@0 | 112 | /* there are more descriptors queued up */ |
| philpem@0 | 113 | desc->prev->next = desc->next; |
| philpem@0 | 114 | desc->next->prev = desc->prev; |
| philpem@0 | 115 | ctx->pHead = (void *)desc->next; |
| philpem@0 | 116 | } |
| philpem@0 | 117 | |
| philpem@0 | 118 | /* invoke the callback routine if there's one specified */ |
| philpem@0 | 119 | if(desc->onCompletion) |
| philpem@0 | 120 | desc->onCompletion(desc, desc->state); |
| philpem@0 | 121 | |
| philpem@0 | 122 | |
| philpem@0 | 123 | /* |
| philpem@0 | 124 | * If pause-flag isn't set, go ahead and start |
| philpem@0 | 125 | * the next dma transaction |
| philpem@0 | 126 | */ |
| philpem@0 | 127 | if( ((ctx->flags & MICODMA_CTX_DMA_PAUSED)==0) && ctx->pHead) |
| philpem@0 | 128 | MicoDMA_Program(ctx->base, (DMADesc_t *)ctx->pHead); |
| philpem@0 | 129 | |
| philpem@0 | 130 | |
| philpem@0 | 131 | /* all done */ |
| philpem@0 | 132 | return; |
| philpem@0 | 133 | } |
| philpem@0 | 134 | |
| philpem@0 | 135 | |
| philpem@0 | 136 | #ifdef __cplusplus |
| philpem@0 | 137 | } |
| philpem@0 | 138 | #endif |
| philpem@0 | 139 | |
| philpem@0 | 140 | |
| philpem@0 | 141 | /* initialization routine */ |
| philpem@0 | 142 | void MicoDMAInit(MicoDMACtx_t *ctx) |
| philpem@0 | 143 | { |
| philpem@0 | 144 | /* |
| philpem@0 | 145 | * Unfortunately, we cannot "stop" the DMA if it was already |
| philpem@0 | 146 | * running.. |
| philpem@0 | 147 | */ |
| philpem@0 | 148 | volatile int i; |
| philpem@0 | 149 | volatile MicoDMA_t *dma = (volatile MicoDMA_t *)ctx->base; |
| philpem@0 | 150 | |
| philpem@0 | 151 | |
| philpem@0 | 152 | i = dma->status; |
| philpem@0 | 153 | dma->status = 0; |
| philpem@0 | 154 | |
| philpem@0 | 155 | |
| philpem@0 | 156 | /* set to "no flags" */ |
| philpem@0 | 157 | ctx->flags = 0; |
| philpem@0 | 158 | |
| philpem@0 | 159 | |
| philpem@0 | 160 | /* compute max-length based on the length-register width */ |
| philpem@0 | 161 | if(ctx->maxLength > 0){ |
| philpem@0 | 162 | i = ctx->maxLength; |
| philpem@0 | 163 | ctx->maxLength = 1; |
| philpem@0 | 164 | do{ |
| philpem@0 | 165 | ctx->maxLength += ctx->maxLength; |
| philpem@0 | 166 | }while(--i > 0); |
| philpem@0 | 167 | ctx->maxLength--; |
| philpem@0 | 168 | } |
| philpem@0 | 169 | |
| philpem@0 | 170 | |
| philpem@0 | 171 | /* initialize descriptor to null */ |
| philpem@0 | 172 | ctx->pCurr = 0; |
| philpem@0 | 173 | ctx->pHead = 0; |
| philpem@0 | 174 | |
| philpem@0 | 175 | |
| philpem@0 | 176 | /* register ISR for this DMA */ |
| philpem@0 | 177 | MicoRegisterISR(ctx->irq, (void *)ctx, MicoDMA_ISR); |
| philpem@0 | 178 | |
| philpem@0 | 179 | |
| philpem@0 | 180 | /* Register this DMA as an available device for the lookup service */ |
| philpem@0 | 181 | ctx->lookupReg.name = ctx->name; |
| philpem@0 | 182 | ctx->lookupReg.deviceType = "DMADevice"; |
| philpem@0 | 183 | ctx->lookupReg.priv = ctx; |
| philpem@0 | 184 | MicoRegisterDevice( &(ctx->lookupReg) ); |
| philpem@0 | 185 | |
| philpem@0 | 186 | |
| philpem@0 | 187 | /* all done */ |
| philpem@0 | 188 | return; |
| philpem@0 | 189 | } |
| philpem@0 | 190 | |
| philpem@0 | 191 | |
| philpem@0 | 192 | /* queue a new descriptor */ |
| philpem@0 | 193 | unsigned int MicoDMAQueueRequest(MicoDMACtx_t *ctx, DMADesc_t *desc, DMACallback_t callback) |
| philpem@0 | 194 | { |
| philpem@0 | 195 | int byteLength; |
| philpem@0 | 196 | DMADesc_t *pPrevTail, *pHead; |
| philpem@0 | 197 | |
| philpem@0 | 198 | |
| philpem@0 | 199 | /* make sure descriptor and context are valid */ |
| philpem@0 | 200 | if((ctx == 0) || (desc == 0)) |
| philpem@0 | 201 | return(MICODMA_ERR_INVALID_POINTERS); |
| philpem@0 | 202 | |
| philpem@0 | 203 | |
| philpem@0 | 204 | /* convert read/write length into byte-length */ |
| philpem@0 | 205 | byteLength = desc->length; |
| philpem@0 | 206 | if(desc->type & DMA_16BIT_TRANSFER){ |
| philpem@0 | 207 | byteLength += byteLength; /* left-shift by 1 to multiply by 2 for 16-bit transfer */ |
| philpem@0 | 208 | } else if(desc->type & DMA_32BIT_TRANSFER) { |
| philpem@0 | 209 | byteLength += byteLength; /* left-shift by 1 to multiply by 2 */ |
| philpem@0 | 210 | byteLength += byteLength; /* left-shift by 1 to multiply by 2, again */ |
| philpem@0 | 211 | } |
| philpem@0 | 212 | |
| philpem@0 | 213 | |
| philpem@0 | 214 | |
| philpem@0 | 215 | /* make sure length of transaction is okay */ |
| philpem@0 | 216 | if((byteLength > ctx->maxLength) || (byteLength == 0)) |
| philpem@0 | 217 | return(MICODMA_ERR_DESC_LEN_ERR); |
| philpem@0 | 218 | |
| philpem@0 | 219 | |
| philpem@0 | 220 | /* save the new byte-length */ |
| philpem@0 | 221 | desc->reserved = byteLength; |
| philpem@0 | 222 | |
| philpem@0 | 223 | |
| philpem@0 | 224 | /* prepare descriptor */ |
| philpem@0 | 225 | desc->state = MICODMA_STATE_PENDING; |
| philpem@0 | 226 | desc->onCompletion = callback; |
| philpem@0 | 227 | |
| philpem@0 | 228 | |
| philpem@0 | 229 | /* disable this DMA's interrupt */ |
| philpem@0 | 230 | MicoDisableInterrupt(ctx->irq); |
| philpem@0 | 231 | |
| philpem@0 | 232 | |
| philpem@0 | 233 | /* attach to this DMA's descriptor list */ |
| philpem@0 | 234 | if(ctx->pHead == 0){ |
| philpem@0 | 235 | ctx->pHead = (void *)desc; |
| philpem@0 | 236 | desc->next = desc; |
| philpem@0 | 237 | desc->prev = desc; |
| philpem@0 | 238 | |
| philpem@0 | 239 | /* |
| philpem@0 | 240 | * Since this is the first element in the list, |
| philpem@0 | 241 | * kick-off the DMA, if we're not paused |
| philpem@0 | 242 | */ |
| philpem@0 | 243 | if((ctx->flags & MICODMA_CTX_DMA_PAUSED)==0) |
| philpem@0 | 244 | MicoDMA_Program(ctx->base, desc); |
| philpem@0 | 245 | |
| philpem@0 | 246 | }else{ |
| philpem@0 | 247 | pHead = (DMADesc_t *)(ctx->pHead); |
| philpem@0 | 248 | pPrevTail = pHead->prev; |
| philpem@0 | 249 | |
| philpem@0 | 250 | pPrevTail->next = desc; |
| philpem@0 | 251 | pHead->prev = desc; |
| philpem@0 | 252 | desc->next = pHead; |
| philpem@0 | 253 | desc->prev = pPrevTail; |
| philpem@0 | 254 | } |
| philpem@0 | 255 | |
| philpem@0 | 256 | /* reenable this DMA's interrupts */ |
| philpem@0 | 257 | MicoEnableInterrupt(ctx->irq); |
| philpem@0 | 258 | |
| philpem@0 | 259 | return(0); |
| philpem@0 | 260 | } |
| philpem@0 | 261 | |
| philpem@0 | 262 | |
| philpem@0 | 263 | /* dequeue an existing descriptor */ |
| philpem@0 | 264 | unsigned int MicoDMADequeueRequest(MicoDMACtx_t *ctx, DMADesc_t *desc, unsigned int callback) |
| philpem@0 | 265 | { |
| philpem@0 | 266 | DMADesc_t *pHead, *pTail; |
| philpem@0 | 267 | |
| philpem@0 | 268 | /* cannot dequeue a request if it is in a state other than pending */ |
| philpem@0 | 269 | if((ctx == 0) || (desc == 0) || (desc->next == 0) || (desc->prev == 0)) |
| philpem@0 | 270 | return(MICODMA_ERR_INVALID_POINTERS); |
| philpem@0 | 271 | |
| philpem@0 | 272 | |
| philpem@0 | 273 | /* disable interrupts */ |
| philpem@0 | 274 | MicoDisableInterrupt(ctx->irq); |
| philpem@0 | 275 | |
| philpem@0 | 276 | if(desc->state != MICODMA_STATE_PENDING){ |
| philpem@0 | 277 | |
| philpem@0 | 278 | /* cannot dequeue a request that isn't "pending" */ |
| philpem@0 | 279 | MicoEnableInterrupt(ctx->irq); |
| philpem@0 | 280 | return(MICODMA_ERR_DESCRIPTOR_NOT_PENDING); |
| philpem@0 | 281 | |
| philpem@0 | 282 | }else{ |
| philpem@0 | 283 | /* |
| philpem@0 | 284 | * mark this descriptor as non-pending and |
| philpem@0 | 285 | * remove it from the queue |
| philpem@0 | 286 | */ |
| philpem@0 | 287 | desc->state = MICODMA_STATE_ABORTED; |
| philpem@0 | 288 | if(desc->next != desc){ |
| philpem@0 | 289 | /* this isn't the only descriptor in the list */ |
| philpem@0 | 290 | pHead = (DMADesc_t *)ctx->pHead; |
| philpem@0 | 291 | pTail = pHead->prev; |
| philpem@0 | 292 | |
| philpem@0 | 293 | /* adjust pointers */ |
| philpem@0 | 294 | desc->prev->next = desc->next; |
| philpem@0 | 295 | desc->next->prev = desc->prev; |
| philpem@0 | 296 | |
| philpem@0 | 297 | /* if this was the head, readjust the head */ |
| philpem@0 | 298 | if(pHead == desc) |
| philpem@0 | 299 | pHead = desc->next; |
| philpem@0 | 300 | }else{ |
| philpem@0 | 301 | /* this was the only descriptor */ |
| philpem@0 | 302 | ctx->pHead = 0; |
| philpem@0 | 303 | } |
| philpem@0 | 304 | desc->prev = 0; |
| philpem@0 | 305 | desc->next = 0; |
| philpem@0 | 306 | |
| philpem@0 | 307 | /* call the callback with 'abort status' if requested */ |
| philpem@0 | 308 | if(callback != 0){ |
| philpem@0 | 309 | (desc->onCompletion)(desc, MICODMA_STATE_ABORTED); |
| philpem@0 | 310 | } |
| philpem@0 | 311 | |
| philpem@0 | 312 | /* reenable interrupt */ |
| philpem@0 | 313 | MicoEnableInterrupt(ctx->irq); |
| philpem@0 | 314 | |
| philpem@0 | 315 | /* all done successfully */ |
| philpem@0 | 316 | return(0); |
| philpem@0 | 317 | } |
| philpem@0 | 318 | |
| philpem@0 | 319 | } |
| philpem@0 | 320 | |
| philpem@0 | 321 | |
| philpem@0 | 322 | /* query status of a descriptor */ |
| philpem@0 | 323 | unsigned int MicoDMAGetState(DMADesc_t *desc) |
| philpem@0 | 324 | { |
| philpem@0 | 325 | return(desc->state); |
| philpem@0 | 326 | } |
| philpem@0 | 327 | |
| philpem@0 | 328 | |
| philpem@0 | 329 | /* pause DMA operations (after completion of the currently active descr.) */ |
| philpem@0 | 330 | unsigned int MicoDMAPause(MicoDMACtx_t *ctx) |
| philpem@0 | 331 | { |
| philpem@0 | 332 | if(ctx == 0) |
| philpem@0 | 333 | return(MICODMA_ERR_INVALID_POINTERS); |
| philpem@0 | 334 | |
| philpem@0 | 335 | /* disable interrupt for this DMA device */ |
| philpem@0 | 336 | MicoDisableInterrupt(ctx->irq); |
| philpem@0 | 337 | |
| philpem@0 | 338 | /* set the pause flag */ |
| philpem@0 | 339 | ctx->flags |= MICODMA_CTX_DMA_PAUSED; |
| philpem@0 | 340 | |
| philpem@0 | 341 | /* enable interrupts */ |
| philpem@0 | 342 | MicoEnableInterrupt(ctx->irq); |
| philpem@0 | 343 | |
| philpem@0 | 344 | return(0); |
| philpem@0 | 345 | } |
| philpem@0 | 346 | |
| philpem@0 | 347 | |
| philpem@0 | 348 | |
| philpem@0 | 349 | /* resume DMA operations */ |
| philpem@0 | 350 | unsigned int MicoDMAResume(MicoDMACtx_t *ctx) |
| philpem@0 | 351 | { |
| philpem@0 | 352 | if(ctx == 0) |
| philpem@0 | 353 | return(MICODMA_ERR_INVALID_POINTERS); |
| philpem@0 | 354 | |
| philpem@0 | 355 | /* disable interrupt for the dma-device */ |
| philpem@0 | 356 | MicoDisableInterrupt(ctx->irq); |
| philpem@0 | 357 | |
| philpem@0 | 358 | if(ctx->flags & MICODMA_CTX_DMA_PAUSED){ |
| philpem@0 | 359 | /* reset the pause-flag */ |
| philpem@0 | 360 | ctx->flags &= ~(MICODMA_CTX_DMA_PAUSED); |
| philpem@0 | 361 | |
| philpem@0 | 362 | /* |
| philpem@0 | 363 | * If there are descriptors queued up, |
| philpem@0 | 364 | * kick-start the dma process |
| philpem@0 | 365 | */ |
| philpem@0 | 366 | if(ctx->pHead) |
| philpem@0 | 367 | MicoDMA_Program(ctx->base, (DMADesc_t *)ctx->pHead); |
| philpem@0 | 368 | |
| philpem@0 | 369 | } |
| philpem@0 | 370 | |
| philpem@0 | 371 | /* reenable interrupt for this dma device */ |
| philpem@0 | 372 | MicoEnableInterrupt(ctx->irq); |
| philpem@0 | 373 | |
| philpem@0 | 374 | return(0); |
| philpem@0 | 375 | } |
| philpem@0 | 376 |