mediacore-hid/src/gpio/dma.cpp
2021-04-23 13:12:32 +02:00

796 lines
No EOL
23 KiB
C++

/*
* This file is part of RPIO-PWM.
*
* Copyright
*
* Copyright (C) 2020 Xinkai Wang <xinkaiwang1017@gmail.com>
*
* License
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details at
* <http://www.gnu.org/licenses/lgpl-3.0-standalone.html>
*
* Documentation
*
* https://github.com/xinkaiwang/rpio-pwm
*
* dma.c, based on the excellent servod.c by Richard Hirst, provides flexible
* PWM via DMA for the Raspberry Pi, supporting a resolution of up to 1us,
* all 15 DMA channels, multiple GPIOs per channel, timing by PWM (default)
* or PCM, a Python wrapper, and more.
*
* Feedback is much appreciated.
*/
#include "dma.hpp"
#include <bcm_host.h>
#include <fcntl.h>
#include <iostream>
#include <sys/mman.h>
#include "mailbox.h"
#define DMY 255 // Used to represent an invalid P1 pin, or unmapped servo
#define NUM_P1PINS 40
#define NUM_P5PINS 8
#define MAX_MEMORY_USAGE \
(16 * 1024 * 1024) /* Somewhat arbitrary limit of 16MB */
#define DEFAULT_CYCLE_TIME_US 20000
#define DEFAULT_STEP_TIME_US 10
#define DEFAULT_SERVO_MIN_US 500
#define DEFAULT_SERVO_MAX_US 2500
#define DEVFILE "/dev/servoblaster"
#define CFGFILE "/dev/servoblaster-cfg"
#define PAGE_SIZE 4096
#define PAGE_SHIFT 12
#define DMA_CHAN_SIZE 0x100
#define DMA_CHAN_MIN 0
#define DMA_CHAN_MAX 14
#define DMA_CHAN_DEFAULT 14
#define DMA_CHAN_PI4 7
#define DMA_BASE_OFFSET 0x00007000
#define DMA_LEN DMA_CHAN_SIZE *(DMA_CHAN_MAX + 1)
#define PWM_BASE_OFFSET 0x0020C000
#define PWM_LEN 0x28
#define CLK_BASE_OFFSET 0x00101000
#define CLK_LEN 0xA8
#define GPIO_BASE_OFFSET 0x00200000
#define GPIO_LEN 0x100
#define PCM_BASE_OFFSET 0x00203000
#define PCM_LEN 0x24
#define DMA_VIRT_BASE(hw) ((hw).periph_virt_base + DMA_BASE_OFFSET)
#define PWM_VIRT_BASE(hw) ((hw).periph_virt_base + PWM_BASE_OFFSET)
#define CLK_VIRT_BASE(hw) ((hw).periph_virt_base + CLK_BASE_OFFSET)
#define GPIO_VIRT_BASE(hw) ((hw).periph_virt_base + GPIO_BASE_OFFSET)
#define PCM_VIRT_BASE(hw) ((hw).periph_virt_base + PCM_BASE_OFFSET)
#define PWM_PHYS_BASE(hw) ((hw).periph_phys_base + PWM_BASE_OFFSET)
#define PCM_PHYS_BASE(hw) ((hw).periph_phys_base + PCM_BASE_OFFSET)
#define GPIO_PHYS_BASE(hw) ((hw).periph_phys_base + GPIO_BASE_OFFSET)
#define DMA_NO_WIDE_BURSTS (1 << 26)
#define DMA_WAIT_RESP (1 << 3)
#define DMA_D_DREQ (1 << 6)
#define DMA_PER_MAP(x) ((x) << 16)
#define DMA_END (1 << 1)
#define DMA_RESET (1U << 31)
#define DMA_INT (1 << 2)
#define DMA_CS (0x00 / 4)
#define DMA_CONBLK_AD (0x04 / 4)
#define DMA_SOURCE_AD (0x0c / 4)
#define DMA_DEBUG (0x20 / 4)
#define GPIO_FSEL0 (0x00 / 4)
#define GPIO_SET0 (0x1c / 4)
#define GPIO_CLR0 (0x28 / 4)
#define GPIO_LEV0 (0x34 / 4)
#define GPIO_PULLEN (0x94 / 4)
#define GPIO_PULLCLK (0x98 / 4)
#define GPIO_MODE_IN 0
#define GPIO_MODE_OUT 1
#define PWM_CTL (0x00 / 4)
#define PWM_DMAC (0x08 / 4)
#define PWM_RNG1 (0x10 / 4)
#define PWM_FIFO (0x18 / 4)
#define PWMCLK_CNTL 40
#define PWMCLK_DIV 41
#define PWMCTL_MODE1 (1 << 1)
#define PWMCTL_PWEN1 (1 << 0)
#define PWMCTL_CLRF (1 << 6)
#define PWMCTL_USEF1 (1 << 5)
#define PWMDMAC_ENAB (1U << 31)
#define PWMDMAC_THRSHLD ((15 << 8) | (15 << 0))
#define PCM_CS_A (0x00 / 4)
#define PCM_FIFO_A (0x04 / 4)
#define PCM_MODE_A (0x08 / 4)
#define PCM_RXC_A (0x0c / 4)
#define PCM_TXC_A (0x10 / 4)
#define PCM_DREQ_A (0x14 / 4)
#define PCM_INTEN_A (0x18 / 4)
#define PCM_INT_STC_A (0x1c / 4)
#define PCM_GRAY (0x20 / 4)
#define PCMCLK_CNTL 38
#define PCMCLK_DIV 39
#define PLLDFREQ_MHZ_DEFAULT 500
#define PLLDFREQ_MHZ_PI4 750
// #define DELAY_VIA_PWM 0
// #define DELAY_VIA_PCM 1
#define ROUNDUP(val, blksz) (((val) + ((blksz)-1)) & ~(blksz - 1))
#define BUS_TO_PHYS(x) ((x) & ~0xC0000000)
using namespace wpp;
namespace {
// DmaHardware hardware{};
// L362
static struct {
int handle; /* From mbox_open() */
uint32_t size; /* Required size */
unsigned mem_ref; /* From mem_alloc() */
unsigned bus_addr; /* From mem_lock() */
uint8_t *virt_addr; /* From mapmem() */
} mbox;
void set_servo(DmaChannel &ch, int servo, int width);
// L375
static void udelay(int us) {
struct timespec ts = {0, us * 1000};
nanosleep(&ts, NULL);
}
// L384
// void terminateChannel(DmaChannel &ch) {
// if (ch.dma_reg && mbox.virt_addr) {
// for (int i = 0; i < maxServoCount; i++) {
// if (ch.pins[i]) {
// set_servo(ch, i, 0);
// }
// }
// udelay(ch.cycle_time_us);
// ch.dma_reg[DMA_CS] = DMA_RESET;
// udelay(10);
// }
// // if (restore_gpio_modes) {
// // for (i = 0; i < MAX_SERVOS; i++) {
// // if (servo2gpio[i] != DMY)
// // gpio_set_mode(servo2gpio[i], gpiomode[i]);
// // }
// // }
// }
void terminate(int dummy) {
if (DmaHardware::GetInstance().current_log_level >= LogLevel::Info) {
printf("terminate() %d\n", dummy);
}
for (auto &it : DmaHardware::GetInstance().channels) {
if (auto locked = it.lock()) {
locked->DeactivateChannel();
}
}
if (mbox.virt_addr != NULL) {
unmapmem(mbox.virt_addr, mbox.size);
mem_unlock(mbox.handle, mbox.mem_ref);
mem_free(mbox.handle, mbox.mem_ref);
if (mbox.handle >= 0)
mbox_close(mbox.handle);
}
exit(1);
}
// L480
static uint32_t gpio_get_mode(DmaChannel &ch, uint32_t gpio) {
uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10];
return (fsel >> ((gpio % 10) * 3)) & 7;
}
// L488
static void gpio_set_mode(DmaChannel &ch, uint32_t gpio, uint32_t mode) {
uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10];
fsel &= ~(7 << ((gpio % 10) * 3));
fsel |= mode << ((gpio % 10) * 3);
ch.gpio_reg[GPIO_FSEL0 + gpio / 10] = fsel;
}
// L498
static void gpio_set(DmaChannel &ch, int gpio, int level) {
if (level)
ch.gpio_reg[GPIO_SET0] = 1 << gpio;
else
ch.gpio_reg[GPIO_CLR0] = 1 << gpio;
}
// L506
static uint32_t mem_virt_to_phys(void *virt) {
uint32_t offset = (uint8_t *)virt - mbox.virt_addr;
return mbox.bus_addr + offset;
}
// L515
static void *map_peripheral(uint32_t base, uint32_t len) {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *vaddr;
if (fd < 0)
fatal("rpio-pwm: Failed to open /dev/mem: %m\n");
vaddr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base);
if (vaddr == MAP_FAILED)
fatal("rpio-pwm: Failed to map peripheral at 0x%08x: %m\n", base);
close(fd);
return vaddr;
}
/* Carefully add or remove bits from the turnoff_mask such that regardless
* of where the DMA controller is in its cycle, and whether we are increasing
* or decreasing the pulse width, the generated pulse will only ever be the
* old width or the new width. If we don't take such care then there could be
* a cycle with some pulse width between the two requested ones. That doesn't
* really matter for servos, but when driving LEDs some odd intensity for one
* cycle can be noticeable. It may be that the servo output has been turned
* off via the inactivity timer, which is handled by always setting the turnon
* mask appropriately at the end of this function.
*/
// L556
void set_servo(DmaChannel &ch, int servo, int newWidth) {
volatile uint32_t *dp;
int i;
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
int oldWidth = ch.pins[servo]->servowidth;
if (ch.hw.current_log_level >= LogLevel::Debug) {
printf("set_servo oldWidth=%d new=%d\n", oldWidth, newWidth);
}
if (newWidth > oldWidth) {
dp = ch.turnoff_mask + ch.servostart[servo] + newWidth;
if (dp >= ch.turnoff_mask + ch.num_samples)
dp -= ch.num_samples;
for (i = newWidth; i > oldWidth; i--) {
dp--;
if (dp < ch.turnoff_mask)
dp = ch.turnoff_mask + ch.num_samples - 1;
// printf("%5d, clearing at %p\n", dp - ctl->turnoff, dp);
*dp &= ~mask;
}
} else if (newWidth < oldWidth) {
dp = ch.turnoff_mask + ch.servostart[servo] + newWidth;
if (dp >= ch.turnoff_mask + ch.num_samples)
dp -= ch.num_samples;
for (i = newWidth; i < oldWidth; i++) {
// printf("%5d, setting at %p\n", dp - ctl->turnoff, dp);
*dp++ |= mask;
if (dp >= ch.turnoff_mask + ch.num_samples)
dp = ch.turnoff_mask;
}
}
ch.pins[servo]->servowidth = newWidth;
if (newWidth == 0) {
ch.turnon_mask[servo] = 0;
} else {
ch.turnon_mask[servo] = mask;
}
}
void set_mask_all(DmaChannel &ch, int servo) {
volatile uint32_t *dp;
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) {
*dp++ |= mask;
}
}
void clear_mask_all(DmaChannel &ch, int servo) {
volatile uint32_t *dp;
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) {
*dp++ &= ~mask;
}
}
// L596
// static void setup_sighandlers(void) {
// int i;
// // Catch all signals possible - it is vital we kill the DMA engine
// // on process exit!
// for (i = 0; i < 64; i++) {
// struct sigaction sa;
// memset(&sa, 0, sizeof(sa));
// sa.sa_handler = terminate;
// sigaction(i, &sa, NULL);
// }
// }
// L612
void init_ctrl_data(DmaHardware &hw, DmaChannel &ch) {
dma_cb_t *cbp = ch.cb_base;
uint32_t phys_fifo_addr, cbinfo;
uint32_t phys_gpclr0;
uint32_t phys_gpset0;
int curstart = 0;
uint32_t maskall = 0;
if (ch.invert) {
phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x1c;
phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x28;
} else {
phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x28;
phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x1c;
}
if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) {
phys_fifo_addr = PWM_PHYS_BASE(hw) + 0x18;
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5);
} else {
phys_fifo_addr = PCM_PHYS_BASE(hw) + 0x04;
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2);
}
memset(ch.turnon_mask, 0, maxServoCount * sizeof(*(ch.turnon_mask)));
// for (servo = 0 ; servo < MAX_SERVOS; servo++) {
// servowidth[servo] = 0;
// if (servo2gpio[servo] != DMY) {
// numservos++;
// maskall |= 1 << servo2gpio[servo];
// }
// }
for (int i = 0; i < ch.num_samples; i++)
ch.turnoff_mask[i] = maskall;
for (uint32_t servo = 0; servo < ch.pins.size(); servo++) {
ch.servostart[servo] = curstart;
curstart += ch.num_samples / ch.pins.size();
}
for (int i = 0, servo = 0; i < ch.num_samples; i++) {
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
cbp->src = mem_virt_to_phys(ch.turnoff_mask + i);
cbp->dst = phys_gpclr0;
cbp->length = 4;
cbp->stride = 0;
cbp->next = mem_virt_to_phys(cbp + 1);
cbp++;
if (servo < maxServoCount && i == ch.servostart[servo]) {
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
cbp->src = mem_virt_to_phys(ch.turnon_mask + servo);
cbp->dst = phys_gpset0;
cbp->length = 4;
cbp->stride = 0;
cbp->next = mem_virt_to_phys(cbp + 1);
cbp++;
servo++;
}
// Delay
cbp->info = cbinfo;
cbp->src = mem_virt_to_phys(ch.turnoff_mask); // Any data will do
cbp->dst = phys_fifo_addr;
cbp->length = 4;
cbp->stride = 0;
cbp->next = mem_virt_to_phys(cbp + 1);
cbp++;
}
cbp--;
cbp->next = mem_virt_to_phys(ch.cb_base);
}
// L695
void init_hardware(DmaHardware &hw, DmaChannel &ch) {
if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) {
// Initialise PWM
ch.pwm_reg[PWM_CTL] = 0;
udelay(10);
ch.clk_reg[PWMCLK_CNTL] =
0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4)
udelay(100);
ch.clk_reg[PWMCLK_DIV] =
0x5A000000 | (hw.plldfreq_mhz << 12); // set pwm div to give 1MHz
udelay(100);
ch.clk_reg[PWMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable
udelay(100);
ch.pwm_reg[PWM_RNG1] = ch.step_time_us;
udelay(10);
ch.pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD;
udelay(10);
ch.pwm_reg[PWM_CTL] = PWMCTL_CLRF;
udelay(10);
ch.pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1;
udelay(10);
} else {
// Initialise PCM
ch.pcm_reg[PCM_CS_A] = 1; // Disable Rx+Tx, Enable PCM block
udelay(100);
ch.clk_reg[PCMCLK_CNTL] =
0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4)
udelay(100);
ch.clk_reg[PCMCLK_DIV] =
0x5A000000 | (hw.plldfreq_mhz << 12); // Set pcm div to give 1MHz
udelay(100);
ch.clk_reg[PCMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable
udelay(100);
ch.pcm_reg[PCM_TXC_A] =
0U << 31 | 1 << 30 | 0 << 20 | 0 << 16; // 1 channel, 8 bits
udelay(100);
ch.pcm_reg[PCM_MODE_A] = (ch.step_time_us - 1) << 10;
udelay(100);
ch.pcm_reg[PCM_CS_A] |= 1 << 4 | 1 << 3; // Clear FIFOs
udelay(100);
ch.pcm_reg[PCM_DREQ_A] =
64 << 24 | 64 << 8; // DMA Req when one slot is free?
udelay(100);
ch.pcm_reg[PCM_CS_A] |= 1 << 9; // Enable DMA
udelay(100);
}
// Initialise the DMA
ch.dma_reg[DMA_CS] = DMA_RESET;
udelay(10);
ch.dma_reg[DMA_CS] = DMA_INT | DMA_END;
ch.dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ch.cb_base);
ch.dma_reg[DMA_DEBUG] = 7; // clear debug error flags
ch.dma_reg[DMA_CS] =
0x10880001; // go, mid priority, wait for outstanding writes
if (ch.delay_hw == DelayHardware::DELAY_VIA_PCM) {
ch.pcm_reg[PCM_CS_A] |= 1 << 2; // Enable Tx
}
}
/* Determining the board revision is a lot more complicated than it should be
* (see comments in wiringPi for details). We will just look at the last two
* digits of the Revision string and treat '00' and '01' as errors, '02' and
* '03' as rev 1, and any other hex value as rev 2. 'Pi1 and Pi2 are
* differentiated by the Hardware being BCM2708 or BCM2709.
*
* NOTE: These days we should just use bcm_host_get_model_type().
*/
// L945
void get_model_and_revision(DmaHardware &hw) {
char buf[128], revstr[128], modelstr[128];
char *ptr, *end, *res;
int board_revision;
FILE *fp;
revstr[0] = modelstr[0] = '\0';
fp = fopen("/proc/cpuinfo", "r");
if (!fp)
fatal("Unable to open /proc/cpuinfo: %m\n");
while ((res = fgets(buf, 128, fp))) {
if (!strncasecmp("hardware", buf, 8))
memcpy(modelstr, buf, 128);
else if (!strncasecmp(buf, "revision", 8))
memcpy(revstr, buf, 128);
}
fclose(fp);
if (modelstr[0] == '\0')
fatal("rpio-pwm: No 'Hardware' record in /proc/cpuinfo\n");
if (revstr[0] == '\0')
fatal("rpio-pwm: No 'Revision' record in /proc/cpuinfo\n");
if (strstr(modelstr, "BCM2708"))
hw.board_model = 1;
else if (strstr(modelstr, "BCM2709") || strstr(modelstr, "BCM2835") || strstr(modelstr, "BCM2711"))
hw.board_model = 2;
else
fatal("rpio-pwm: Cannot parse the hardware name string\n");
/* Revisions documented at http://elinux.org/RPi_HardwareHistory */
ptr = revstr + strlen(revstr) - 3;
board_revision = strtol(ptr, &end, 16);
if (end != ptr + 2)
fatal("rpio-pwm: Failed to parse Revision string\n");
if (board_revision < 1)
fatal("rpio-pwm: Invalid board Revision\n");
else if (board_revision < 4)
hw.gpio_cfg = 1;
else if (board_revision < 16)
hw.gpio_cfg = 2;
else
hw.gpio_cfg = 3;
if (bcm_host_is_model_pi4() || strstr(modelstr, "BCM2711") ) {
hw.plldfreq_mhz = PLLDFREQ_MHZ_PI4;
hw.host_is_model_pi4 = true;
// hw.dma_chan = DMA_CHAN_PI4;
} else {
hw.plldfreq_mhz = PLLDFREQ_MHZ_DEFAULT;
hw.host_is_model_pi4 = false;
// hw.dma_chan = DMA_CHAN_DEFAULT;
}
hw.periph_virt_base = bcm_host_get_peripheral_address();
hw.dram_phys_base = bcm_host_get_sdram_address();
hw.periph_phys_base = 0x7e000000;
/*
* See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
*
* 1: MEM_FLAG_DISCARDABLE = 1 << 0 // can be resized to 0 at any time. Use
* for cached data
* MEM_FLAG_NORMAL = 0 << 2 // normal allocating alias. Don't
* use from ARM 4: MEM_FLAG_DIRECT = 1 << 2 // 0xC alias uncached 8:
* MEM_FLAG_COHERENT = 2 << 2 // 0x8 alias. Non-allocating in L2 but coherent
* MEM_FLAG_L1_NONALLOCATING = // Allocating in L2
* (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT)
* 16: MEM_FLAG_ZERO = 1 << 4 // initialise buffer to all zeros
* 32: MEM_FLAG_NO_INIT = 1 << 5 // don't initialise (default is
* initialise to all ones 64: MEM_FLAG_HINT_PERMALOCK = 1 << 6 //
* Likely to be locked for long periods of time
*
*/
if (hw.board_model == 1) {
hw.mem_flag = 0x0c; /* MEM_FLAG_DIRECT | MEM_FLAG_COHERENT */
} else {
hw.mem_flag = 0x04; /* MEM_FLAG_DIRECT */
}
}
// L1184 main()
bool setup_hardware(DmaHardware &hw) {
get_model_and_revision(hw);
// init_idle_timers(hw);
// setup_sighandlers();
return true;
}
} // anonymous namespace
namespace wpp {
// L416
void fatal(const char *fmt, ...) {
printf("fatal() %s\n", fmt);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
terminate(0);
}
//************************** DmaHardware ****************************
// static
DmaHardware &DmaHardware::GetInstance() {
static DmaHardware hardware{};
static bool inited = false;
if (!inited) {
inited = setup_hardware(hardware);
}
return hardware;
}
//************************** DmaChannel ****************************
// static
std::shared_ptr<DmaChannel>
DmaChannel::CreateInstance(const DmaChannelConfig &config) {
auto &hw = DmaHardware::GetInstance();
auto ch = std::make_shared<DmaChannel>(hw, config);
ch->Init();
DmaHardware::GetInstance().channels.push_back(ch);
return ch;
}
DmaChannel::DmaChannel(DmaHardware &hw, const DmaChannelConfig &config)
: hw{hw}, delay_hw{config.delay_hw}, chNum{config.chNum},
cycle_time_us{config.cycleTimeUs},
step_time_us{config.stepTimeUs}, invert{config.invert} {
if (step_time_us < 2 || step_time_us > 1000) {
fatal("Invalid step-size specified");
}
if (cycle_time_us < 1000 || cycle_time_us > 1000000) {
fatal("Invalid cycle-time specified");
}
if (cycle_time_us % step_time_us) {
fatal("cycle-time is not a multiple of step-size");
}
if (cycle_time_us / step_time_us < 100) {
fatal("cycle-time must be at least 100 * step-size");
}
}
void DmaChannel::Init() {
num_samples = cycle_time_us / step_time_us;
num_cbs = num_samples * 2 + maxServoCount;
num_pages = (num_cbs * sizeof(dma_cb_t) + num_samples * 4 +
maxServoCount * 4 + PAGE_SIZE - 1) >>
PAGE_SHIFT;
auto &ch = *this;
ch.dma_reg =
static_cast<uint32_t *>(map_peripheral(DMA_VIRT_BASE(hw), DMA_LEN));
ch.dma_reg += ch.chNum * DMA_CHAN_SIZE / sizeof(uint32_t);
ch.pwm_reg =
static_cast<uint32_t *>(map_peripheral(PWM_VIRT_BASE(hw), PWM_LEN));
ch.pcm_reg =
static_cast<uint32_t *>(map_peripheral(PCM_VIRT_BASE(hw), PCM_LEN));
ch.clk_reg =
static_cast<uint32_t *>(map_peripheral(CLK_VIRT_BASE(hw), CLK_LEN));
ch.gpio_reg =
static_cast<uint32_t *>(map_peripheral(GPIO_VIRT_BASE(hw), GPIO_LEN));
/* Use the mailbox interface to the VC to ask for physical memory */
// Use the mailbox interface to request memory from the VideoCore
// We specifiy (-1) for the handle rather than calling mbox_open()
// so multiple users can share the resource.
mbox.handle = -1; // mbox_open();
mbox.size = ch.num_pages * 4096;
mbox.mem_ref = mem_alloc(mbox.handle, mbox.size, 4096, hw.mem_flag);
if (mbox.mem_ref == 0) {
fatal("Failed to alloc memory from VideoCore\n");
}
mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref);
if (mbox.bus_addr == ~0U) {
mem_free(mbox.handle, mbox.size);
fatal("Failed to lock memory\n");
}
mbox.virt_addr =
static_cast<uint8_t *>(mapmem(BUS_TO_PHYS(mbox.bus_addr), mbox.size));
ch.turnoff_mask = (uint32_t *)mbox.virt_addr;
ch.turnon_mask =
(uint32_t *)(mbox.virt_addr + ch.num_samples * sizeof(uint32_t));
ch.cb_base =
(dma_cb_t *)(mbox.virt_addr + ROUNDUP(ch.num_samples + maxServoCount, 8) *
sizeof(uint32_t));
init_ctrl_data(hw, ch);
init_hardware(hw, ch);
ch.isActive = true;
if (hw.current_log_level >= LogLevel::Info) {
printf("DmaChannel::Init() ch=%d, cycle_time_us=%d, step_time_us=%d, "
"num_samples=%d\n",
chNum, cycle_time_us, step_time_us, num_samples);
}
}
void DmaChannel::DeactivateChannel() {
auto &ch = *this;
if (!ch.IsActive())
return;
if (hw.current_log_level >= LogLevel::Info) {
printf("DmaChannel::DeactivateChannel() ch=%d\n", ch.chNum);
}
ch.isActive = false;
for (int i = 0; i < maxServoCount; i++) {
if (ch.pins[i]) {
ch.pins[i]->DeactivatePin();
}
}
udelay(ch.cycle_time_us);
ch.dma_reg[DMA_CS] = DMA_RESET;
udelay(10);
}
std::shared_ptr<PwmPin>
DmaChannel::CreatePin(const DmaPwmPinConfig &pinConfig) {
auto pin = std::make_shared<PwmPin>(this->shared_from_this(), pinConfig);
// find next available slot in pins
int servo = 0;
while (servo < maxServoCount && pins[servo] != nullptr) {
servo++;
}
if (servo == maxServoCount) {
fatal("run out of availabel slots");
}
pins[servo] = pin;
pin->Init(servo);
set_mask_all(*this, servo);
set_servo(*this, servo, pinConfig.widthInSteps);
return pin;
}
//************************** PwmPin ****************************
PwmPin::PwmPin(std::shared_ptr<DmaChannel> dmaChannel,
const DmaPwmPinConfig &pinConfig)
: ch{dmaChannel}, gpioPinNum{pinConfig.gpioPinNum},
restoreOnExit{pinConfig.restoreOnExit} {
//
}
void PwmPin::Init(int slotIndex) {
this->slotIndex = slotIndex;
this->gpiomode = gpio_get_mode(*ch, gpioPinNum);
gpio_set(*ch, gpioPinNum, ch->invert ? 1 : 0);
gpio_set_mode(*ch, gpioPinNum, GPIO_MODE_OUT);
}
void PwmPin::SetByWidth(const int width) {
if (width < 0 || width > ch->num_samples) {
fatal("width out of range");
}
set_servo(*ch, slotIndex, width);
}
void PwmPin::SetByPercentage(const float pct) {
if (slotIndex < 0) {
fatal("already deactivated?");
}
if (pct < 0.0f || pct > 100.0f) {
fatal("pct out of range");
}
int newWidth = static_cast<int>(pct * ch->num_samples / 100.0f);
set_servo(*ch, slotIndex, newWidth);
}
void PwmPin::SetByActiveTimeUs(const int timeInUs) {
if (slotIndex < 0) {
fatal("already deactivated?");
}
if (timeInUs < 0 || timeInUs > ch->cycle_time_us) {
fatal("timeInUs out of range");
}
int newWidth = timeInUs / ch->step_time_us;
set_servo(*ch, slotIndex, newWidth);
}
void PwmPin::DeactivatePin() {
if (slotIndex < 0) {
// already deactivated?
return;
}
// to avoid destruct before exit scope
std::shared_ptr<PwmPin> self = shared_from_this();
set_servo(*ch, slotIndex, 0 /*newWidth*/);
clear_mask_all(*ch, slotIndex);
ch->pins[slotIndex] = nullptr;
slotIndex = -1;
}
//************************** TestDma ****************************
void TestDma() {
// setup_hardware(hardware);
// auto ch = DmaChannel::CreateInstance(14, 20000,10);
std::cout << "TestDma";
}
} // namespace wpp