diff --git a/src/arm/raspberry_pi.c b/src/arm/raspberry_pi.c index b7a12f0..743f9f7 100644 --- a/src/arm/raspberry_pi.c +++ b/src/arm/raspberry_pi.c @@ -23,14 +23,15 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include +#include #include #include -#include #include -#include -#include "common.h" #include "arm/raspberry_pi.h" +#include "common.h" #define PLATFORM_NAME_RASPBERRY_PI_B_REV_1 "Raspberry Pi Model B Rev 1" #define PLATFORM_NAME_RASPBERRY_PI_A_REV_2 "Raspberry Pi Model A Rev 2" @@ -52,22 +53,251 @@ #define PLATFORM_RASPBERRY_PI3_B 9 #define MMAP_PATH "/dev/mem" #define BCM2835_PERI_BASE 0x20000000 -#define BCM2835_GPIO_BASE (BCM2835_PERI_BASE + 0x200000) #define BCM2836_PERI_BASE 0x3f000000 -#define BCM2836_GPIO_BASE (BCM2836_PERI_BASE + 0x200000) #define BCM2835_BLOCK_SIZE (4 * 1024) #define BCM2836_BLOCK_SIZE (4 * 1024) +#define BCM2837_PERI_BASE (0x3F000000) +#define BCM2837_BLOCK_SIZE (4 * 1024) #define BCM283X_GPSET0 0x001c #define BCM283X_GPCLR0 0x0028 #define BCM2835_GPLEV0 0x0034 #define MAX_SIZE 64 -// MMAP +#define GPIO_OFFSET (0x200000) +#define PWM_OFFSET (0x20C000) +#define CLOCK_OFFSET (0x101000) + +// PWM +#define PWM_BASE_FREQ (19200000) + +// Base register addresses +#define PWM_CTL (0) +#define PWM_RNG1 (4) +#define PWM_DAT1 (5) +#define PWMCLK_CNTL (40) +#define PWMCLK_DIV (41) +// Register addresses offsets divided by 4 (register addresses are word (32-bit) aligned +#define PWM_RNG (4095) +#define DEFAULT_PERIOD_US 500 + +#define MIN_PERIOD_US 400 +#define MAX_PERIOD_US 500000 + +static volatile unsigned* clk_reg = NULL; +static volatile unsigned* gpio_reg = NULL; +static volatile unsigned* pwm_reg = NULL; + + +// GPIO MMAP static uint8_t* mmap_reg = NULL; static int mmap_fd = 0; static int mmap_size; static unsigned int mmap_count = 0; static int platform_detected = 0; +static uint32_t peripheral_base = BCM2835_PERI_BASE; +static uint32_t block_size = BCM2835_BLOCK_SIZE; + +static bool pwm_enabled = false; + +/** +* Calculate the PWM clk divisor given a desired period +*/ +static inline uint32_t +calc_divisor(uint32_t desired_period_us) +{ + float desired_freq = 1.0 / (desired_period_us / 1000000.0); + float divisor = PWM_BASE_FREQ / (desired_freq * PWM_RNG); + return (uint32_t)(divisor + .5); // Round to nearest integer +} + +/** +* Memory map an arbitrary address +*/ +static volatile unsigned* +mmap_reg_addr(unsigned long base_addr) +{ + int mem_fd = 0; + void* reg_addr_map = MAP_FAILED; + + /* open /dev/mem.....need to run program as root i.e. use sudo or su */ + if (!mem_fd) { + if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { + perror("can't open /dev/mem"); + return NULL; + } + } + + /* mmap IO */ + reg_addr_map = + mmap(NULL, // Any adddress in our space will do + block_size, // Map length + PROT_READ | PROT_WRITE | PROT_EXEC, // Enable reading & writting to mapped memory + MAP_SHARED | MAP_LOCKED, // Shared with other processes + mem_fd, // File to map + base_addr // Offset to base address + ); + + if (reg_addr_map == MAP_FAILED) { + perror("mmap error"); + close(mem_fd); + return NULL; + } + + if (close(mem_fd) < 0) { // No need to keep mem_fd open after mmap + // i.e. we can close /dev/mem + perror("couldn't close /dev/mem file descriptor"); + return NULL; + } + return (volatile unsigned*) reg_addr_map; +} + +/** +* Memory map PWM registers +*/ +static int8_t +mmap_regs() +{ + // Already mapped! + if (clk_reg || gpio_reg || pwm_reg) { + return; + } + + clk_reg = mmap_reg_addr(peripheral_base + CLOCK_OFFSET); + gpio_reg = mmap_reg_addr(peripheral_base + GPIO_OFFSET); + pwm_reg = mmap_reg_addr(peripheral_base + PWM_OFFSET); + + // If all 3 weren't mapped correctly + if (!(clk_reg && gpio_reg && pwm_reg)) { + return -1; + } + + return 0; +} + +static void +pwm_stop() +{ + // stop clock and waiting for busy flag doesn't work, so kill clock + *(clk_reg + PWMCLK_CNTL) = 0x5A000000 | (1 << 5); + usleep(10); + + // wait until busy flag is set + while ((*(clk_reg + PWMCLK_CNTL)) & 0x00000080) { + } + + *(pwm_reg + PWM_CTL) = 0; + usleep(10); +} + +static void +pwm_start() +{ + // source=osc and enable clock + *(clk_reg + PWMCLK_CNTL) = 0x5A000011; + + // start PWM1 in mark space mode + *(pwm_reg + PWM_CTL) |= ((1 << 7) | (1 << 0)); + + usleep(10); +} + + +static void +config_pwm1(uint32_t divisor) +{ + /*GPIO 18 in ALT5 mode for PWM0 */ + // Let's first set pin 18 to input + // taken from #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) + *(gpio_reg + 1) &= ~(7 << 24); + // then set it to ALT5 function PWM0 + // taken from #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= + // (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) + *(gpio_reg + 1) |= (2 << 24); + + pwm_stop(); + + // set divisor + *(clk_reg + PWMCLK_DIV) = 0x5A000000 | (divisor << 12); + + // disable PWM & start from a clean slate + *(pwm_reg + PWM_CTL) = 0; + + // needs some time until the PWM module gets disabled, without the delay the PWM module crashs + usleep(10); + + // set the number of counts that constitute a period with 0 for 20 milliseconds = 320 bits + *(pwm_reg + PWM_RNG1) = PWM_RNG; + usleep(10); + + // If the user has pwm set to enabled + if (pwm_enabled) { + pwm_start(); + } +} + + +// This shouldn't be a float, since it's called in mraa_pwm_read_duty, which +// seems to return the duty cycle in us +float +mraa_raspberry_pi_duty_cycle_read_replace(mraa_pwm_context dev) +{ + uint32_t duty_counts = *(pwm_reg + PWM_DAT1); + return (((float) duty_counts) / PWM_RNG) * dev->period; +} + + +mraa_result_t +mraa_raspberry_pi_pwm_period_us_replace(mraa_pwm_context dev, int period) +{ + config_pwm1(calc_divisor(period / 1000)); + dev->period = period; + return MRAA_SUCCESS; +} + +// Used in pwm.c, duty is actually just the raw counts +// Shouldn't be a float +mraa_result_t +mraa_raspberry_pi_pwm_write_duty_replace(mraa_pwm_context dev, float duty) +{ + // We are given duty as a percentage of the period + // Convert it to be a percentage of the range + float duty_pct = duty / dev->period; + *(pwm_reg + PWM_DAT1) = (uint32_t)(duty_pct * PWM_RNG); + return MRAA_SUCCESS; +} + +mraa_result_t +mraa_raspberry_pi_pwm_enable_replace(mraa_pwm_context dev, int enable) +{ + if (enable == 1) { + pwm_enabled = true; + pwm_start(); + } else { + pwm_enabled = false; + pwm_stop(); + } + + return MRAA_SUCCESS; +} + +mraa_result_t +mraa_raspberry_pi_pwm_initraw_replace(mraa_pwm_context dev, int pin) +{ + if (pin != 0) { + return MRAA_ERROR_INVALID_PARAMETER; + } + + if (mmap_regs() != 0) { + return MRAA_ERROR_UNSPECIFIED; + } + + config_pwm1(DEFAULT_PERIOD_US); + *(pwm_reg + PWM_DAT1) = 0; + dev->period = DEFAULT_PERIOD_US; + + return MRAA_SUCCESS; +} + mraa_result_t mraa_raspberry_pi_spi_init_pre(int index) @@ -188,13 +418,8 @@ mraa_raspberry_pi_mmap_setup(mraa_gpio_context dev, mraa_boolean_t en) return MRAA_ERROR_INVALID_HANDLE; } - if (platform_detected == PLATFORM_RASPBERRY_PI2_B_REV_1) { - mmap_reg = (uint8_t*) mmap(NULL, BCM2836_BLOCK_SIZE, PROT_READ | PROT_WRITE, - MAP_FILE | MAP_SHARED, mmap_fd, BCM2836_GPIO_BASE); - } else { - mmap_reg = (uint8_t*) mmap(NULL, BCM2835_BLOCK_SIZE, PROT_READ | PROT_WRITE, - MAP_FILE | MAP_SHARED, mmap_fd, BCM2835_GPIO_BASE); - } + mmap_reg = (uint8_t*) mmap(NULL, block_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, + mmap_fd, peripheral_base + GPIO_OFFSET); if (mmap_reg == MAP_FAILED) { syslog(LOG_ERR, "raspberry mmap: failed to mmap"); mmap_reg = NULL; @@ -271,11 +496,15 @@ mraa_raspberry_pi() b->platform_name = PLATFORM_NAME_RASPBERRY_PI2_B_REV_1; platform_detected = PLATFORM_RASPBERRY_PI2_B_REV_1; b->phy_pin_count = MRAA_RASPBERRY_PI2_B_REV_1_PINCOUNT; + peripheral_base = BCM2836_PERI_BASE; + block_size = BCM2836_BLOCK_SIZE; } else if (strstr(line, "a02082") || strstr(line, "a020a0") || strstr(line, "a22082") || strstr(line, "a32082")) { b->platform_name = PLATFORM_NAME_RASPBERRY_PI3_B; platform_detected = PLATFORM_RASPBERRY_PI3_B; b->phy_pin_count = MRAA_RASPBERRY_PI3_B_PINCOUNT; + peripheral_base = BCM2837_PERI_BASE; + block_size = BCM2837_BLOCK_SIZE; } else { b->platform_name = PLATFORM_NAME_RASPBERRY_PI_B_REV_1; platform_detected = PLATFORM_RASPBERRY_PI_B_REV_1; @@ -293,7 +522,7 @@ mraa_raspberry_pi() if (!tweakedCpuinfo) { // See Documentation/devicetree/bindings/arm/bcm/brcm,bcm2835.txt // for the values - const char *compatible_path = "/proc/device-tree/compatible"; + const char* compatible_path = "/proc/device-tree/compatible"; if (mraa_file_contains(compatible_path, "raspberrypi,model-b")) { b->platform_name = PLATFORM_NAME_RASPBERRY_PI_B_REV_1; platform_detected = PLATFORM_RASPBERRY_PI_B_REV_1; @@ -340,9 +569,9 @@ mraa_raspberry_pi() b->aio_count = 0; b->adc_raw = 0; b->adc_supported = 0; - b->pwm_default_period = 500; - b->pwm_max_period = 2147483; - b->pwm_min_period = 1; + b->pwm_default_period = DEFAULT_PERIOD_US; + b->pwm_max_period = MAX_PERIOD_US; + b->pwm_min_period = MIN_PERIOD_US; if (b->phy_pin_count == 0) { free(b); @@ -392,6 +621,11 @@ mraa_raspberry_pi() b->adv_func->i2c_init_pre = &mraa_raspberry_pi_i2c_init_pre; b->adv_func->gpio_mmap_setup = &mraa_raspberry_pi_mmap_setup; b->adv_func->spi_frequency_replace = &mraa_raspberry_pi_spi_frequency_replace; + b->adv_func->pwm_init_raw_replace = &mraa_raspberry_pi_pwm_initraw_replace; + b->adv_func->pwm_write_replace = &mraa_raspberry_pi_pwm_write_duty_replace; + b->adv_func->pwm_period_replace = &mraa_raspberry_pi_pwm_period_us_replace; + b->adv_func->pwm_read_replace = &mraa_raspberry_pi_duty_cycle_read_replace; + b->adv_func->pwm_enable_replace = &mraa_raspberry_pi_pwm_enable_replace; strncpy(b->pins[0].name, "INVALID", MRAA_PIN_NAME_SIZE); b->pins[0].capabilities = (mraa_pincapabilities_t){ 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -450,9 +684,10 @@ mraa_raspberry_pi() b->pins[11].gpio.mux_total = 0; strncpy(b->pins[12].name, "GPIO18", MRAA_PIN_NAME_SIZE); - b->pins[12].capabilities = (mraa_pincapabilities_t){ 1, 1, 0, 0, 0, 0, 0, 0 }; + b->pins[12].capabilities = (mraa_pincapabilities_t){ 1, 1, 1, 0, 0, 0, 0, 0 }; b->pins[12].gpio.pinmap = pin_base + 18; b->pins[12].gpio.mux_total = 0; + b->pins[12].pwm.pinmap = 0; if (platform_detected == PLATFORM_RASPBERRY_PI_B_REV_1) { strncpy(b->pins[13].name, "GPIO21", MRAA_PIN_NAME_SIZE); @@ -593,8 +828,7 @@ mraa_raspberry_pi() if ((platform_detected == PLATFORM_RASPBERRY_PI_A_PLUS_REV_1) || (platform_detected == PLATFORM_RASPBERRY_PI_B_PLUS_REV_1) || (platform_detected == PLATFORM_RASPBERRY_PI2_B_REV_1) || - (platform_detected == PLATFORM_RASPBERRY_PI3_B) || - (platform_detected == PLATFORM_RASPBERRY_PI_ZERO)) { + (platform_detected == PLATFORM_RASPBERRY_PI3_B) || (platform_detected == PLATFORM_RASPBERRY_PI_ZERO)) { strncpy(b->pins[27].name, "ID_SD", MRAA_PIN_NAME_SIZE); b->pins[27].capabilities = (mraa_pincapabilities_t){ 1, 0, 0, 0, 0, 0, 0, 0 };