// Routines for interfacing with the PRU/PRUSS. Some of the routines are // generic and others specific to communicating with the code running on // the PRU. Only PRU 0 is currently used. // // Call pru_setup first to open PRU device and map memory. // Call pru_exec_program to load prucode code from directory of executable. // Call pru_shutdown when done to close PRU device. // These routines are specific to the code running on the PRU. // Call pru_exec_cmd to send a command to PRU and wait for response. // Call pru_get_cmd_status to get the command completion status. // Call pru_get_cmd_data to get data value set by a command. // Call pru_restart to restart PRU from the restart address. // Call pru_get_pc to get the program counter of the PRU. For debugging // Call pru_write_mem to write data to memory // Call pru_read_mem to read data from memory // Call pru_read_word to read a 32 bit word from memory // Call pru_write_word to write a 32 bit word to memory // // TODO: Use cache control to make memory transfers faster with PRU // // 09/17/23 Added pru_exec_program to set correct path for file to load // 09/12/23 JST Changes to support 5.10 kernel and --sync option // 06/19/19 DJG Added support for 8.5 MHz --rate for Xerox Star and // fixed typo in comment // 04/08/19 DJG Added support for 8.6 MHz --rate for WANG SVP // 03/14/19 DJG Fix comment // 06/23/18 DJG Add 8.68 MHz data rate support. // 05/19/17 DJG Add ability to dump PRU shared memory. // 12/24/15 DJG Comment cleanup // 11/22/15 DJG Add 15 MHz data rate support. // 05/17/15 DJG Added routines to allow dumping of state if PRU halts // due to error. // 01/04/15 DJG Added pru_set_clock to set the PRU clock to generate the // desired mfm clock and data bit rate. // // Copyright 2014 David Gesswein. // This file is part of MFM disk utilities. // // MFM disk utilities is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // MFM disk utilities 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with MFM disk utilities. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msg.h" #include "cmd.h" #include "pru_setup.h" #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) // This is memory for communicating with the PRU/PRUSS processors // DDR is the shared region of main memory, pru_dataram# is data memory for pru #. // pru_sharedram is the shared PRU local memory static void *ddr_mem, *pru_dataram0, *pru_dataram1, *pru_sharedram; static uint32_t ddr_phys_addr; static int ddr_mem_size; static int num_pru; static int data_mem_type[] = {MEM_PRU0_DATA, MEM_PRU1_DATA}; // Map DDR shared memory segment into our address space and return addresses // and size. // // ddrmem: Returns pointer to memory (virtual address). // ddr_phys_addr: Returns physical address of memory. // return: Size of region in bytes. static int pru_allocate_ddr_memory(void **ddrmem, uint32_t *ddr_phys_addr) { uint32_t ddr_offset, ddr_mem_size; FILE *fin; fin = fopen("/sys/class/uio/uio0/maps/map1/addr", "r"); if (fin == NULL) { perror("Unable to open DDR map address"); exit(1); } fscanf(fin, "%x", ddr_phys_addr); fclose(fin); fin = fopen("/sys/class/uio/uio0/maps/map1/offset", "r"); if (fin == NULL) { perror("Unable to open DDR map offset"); exit(1); } fscanf(fin, "%x", &ddr_offset); fclose(fin); fin = fopen("/sys/class/uio/uio0/maps/map1/size", "r"); if (fin == NULL) { perror("Unable to open DDR map address"); exit(1); } fscanf(fin, "%x", &ddr_mem_size); fclose(fin); //printf("DDR base %x offset %x size %x\n", ddr_base, ddr_offset, // ddrMemSize); *ddrmem = prussdrv_get_virt_addr(*ddr_phys_addr + ddr_offset); return (ddr_mem_size); } // Set up PRU device and map memory // // num_pru_in: Number of PRU to setup, 1 or 2 // return: size of DDR memory segment in bytes. int pru_setup(int num_pru_in) { tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA; int ret; // If clock was changed resetting board can get it in a bad state. Make // sure it is good pru_set_clock(10000000, PRU_SET_CLOCK_NO_HALT); num_pru = num_pru_in; /* Initialize the PRU */ prussdrv_init(); /* Open PRU Interrupt */ ret = prussdrv_open(PRU_EVTOUT_0); if (ret) { msg(MSG_FATAL, "prussdrv_open open 0 failed\n"); return -1; } if (num_pru == 2) { ret = prussdrv_open(PRU_EVTOUT_1); if (ret) { msg(MSG_FATAL, "prussdrv_open open 1 failed\n"); return -1; } } /* Get the interrupt initialized */ prussdrv_pruintc_init(&pruss_intc_initdata); /* Allocate PRU memory. */ if (prussdrv_map_prumem(PRUSS0_PRU0_DATARAM,(void **) &pru_dataram0) == -1) { msg(MSG_FATAL, "prussdrv_map_prumem PRUSS0_PRU0_DATARAM map failed\n"); return -1; } #ifdef PRU_DATARAM_ADDR pru_write_word(MEM_PRU0_DATA, PRU_DATARAM_ADDR, prussdrv_get_phys_addr((void *) pru_dataram0)); #endif if (prussdrv_map_prumem(PRUSS0_PRU1_DATARAM,(void **) &pru_dataram1) == -1) { msg(MSG_FATAL, "prussdrv_map_prumem PRUSS0_PRU1_DATARAM map failed\n"); return -1; } #ifdef PRU_DATARAM_ADDR pru_write_word(MEM_PRU1_DATA, PRU_DATARAM_ADDR, prussdrv_get_phys_addr((void *) pru_dataram1)); #endif if (prussdrv_map_prumem(PRUSS0_SHARED_DATARAM,(void **) &pru_sharedram) == -1) { msg(MSG_FATAL, "prussdrv_map_prumem PRUSS0_SHARED_DATARAM map failed\n"); return -1; } ddr_mem_size = pru_allocate_ddr_memory(&ddr_mem, &ddr_phys_addr); // Give the DDR memory region address to the PRU pru_write_word(MEM_PRU0_DATA, PRU_DDR_ADDR, ddr_phys_addr); pru_write_word(MEM_PRU0_DATA, PRU_DDR_SIZE, ddr_mem_size - 1); pru_write_word(MEM_PRU1_DATA, PRU_DDR_ADDR, ddr_phys_addr); pru_write_word(MEM_PRU1_DATA, PRU_DDR_SIZE, ddr_mem_size - 1); return ddr_mem_size; } // Append path of executable running to filename to load pru code // pru: number of pru to load // filename: filename to load // return: prussdrv_exec_program return code int pru_exec_program(int pru, char *filename) { #define PATH_MAX 4096 char pbuf[PATH_MAX]; char buf[PATH_MAX + strlen(filename) + 1]; int rc; rc = readlink("/proc/self/exe", pbuf, PATH_MAX); if (rc < 0 || rc == PATH_MAX) { msg(MSG_FATAL, "Failed getting path for pru"); exit(1); } strcpy(buf, dirname(pbuf)); strcat(buf, "/"); strcat(buf, filename); rc = prussdrv_exec_program(pru, buf); return rc; } // Wait for completion from PRU and shut down device. void pru_shutdown() { // Wait until PRU0 has finished execution prussdrv_pru_wait_event (PRU_EVTOUT_0); prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT); prussdrv_pru_disable(0); if (num_pru == 2) { prussdrv_pru_wait_event (PRU_EVTOUT_1); prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU1_ARM_INTERRUPT); prussdrv_pru_disable(1); } prussdrv_exit (); } // This sends a command and data word to the PRU and waits for completion or, // for reads, that the read has started. To execute a command first write the // data value if needed then write the command to the command address. The PRU // will execute the command and update the command address with the command // status. The read command which we wish to overlap processing data with // reading returns a command started status and then a command done status. // See cmd.h for commands. // // cmd: Command to execute. // data: Data value for command. // return: Zero if no error, command status if error int pru_exec_cmd(uint32_t cmd, uint32_t data) { uint32_t cmd_word; // The command location is also used for status. Wait for it to change // from what we write pru_write_word(MEM_PRU0_DATA, PRU0_CMD_DATA, data); pru_write_word(MEM_PRU0_DATA, PRU0_CMD, cmd); while ((cmd_word = pru_read_word(MEM_PRU0_DATA, PRU0_CMD)) == cmd) { usleep(500); }; // If not an expected result print an error message if (cmd_word != CMD_STATUS_OK && cmd_word != CMD_STATUS_READ_STARTED) { msg(MSG_ERR, "Command %d fault %x status %x\n", cmd, cmd_word, pru_read_word(MEM_PRU0_DATA, PRU0_STATUS)); return cmd_word; } return 0; } // Return the command status value. See cmd.h for values. uint32_t pru_get_cmd_status(void) { return pru_read_word(MEM_PRU0_DATA, PRU0_CMD); } // Return the command data value. Not all commands return data. uint32_t pru_get_cmd_data(void) { return pru_read_word(MEM_PRU0_DATA, PRU0_CMD_DATA); } // Call this routine to restart PRU at the restart address // The control register is actually outside PRU data memory but the // mapped region is large enough to get to it. // // pru_num: PRU number, 0 or 1 void pru_restart(int pru_num) { // Set the starting address to STOP_ADDR and restart the PRU. pru_write_word(data_mem_type[pru_num], PRU_CONTROL_REG, (RESTART_ADDR << 16) | 2); } // Call this routine to get PRU program counter // The status register is actually outside PRU data memory but the // mapped region is large enough to get to it. uint32_t pru_get_pc(int pru_num) { // Set the starting address to STOP_ADDR and restart the PRU. return pru_read_word(data_mem_type[pru_num], PRU_STATUS_REG); } // Call this routine to get PRU halt state // The status register is actually outside PRU data memory but the // mapped region is large enough to get to it. uint32_t pru_get_halt(int pru_num) { // Check running bit return (pru_read_word(data_mem_type[pru_num], PRU_CONTROL_REG) & 0x8000) == 0; } // Call this routine to print registers // The status register is actually outside PRU data memory but the // mapped region is large enough to get to it. void pru_print_registers(int pru_num) { int i, reg; // Halt so registers are readable pru_write_word(data_mem_type[pru_num], PRU_CONTROL_REG, pru_read_word(data_mem_type[pru_num], PRU_CONTROL_REG) & ~2); for (i = 0; i < 32*4; i += 4) { if (i % 32 == 0) { msg(MSG_FATAL, "%02d: ",i/4); } // Set the starting address to STOP_ADDR and restart the PRU. reg = pru_read_word(data_mem_type[pru_num], PRU_DEBUG + i); msg(MSG_FATAL, "%08x ",reg); if (i % 32 == 28) { msg(MSG_FATAL,"\n"); } } msg(MSG_FATAL,"\n"); } // Call this routine to print memory // The status register is actually outside PRU data memory but the // mapped region is large enough to get to it. // type: type of memory to dump // start: start offset in bytes // len : length in bytes void pru_print_memory(MEM_TYPE mem_type, int start, int len) { int i, reg; for (i = start; i < start+len; i += 4) { if (i % 32 == 0) { msg(MSG_FATAL, "%04x: ",i); } // Set the starting address to STOP_ADDR and restart the PRU. reg = pru_read_word(mem_type, i); msg(MSG_FATAL, "%08x ",reg); if (i % 32 == 28) { msg(MSG_FATAL,"\n"); } } msg(MSG_FATAL,"\n"); } // This returns the size and a pointer to the specified memory type // // mem_type: Type of memory to read // mem_ptr: Address of memory // mem_size: Size of memory static void get_mem_addr_size(MEM_TYPE mem_type, uint8_t **mem_ptr, int *mem_size) { switch(mem_type) { case MEM_PRU0_DATA: *mem_ptr = pru_dataram0; *mem_size = 8*1024; break; case MEM_PRU1_DATA: *mem_ptr = pru_dataram1; *mem_size = 8*1024; break; case MEM_PRU_SHARED: *mem_ptr = pru_sharedram; *mem_size = 12*1024; break; case MEM_DDR: *mem_ptr = ddr_mem; *mem_size = ddr_mem_size; break; default: msg(MSG_FATAL, "Invalid memory type %d\n", mem_type); exit(1); } } // Write to memory. The DDR memory is uncached so slow. // TODO: Should use cached memory and use cache flush/invalidate // to improve performance. // // Offset and length are in bytes // mem_type: Type of memory to read // data: Pointer to location to write data to // len: Length in bytes to read // offset: Offset into memory in bytes to read from int pru_write_mem(MEM_TYPE mem_type, void *data, int len, int offset) { uint8_t *mem_ptr; int mem_size; get_mem_addr_size(mem_type, &mem_ptr, &mem_size); if (offset+len > mem_size || offset < 0 || len < 0) { msg(MSG_FATAL, "Transfer exceeds DDR size %d %d %d\n", offset, len, mem_size); exit(1); } memcpy(&mem_ptr[offset], data, len); return len; } // Read from memory // // mem_type: Type of memory to read // data: Pointer to location to write data to // len: Length in bytes to read // offset: Offset into memory in bytes to read from int pru_read_mem(MEM_TYPE mem_type, void *data, int len, int offset) { uint8_t *mem_ptr; int mem_size; get_mem_addr_size(mem_type, &mem_ptr, &mem_size); if (offset+len > mem_size || offset < 0 || len < 0) { msg(MSG_FATAL, "Transfer exceeds DDR size %d %d %d\n", offset, len, mem_size); exit(1); } memcpy(data, &mem_ptr[offset], len); return len; } // Write a word to memory. Offset is in bytes from start of memory type // // mem_type: Type of memory to read // offset: Offset into memory in bytes // return: Memory contents uint32_t pru_read_word(MEM_TYPE mem_type, int offset) { uint8_t *mem_ptr; uint32_t *mem_ptr_32; int mem_size; get_mem_addr_size(mem_type, &mem_ptr, &mem_size); mem_ptr_32 = (uint32_t *) (mem_ptr + offset); return *mem_ptr_32; } // Write a word to memory. // // mem_type: Type of memory to write // offset: Offset into memory in bytes // value: Value to write void pru_write_word(MEM_TYPE mem_type, int offset, uint32_t value) { uint8_t *mem_ptr; uint32_t *mem_ptr_32; int mem_size; get_mem_addr_size(mem_type, &mem_ptr, &mem_size); mem_ptr_32 = (uint32_t *) (mem_ptr + offset); *mem_ptr_32 = value; // Force a hw write barrier to avoid race conditions __sync_synchronize(); } // Wait for bits to be set in a memory location. Timeout after a second // ptr: Address to check // mask: Mask for bits to check // value: Value looking for // desc: Description for error message on failure static void wait_bits(uint32_t *ptr, uint32_t mask, uint32_t value, char *desc) { int i; int good = 0; // Wait up to a second for (i = 0; !good && i < 1000; i++) { if ((*ptr & mask) == value) { good = 1; } else { usleep(1000); // wait 1 millisecond/ } } if (!good) { msg(MSG_FATAL, "Didn't get %x waiting for %s mask %x reg value %x\n", value, desc, mask, *ptr); exit(1); } } // This routine changes the clock to the PRU if necessary to get the proper // rate from the PWM. This changes the processor and some of the peripherals // include ecap. The code only knows how to deal with specific target bit rates // since calculating proper dividers is non trivial. // tgt_bitrate_hz: Target bitrate for mfm clock and data in Hertz // halt: Halt PRU during clock switch. If code is running best to halt. // return: PRU clock rate in Hertz uint32_t pru_set_clock(uint32_t tgt_bitrate_hz, int halt) { int fd; uint32_t *ptr; int map_length = 0x1000; uint32_t rc = 0; uint32_t orig_control_reg[2]; int pru_num; struct { int bitrate; int pre_divide; int mult; int post_divide; } rates[] = { // This assumes input clock is 24 MHz. We pre divide by 1, multiply by // 33 and divide by 4 to get clock of 198 MHz. That // divided by 18 gives desired 11 MHz bit rate. { 11000000, 1, 33, 4}, // This gives 195 MHz, divided by 13 gives 15 MHz rate. Pre divide // by 2, multiply by 65 and divide by 4. { 15000000, 2, 65, 4}, // This gives 199.68 MHz, divided by 23 gives 8.6817 MHz rate vs // desires 8.68. Pre divide by 5, multiply by 208 and divide by 5. { 8680000, 5, 208, 5}, // This gives 197.8182 MHz, divided by 23 gives 8.6008 MHz rate vs // desires 8.6. Pre divide by 11, multiply by 272 and divide by 3. { 8600000, 11, 272, 3}, // This gives 195.5 MHz, divided by 23 gives 8.5 MHz rate // Pre divide by 8, multiply by 397 and divide by 6. { 8500000, 8, 391, 6} }; int ndx; fd = open("/dev/uio/prcm/module", O_RDWR); ptr = mmap(0, map_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { // Fall back to /dev/mem for older setups fd = open("/dev/mem", O_RDWR); ptr = mmap(0, map_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x44e00000); if (ptr == MAP_FAILED) { msg(MSG_FATAL, "Clock mmap failed", strerror(errno)); exit(1); } } // Halt so switching clock doesn't mess up PRU for (pru_num = 0; pru_num < 2 && halt == PRU_SET_CLOCK_HALT; pru_num++) { orig_control_reg[pru_num] = pru_read_word(data_mem_type[pru_num], PRU_CONTROL_REG); pru_write_word(data_mem_type[pru_num], PRU_CONTROL_REG, orig_control_reg[pru_num] & ~2); } if (tgt_bitrate_hz == 10000000) { // Select normal 200 MHz clock *(ptr + CLKSEL_PRU_ICSS_OCP_CLK/4) = 0; rc = 200000000; // Default clock works for this } else { for (ndx = 0; ndx < ARRAYSIZE(rates); ndx++) { if (rates[ndx].bitrate == tgt_bitrate_hz) { break; } } if (ndx >= ARRAYSIZE(rates)) { msg(MSG_FATAL, "Don't know how to set target bitrate to %d Hz\n", tgt_bitrate_hz); exit(1); } // Turn off spread spectrum *(ptr + CM_SSC_DELTAMSTEP_DPLL_DISP/4) = 0; *(ptr + CM_SSC_MODFREQDIV_DPLL_DISP/4) = 0; // Set to bypass so we can update *(ptr + CM_CLKMODE_DPLL_DISP/4) = 4; wait_bits(ptr + CM_IDLEST_DPLL_DISP/4, 0x100, 0x100, "PLL bypass"); *(ptr + CM_CLKSEL_DPLL_DISP/4) = (rates[ndx].mult << 8) | (rates[ndx].pre_divide - 1); *(ptr + CM_DIV_M2_DPLL_DISP/4) = 0x100 | rates[ndx].post_divide; // Enable PLL *(ptr + CM_CLKMODE_DPLL_DISP/4) = 7; // Verify locked and out of bypass wait_bits(ptr + CM_IDLEST_DPLL_DISP/4, 0x101, 0x1,"Locked and not bypass"); // Select display clock for PRU *(ptr + CLKSEL_PRU_ICSS_OCP_CLK/4) = 1; rc = 24000000 / rates[ndx].pre_divide * rates[ndx].mult / rates[ndx].post_divide; } // Restore PRU state for (pru_num = 0; pru_num < 2 && halt == PRU_SET_CLOCK_HALT; pru_num++) { pru_write_word(data_mem_type[pru_num], PRU_CONTROL_REG, orig_control_reg[pru_num]); } munmap(ptr, map_length); close(fd); return rc; }