// Raspberry Pi DMA utilities; see https://iosoft.blog for details // // Copyright (c) 2020 Jeremy P Bentham // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include #include #include #include #include #include #include #include #include "rpi_dma_utils.h" // If non-zero, print debug information #define DEBUG 0 // If non-zero, enable PWM hardware output #define PWM_OUT 0 char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD", "TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""}; char *gpio_mode_strs[] = {GPIO_MODE_STRS}; // Virtual memory pointers to acceess GPIO, DMA and PWM from user space MEM_MAP pwm_regs, gpio_regs, dma_regs, clk_regs; // Use mmap to obtain virtual address, given physical void *map_periph(MEM_MAP *mp, void *phys, int size) { mp->phys = phys; mp->size = PAGE_ROUNDUP(size); mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE); mp->virt = map_segment(phys, mp->size); return(mp->virt); } // Allocate uncached memory, get bus & phys addresses void *map_uncached_mem(MEM_MAP *mp, int size) { void *ret; mp->size = PAGE_ROUNDUP(size); mp->fd = open_mbox(); ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 && (mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 && (mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0 ? mp->virt : 0; printf("VC mem handle %u, phys %p, virt %p\n", mp->h, mp->bus, mp->virt); return(ret); } // Free mapped peripheral or memory void unmap_periph_mem(MEM_MAP *mp) { if (mp) { if (mp->fd) { unmap_segment(mp->virt, mp->size); unlock_vc_mem(mp->fd, mp->h); free_vc_mem(mp->fd, mp->h); close_mbox(mp->fd); } else unmap_segment(mp->virt, mp->size); } } // ----- GPIO ----- // Set input or output with pullups void gpio_set(int pin, int mode, int pull) { gpio_mode(pin, mode); gpio_pull(pin, pull); } // Set I/P pullup or pulldown void gpio_pull(int pin, int pull) { volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32; *REG32(gpio_regs, GPIO_GPPUD) = pull; usleep(2); *reg = pin << (pin % 32); usleep(2); *REG32(gpio_regs, GPIO_GPPUD) = 0; *reg = 0; } // Set input or output void gpio_mode(int pin, int mode) { volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3; *reg = (*reg & ~(7 << shift)) | (mode << shift); } // Set an O/P pin void gpio_out(int pin, int val) { volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin/32; *reg = 1 << (pin % 32); } // Get an I/P pin value uint8_t gpio_in(int pin) { volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin/32; return (((*reg) >> (pin % 32)) & 1); } // Display the values in a GPIO mode register void disp_mode_vals(uint32_t mode) { int i; for (i=0; i<10; i++) printf("%u:%-4s ", i, gpio_mode_strs[(mode>>(i*3)) & 7]); printf("\n"); } // ----- VIDEOCORE MAILBOX ----- // Open mailbox interface, return file descriptor int open_mbox(void) { int fd; if ((fd = open("/dev/vcio", 0)) < 0) fail("Error: can't open VC mailbox\n"); return(fd); } // Close mailbox interface void close_mbox(int fd) { if (fd >= 0) close(fd); } // Send message to mailbox, return first response int, 0 if error uint32_t msg_mbox(int fd, VC_MSG *msgp) { uint32_t ret=0, i; for (i=msgp->dlen/4; i<=msgp->blen/4; i+=4) msgp->uints[i++] = 0; msgp->len = (msgp->blen + 6) * 4; msgp->req = 0; if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0) printf("VC IOCTL failed\n"); else if ((msgp->req&0x80000000) == 0) printf("VC IOCTL error\n"); else if (msgp->req == 0x80000001) printf("VC IOCTL partial error\n"); else ret = msgp->uints[0]; #if DEBUG disp_vc_msg(msgp); #endif return(ret); } // Allocate memory on PAGE_SIZE boundary, return handle uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) { VC_MSG msg={.tag=0x3000c, .blen=12, .dlen=12, .uints={PAGE_ROUNDUP(size), PAGE_SIZE, flags}}; return(msg_mbox(fd, &msg)); } // Lock allocated memory, return bus address void *lock_vc_mem(int fd, int h) { VC_MSG msg={.tag=0x3000d, .blen=4, .dlen=4, .uints={h}}; return(h ? (void *)msg_mbox(fd, &msg) : 0); } // Unlock allocated memory uint32_t unlock_vc_mem(int fd, int h) { VC_MSG msg={.tag=0x3000e, .blen=4, .dlen=4, .uints={h}}; return(h ? msg_mbox(fd, &msg) : 0); } // Free memory uint32_t free_vc_mem(int fd, int h) { VC_MSG msg={.tag=0x3000f, .blen=4, .dlen=4, .uints={h}}; return(h ? msg_mbox(fd, &msg) : 0); } uint32_t set_vc_clock(int fd, int id, uint32_t freq) { VC_MSG msg1={.tag=0x38001, .blen=8, .dlen=8, .uints={id, 1}}; VC_MSG msg2={.tag=0x38002, .blen=12, .dlen=12, .uints={id, freq, 0}}; msg_mbox(fd, &msg1); disp_vc_msg(&msg1); msg_mbox(fd, &msg2); disp_vc_msg(&msg2); return(0); } // Display mailbox message void disp_vc_msg(VC_MSG *msgp) { int i; printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag, msgp->blen, msgp->dlen); for (i=0; iblen/4; i++) printf("%08X ", msgp->uints[i]); printf("\n"); } // ----- VIRTUAL MEMORY ----- // Get virtual memory segment for peripheral regs or physical mem void *map_segment(void *addr, int size) { int fd; void *mem; size = PAGE_ROUNDUP(size); if ((fd = open ("/dev/mem", O_RDWR|O_SYNC|O_CLOEXEC)) < 0) fail("Error: can't open /dev/mem, run using sudo\n"); mem = mmap(0, size, PROT_WRITE|PROT_READ, MAP_SHARED, fd, (uint32_t)addr); close(fd); #if DEBUG printf("Map %p -> %p\n", (void *)addr, mem); #endif if (mem == MAP_FAILED) fail("Error: can't map memory\n"); return(mem); } // Free mapped memory void unmap_segment(void *mem, int size) { if (mem) munmap(mem, PAGE_ROUNDUP(size)); } // ----- DMA ----- // Enable and reset DMA void enable_dma(int chan) { *REG32(dma_regs, DMA_ENABLE) |= (1 << chan); *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; } // Start DMA, given first control block void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) { *REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp); *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag *REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1|csval; // Start DMA } // Return remaining transfer length uint32_t dma_transfer_len(int chan) { return(*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); } // Check if DMA is active uint32_t dma_active(int chan) { return((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); } // Halt current DMA operation by resetting controller void stop_dma(int chan) { if (dma_regs.virt) *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; } // Display DMA registers void disp_dma(int chan) { volatile uint32_t *p=REG32(dma_regs, DMA_REG(chan, DMA_CS)); int i=0; while (dma_regstrs[i][0]) { printf("%-7s %08X ", dma_regstrs[i++], *p++); if (i%5==0 || dma_regstrs[i][0]==0) printf("\n"); } } // ----- PWM ----- // Initialise PWM void init_pwm(int freq, int range, int val) { stop_pwm(); if (*REG32(pwm_regs, PWM_STA) & 0x100) { printf("PWM bus error\n"); *REG32(pwm_regs, PWM_STA) = 0x100; } #if USE_VC_CLOCK_SET set_vc_clock(mbox_fd, PWM_CLOCK_ID, freq); #else int divi=CLOCK_HZ / freq; *REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | (1 << 5); while (*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7)) ; *REG32(clk_regs, CLK_PWM_DIV) = CLK_PASSWD | (divi << 12); *REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | 6 | (1 << 4); while ((*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7)) == 0) ; #endif usleep(100); *REG32(pwm_regs, PWM_RNG1) = range; *REG32(pwm_regs, PWM_FIF1) = val; #if PWM_OUT gpio_mode(PWM_PIN, PWM_PIN==12 ? GPIO_ALT0 : GPIO_ALT5); #endif } // Start PWM operation void start_pwm(void) { *REG32(pwm_regs, PWM_CTL) = PWM_CTL_USEF1 | PWM_ENAB; } // Stop PWM operation void stop_pwm(void) { if (pwm_regs.virt) { *REG32(pwm_regs, PWM_CTL) = 0; usleep(100); } } // EOF