andrewm@0: /* andrewm@0: * PRU.cpp andrewm@0: * andrewm@0: * Code for communicating with the Programmable Realtime Unit (PRU) andrewm@0: * on the BeagleBone AM335x series processors. The PRU loads and runs andrewm@0: * a separate code image compiled from an assembly file. Here it is andrewm@0: * used to handle audio and SPI ADC/DAC data. andrewm@0: * andrewm@0: * This code is specific to the PRU code in the assembly file; for example, andrewm@0: * it uses certain GPIO resources that correspond to that image. andrewm@0: * andrewm@0: * Created on: May 27, 2014 andrewm@0: * Author: andrewm andrewm@0: */ andrewm@0: andrewm@0: #include "../include/PRU.h" andrewm@0: #include "../include/prussdrv.h" andrewm@0: #include "../include/pruss_intc_mapping.h" andrewm@0: #include "../include/GPIOcontrol.h" andrewm@0: #include "../include/render.h" andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: andrewm@0: // Xenomai-specific includes andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: andrewm@0: using namespace std; andrewm@0: andrewm@0: #define PRU_MEM_MCASP_OFFSET 0x2000 // Offset within PRU-SHARED RAM andrewm@0: #define PRU_MEM_MCASP_LENGTH 0x2000 // Length of McASP memory, in bytes andrewm@0: #define PRU_MEM_DAC_OFFSET 0x0 // Offset within PRU0 RAM andrewm@0: #define PRU_MEM_DAC_LENGTH 0x2000 // Length of ADC+DAC memory, in bytes andrewm@0: #define PRU_MEM_COMM_OFFSET 0x0 // Offset within PRU-SHARED RAM andrewm@0: andrewm@0: #define PRU_SHOULD_STOP 0 andrewm@0: #define PRU_CURRENT_BUFFER 1 andrewm@0: #define PRU_BUFFER_FRAMES 2 andrewm@0: #define PRU_SHOULD_SYNC 3 andrewm@0: #define PRU_SYNC_ADDRESS 4 andrewm@0: #define PRU_SYNC_PIN_MASK 5 andrewm@0: #define PRU_LED_ADDRESS 6 andrewm@0: #define PRU_LED_PIN_MASK 7 andrewm@0: #define PRU_FRAME_COUNT 8 andrewm@0: #define PRU_USE_SPI 9 andrewm@0: andrewm@0: #define PRU_SAMPLE_INTERVAL_NS 45351 // 22050Hz per SPI sample = 45.351us andrewm@0: andrewm@0: #define GPIO0_ADDRESS 0x44E07000 andrewm@0: #define GPIO1_ADDRESS 0x4804C000 andrewm@0: #define GPIO_SIZE 0x198 andrewm@0: #define GPIO_CLEARDATAOUT (0x190 / 4) andrewm@0: #define GPIO_SETDATAOUT (0x194 / 4) andrewm@0: andrewm@0: #define TEST_PIN_GPIO_BASE GPIO0_ADDRESS // Use GPIO0(31) for debugging andrewm@0: #define TEST_PIN_MASK (1 << 31) andrewm@0: #define TEST_PIN2_MASK (1 << 26) andrewm@0: andrewm@0: #define USERLED3_GPIO_BASE GPIO1_ADDRESS // GPIO1(24) is user LED 3 andrewm@0: #define USERLED3_PIN_MASK (1 << 24) andrewm@0: andrewm@0: const unsigned int PRU::kPruGPIODACSyncPin = 5; // GPIO0(5); P9-17 andrewm@0: const unsigned int PRU::kPruGPIOADCSyncPin = 48; // GPIO1(16); P9-15 andrewm@0: andrewm@0: const unsigned int PRU::kPruGPIOTestPin = 60; // GPIO1(28); P9-12 andrewm@0: const unsigned int PRU::kPruGPIOTestPin2 = 31; // GPIO0(31); P9-13 andrewm@0: const unsigned int PRU::kPruGPIOTestPin3 = 26; // GPIO0(26); P8-14 andrewm@0: andrewm@0: extern int gShouldStop; andrewm@0: extern int gRTAudioVerbose; andrewm@0: andrewm@0: // Constructor: specify a PRU number (0 or 1) andrewm@0: PRU::PRU() andrewm@0: : pru_number(0), running(false), spi_enabled(false), gpio_enabled(false), led_enabled(false), andrewm@0: gpio_test_pin_enabled(false), xenomai_gpio_fd(-1), xenomai_gpio(0) andrewm@0: { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // Destructor andrewm@0: PRU::~PRU() andrewm@0: { andrewm@0: if(running) andrewm@0: disable(); andrewm@0: if(gpio_enabled) andrewm@0: cleanupGPIO(); andrewm@0: if(xenomai_gpio_fd >= 0) andrewm@0: close(xenomai_gpio_fd); andrewm@0: } andrewm@0: andrewm@0: // Prepare the GPIO pins needed for the PRU andrewm@0: // If include_test_pin is set, the GPIO output andrewm@0: // is also prepared for an output which can be andrewm@0: // viewed on a scope. If include_led is set, andrewm@0: // user LED 3 on the BBB is taken over by the PRU andrewm@0: // to indicate activity andrewm@0: int PRU::prepareGPIO(int use_spi, int include_test_pin, int include_led) andrewm@0: { andrewm@0: if(use_spi) { andrewm@0: // Prepare DAC CS/ pin: output, high to begin andrewm@0: if(gpio_export(kPruGPIODACSyncPin)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Warning: couldn't export DAC sync pin\n"; andrewm@0: } andrewm@0: if(gpio_set_dir(kPruGPIODACSyncPin, OUTPUT_PIN)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set direction on DAC sync pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: if(gpio_set_value(kPruGPIODACSyncPin, HIGH)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set value on DAC sync pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: // Prepare ADC CS/ pin: output, high to begin andrewm@0: if(gpio_export(kPruGPIOADCSyncPin)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Warning: couldn't export ADC sync pin\n"; andrewm@0: } andrewm@0: if(gpio_set_dir(kPruGPIOADCSyncPin, OUTPUT_PIN)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set direction on ADC sync pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: if(gpio_set_value(kPruGPIOADCSyncPin, HIGH)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set value on ADC sync pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: spi_enabled = true; andrewm@0: } andrewm@0: andrewm@0: if(include_test_pin) { andrewm@0: // Prepare GPIO test output (for debugging), low to begin andrewm@0: if(gpio_export(kPruGPIOTestPin)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Warning: couldn't export GPIO test pin\n"; andrewm@0: } andrewm@0: if(gpio_set_dir(kPruGPIOTestPin, OUTPUT_PIN)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set direction on GPIO test pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: if(gpio_set_value(kPruGPIOTestPin, LOW)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set value on GPIO test pin\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: if(gpio_export(kPruGPIOTestPin2)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Warning: couldn't export GPIO test pin 2\n"; andrewm@0: } andrewm@0: if(gpio_set_dir(kPruGPIOTestPin2, OUTPUT_PIN)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set direction on GPIO test pin 2\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: if(gpio_set_value(kPruGPIOTestPin2, LOW)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set value on GPIO test pin 2\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: if(gpio_export(kPruGPIOTestPin3)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Warning: couldn't export GPIO test pin 3\n"; andrewm@0: } andrewm@0: if(gpio_set_dir(kPruGPIOTestPin3, OUTPUT_PIN)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set direction on GPIO test pin 3\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: if(gpio_set_value(kPruGPIOTestPin3, LOW)) { andrewm@0: if(gRTAudioVerbose) andrewm@0: cout << "Couldn't set value on GPIO test pin 3\n"; andrewm@0: return -1; andrewm@0: } andrewm@0: gpio_test_pin_enabled = true; andrewm@0: } andrewm@0: andrewm@0: if(include_led) { andrewm@0: // Turn off system function for LED3 so it can be reused by PRU andrewm@0: led_set_trigger(3, "none"); andrewm@0: led_enabled = true; andrewm@0: } andrewm@0: andrewm@0: gpio_enabled = true; andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Clean up the GPIO at the end andrewm@0: void PRU::cleanupGPIO() andrewm@0: { andrewm@0: if(!gpio_enabled) andrewm@0: return; andrewm@0: if(spi_enabled) { andrewm@0: gpio_unexport(kPruGPIODACSyncPin); andrewm@0: gpio_unexport(kPruGPIOADCSyncPin); andrewm@0: } andrewm@0: if(gpio_test_pin_enabled) { andrewm@0: gpio_unexport(kPruGPIOTestPin); andrewm@0: gpio_unexport(kPruGPIOTestPin2); andrewm@0: gpio_unexport(kPruGPIOTestPin3); andrewm@0: } andrewm@0: if(led_enabled) { andrewm@0: // Set LED back to default eMMC status andrewm@0: // TODO: make it go back to its actual value before this program, andrewm@0: // rather than the system default andrewm@0: led_set_trigger(3, "mmc1"); andrewm@0: } andrewm@0: andrewm@0: gpio_enabled = gpio_test_pin_enabled = false; andrewm@0: } andrewm@0: andrewm@0: // Initialise and open the PRU andrewm@0: int PRU::initialise(int pru_num, int frames_per_buffer, bool xenomai_test_pin) andrewm@0: { andrewm@0: uint32_t *pruMem = 0; andrewm@0: andrewm@0: if(!gpio_enabled) { andrewm@0: rt_printf("initialise() called before GPIO enabled\n"); andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: pru_number = pru_num; andrewm@0: andrewm@0: /* Initialize structure used by prussdrv_pruintc_intc */ andrewm@0: /* PRUSS_INTC_INITDATA is found in pruss_intc_mapping.h */ andrewm@0: tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA; andrewm@0: andrewm@0: /* Allocate and initialize memory */ andrewm@0: prussdrv_init(); andrewm@0: if(prussdrv_open(PRU_EVTOUT_0)) { andrewm@0: rt_printf("Failed to open PRU driver\n"); andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: /* Map PRU's INTC */ andrewm@0: prussdrv_pruintc_init(&pruss_intc_initdata); andrewm@0: andrewm@0: spi_buffer_frames = frames_per_buffer; andrewm@0: audio_buffer_frames = spi_buffer_frames * 2; andrewm@0: andrewm@0: /* Map PRU memory to pointers */ andrewm@0: prussdrv_map_prumem (PRUSS0_SHARED_DATARAM, (void **)&pruMem); andrewm@0: pru_buffer_comm = (uint32_t *)&pruMem[PRU_MEM_COMM_OFFSET/sizeof(uint32_t)]; andrewm@0: pru_buffer_audio_dac = (int16_t *)&pruMem[PRU_MEM_MCASP_OFFSET/sizeof(uint32_t)]; andrewm@0: andrewm@0: /* ADC memory starts 2(ch)*2(buffers)*2(samples/spi)*bufsize samples later */ andrewm@0: pru_buffer_audio_adc = &pru_buffer_audio_dac[8 * spi_buffer_frames]; andrewm@0: andrewm@0: if(spi_enabled) { andrewm@0: prussdrv_map_prumem (pru_number == 0 ? PRUSS0_PRU0_DATARAM : PRUSS0_PRU1_DATARAM, (void **)&pruMem); andrewm@0: pru_buffer_spi_dac = (uint16_t *)&pruMem[PRU_MEM_DAC_OFFSET/sizeof(uint32_t)]; andrewm@0: andrewm@0: /* ADC memory starts after 8(ch)*2(buffers)*bufsize samples */ andrewm@0: pru_buffer_spi_adc = &pru_buffer_spi_dac[16 * spi_buffer_frames]; andrewm@0: } andrewm@0: else { andrewm@0: pru_buffer_spi_dac = pru_buffer_spi_adc = 0; andrewm@0: } andrewm@0: andrewm@0: /* Set up flags */ andrewm@0: pru_buffer_comm[PRU_SHOULD_STOP] = 0; andrewm@0: pru_buffer_comm[PRU_CURRENT_BUFFER] = 0; andrewm@0: pru_buffer_comm[PRU_BUFFER_FRAMES] = spi_buffer_frames; andrewm@0: pru_buffer_comm[PRU_SHOULD_SYNC] = 0; andrewm@0: pru_buffer_comm[PRU_SYNC_ADDRESS] = 0; andrewm@0: pru_buffer_comm[PRU_SYNC_PIN_MASK] = 0; andrewm@0: if(led_enabled) { andrewm@0: pru_buffer_comm[PRU_LED_ADDRESS] = USERLED3_GPIO_BASE; andrewm@0: pru_buffer_comm[PRU_LED_PIN_MASK] = USERLED3_PIN_MASK; andrewm@0: } andrewm@0: else { andrewm@0: pru_buffer_comm[PRU_LED_ADDRESS] = 0; andrewm@0: pru_buffer_comm[PRU_LED_PIN_MASK] = 0; andrewm@0: } andrewm@0: if(spi_enabled) { andrewm@0: pru_buffer_comm[PRU_USE_SPI] = 1; andrewm@0: } andrewm@0: else { andrewm@0: pru_buffer_comm[PRU_USE_SPI] = 0; andrewm@0: } andrewm@0: andrewm@0: /* Clear ADC and DAC memory */ andrewm@0: if(spi_enabled) { andrewm@0: for(int i = 0; i < PRU_MEM_DAC_LENGTH / 2; i++) andrewm@0: pru_buffer_spi_dac[i] = 0; andrewm@0: } andrewm@0: for(int i = 0; i < PRU_MEM_MCASP_LENGTH / 2; i++) andrewm@0: pru_buffer_audio_dac[i] = 0; andrewm@0: andrewm@0: /* If using GPIO test pin for Xenomai (for debugging), initialise the pointer now */ andrewm@0: if(xenomai_test_pin && xenomai_gpio_fd < 0) { andrewm@0: xenomai_gpio_fd = open("/dev/mem", O_RDWR); andrewm@0: if(xenomai_gpio_fd < 0) andrewm@0: rt_printf("Unable to open /dev/mem for GPIO test pin\n"); andrewm@0: else { andrewm@0: xenomai_gpio = (uint32_t *)mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, xenomai_gpio_fd, TEST_PIN_GPIO_BASE); andrewm@0: if(xenomai_gpio == MAP_FAILED) { andrewm@0: rt_printf("Unable to map GPIO address for test pin\n"); andrewm@0: xenomai_gpio = 0; andrewm@0: close(xenomai_gpio_fd); andrewm@0: xenomai_gpio_fd = -1; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Run the code image in the specified file andrewm@0: int PRU::start(char * const filename) andrewm@0: { andrewm@0: /* Clear any old interrupt */ andrewm@0: prussdrv_pru_clear_event(pru_number == 0 ? PRU0_ARM_INTERRUPT : PRU1_ARM_INTERRUPT); andrewm@0: andrewm@0: /* Load and execute binary on PRU */ andrewm@0: if(prussdrv_exec_program(pru_number, filename)) { andrewm@0: rt_printf("Failed to execute PRU code from %s\n", filename); andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: running = true; andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Main loop to read and write data from/to PRU andrewm@0: void PRU::loop() andrewm@0: { andrewm@0: // Polling interval is 1/4 of the period andrewm@0: RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * spi_buffer_frames / 4; andrewm@0: float *audioInBuffer, *audioOutBuffer; andrewm@0: andrewm@0: audioInBuffer = (float *)malloc(2 * audio_buffer_frames * sizeof(float)); andrewm@0: audioOutBuffer = (float *)malloc(2 * audio_buffer_frames * sizeof(float)); andrewm@0: andrewm@0: if(audioInBuffer == 0 || audioOutBuffer == 0) { andrewm@0: rt_printf("Error: couldn't allocated audio buffers\n"); andrewm@0: return; andrewm@0: } andrewm@0: andrewm@0: while(!gShouldStop) { andrewm@0: // Wait for PRU to move to buffer 1 andrewm@0: while(pru_buffer_comm[PRU_CURRENT_BUFFER] == 0 && !gShouldStop) { andrewm@0: rt_task_sleep(sleepTime); andrewm@0: } andrewm@0: if(gShouldStop) andrewm@0: break; andrewm@0: andrewm@0: if(xenomai_gpio != 0) { andrewm@0: // Set the test pin high andrewm@0: xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN_MASK; andrewm@0: } andrewm@0: andrewm@0: // Render from/to buffer 0 andrewm@0: andrewm@0: // Convert short (16-bit) samples to float andrewm@0: for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) andrewm@0: audioInBuffer[n] = (float)pru_buffer_audio_adc[n] / 32768.0; andrewm@0: andrewm@0: if(spi_enabled) andrewm@0: render(spi_buffer_frames, audio_buffer_frames, audioInBuffer, audioOutBuffer, andrewm@0: pru_buffer_spi_adc, pru_buffer_spi_dac); andrewm@0: else andrewm@0: render(0, audio_buffer_frames, audioInBuffer, audioOutBuffer, 0, 0); andrewm@0: andrewm@0: // Convert float back to short andrewm@0: for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) { andrewm@0: int out = audioOutBuffer[n] * 32768.0; andrewm@0: if(out < -32768) out = -32768; andrewm@0: else if(out > 32767) out = 32767; andrewm@0: pru_buffer_audio_dac[n] = (int16_t)out; andrewm@0: } andrewm@0: andrewm@0: if(xenomai_gpio != 0) { andrewm@0: // Set the test pin high andrewm@0: xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN_MASK; andrewm@0: } andrewm@0: andrewm@0: // Wait for PRU to move to buffer 0 andrewm@0: while(pru_buffer_comm[PRU_CURRENT_BUFFER] != 0 && !gShouldStop) { andrewm@0: rt_task_sleep(sleepTime); andrewm@0: } andrewm@0: andrewm@0: if(gShouldStop) andrewm@0: break; andrewm@0: andrewm@0: if(xenomai_gpio != 0) { andrewm@0: // Set the test pin high andrewm@0: xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN_MASK; andrewm@0: } andrewm@0: andrewm@0: // Render from/to buffer 1 andrewm@0: andrewm@0: // Convert short (16-bit) samples to float andrewm@0: for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) andrewm@0: audioInBuffer[n] = (float)pru_buffer_audio_adc[n + audio_buffer_frames * 2] / 32768.0; andrewm@0: andrewm@0: if(spi_enabled) andrewm@0: render(spi_buffer_frames, audio_buffer_frames, audioInBuffer, audioOutBuffer, andrewm@0: &pru_buffer_spi_adc[spi_buffer_frames * 8], &pru_buffer_spi_dac[spi_buffer_frames * 8]); andrewm@0: else andrewm@0: render(0, audio_buffer_frames, audioInBuffer, audioOutBuffer, 0, 0); andrewm@0: andrewm@0: // Convert float back to short andrewm@0: for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) { andrewm@0: int out = audioOutBuffer[n] * 32768.0; andrewm@0: if(out < -32768) out = -32768; andrewm@0: else if(out > 32767) out = 32767; andrewm@0: pru_buffer_audio_dac[n + audio_buffer_frames * 2] = (int16_t)out; andrewm@0: } andrewm@0: andrewm@0: if(xenomai_gpio != 0) { andrewm@0: // Set the test pin high andrewm@0: xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN_MASK; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Tell PRU to stop andrewm@0: pru_buffer_comm[PRU_SHOULD_STOP] = 1; andrewm@0: andrewm@0: free(audioInBuffer); andrewm@0: free(audioOutBuffer); andrewm@0: } andrewm@0: andrewm@0: // Wait for an interrupt from the PRU indicate it is finished andrewm@0: void PRU::waitForFinish() andrewm@0: { andrewm@0: if(!running) andrewm@0: return; andrewm@0: prussdrv_pru_wait_event (PRU_EVTOUT_0); andrewm@0: prussdrv_pru_clear_event(pru_number == 0 ? PRU0_ARM_INTERRUPT : PRU1_ARM_INTERRUPT); andrewm@0: } andrewm@0: andrewm@0: // Turn off the PRU when done andrewm@0: void PRU::disable() andrewm@0: { andrewm@0: /* Disable PRU and close memory mapping*/ andrewm@0: prussdrv_pru_disable(pru_number); andrewm@0: prussdrv_exit(); andrewm@0: running = false; andrewm@0: } andrewm@0: andrewm@0: // Debugging andrewm@0: void PRU::setGPIOTestPin() andrewm@0: { andrewm@0: if(!xenomai_gpio) andrewm@0: return; andrewm@0: xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN2_MASK; andrewm@0: } andrewm@0: andrewm@0: void PRU::clearGPIOTestPin() andrewm@0: { andrewm@0: if(!xenomai_gpio) andrewm@0: return; andrewm@0: xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN2_MASK; andrewm@0: }